Skip to main content

sim_lib_journal/
object.rs

1use sha2::{Digest, Sha256};
2use sim_kernel::{ContentId, Datum, Symbol};
3
4use crate::{JournalError, datum_codec};
5
6/// Immutable semantic value and its convenient payload-byte projection.
7///
8/// [`JournalObject::from_bytes`] wraps bytes in `journal/exact-bytes-v1`, so
9/// their public id is a kernel Datum id rather than a physical storage digest.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct JournalObject {
12    pub id: ContentId,
13    pub bytes: Vec<u8>,
14    datum: Datum,
15}
16
17impl JournalObject {
18    /// Constructs a tagged exact-byte semantic value.
19    pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
20        let bytes = bytes.into();
21        let datum = Datum::Node {
22            tag: Symbol::qualified("journal", "exact-bytes-v1"),
23            fields: vec![(Symbol::new("bytes"), Datum::Bytes(bytes.clone()))],
24        };
25        let id = datum
26            .content_id()
27            .expect("exact byte payload is a canonical datum");
28        Self { id, bytes, datum }
29    }
30
31    /// Constructs an object from any canonical Datum.
32    pub fn from_datum(datum: Datum) -> Result<Self, JournalError> {
33        let id = datum
34            .content_id()
35            .map_err(|_| JournalError::NonCanonicalDatum)?;
36        let bytes = exact_bytes(&datum).unwrap_or(datum_codec::encode(&datum)?);
37        Ok(Self { id, bytes, datum })
38    }
39
40    /// Borrows the semantic value.
41    pub fn datum(&self) -> &Datum {
42        &self.datum
43    }
44
45    /// Rejects any object whose claimed semantic id or byte projection differs.
46    pub fn verify(&self) -> Result<(), JournalError> {
47        if self
48            .datum
49            .content_id()
50            .map_err(|_| JournalError::NonCanonicalDatum)?
51            != self.id
52        {
53            return Err(JournalError::CorruptObject(self.id.clone()));
54        }
55        if let Some(exact) = exact_bytes(&self.datum) {
56            if exact != self.bytes {
57                return Err(JournalError::CorruptObject(self.id.clone()));
58            }
59        } else if datum_codec::encode(&self.datum)? != self.bytes {
60            return Err(JournalError::CorruptObject(self.id.clone()));
61        }
62        Ok(())
63    }
64
65    pub(crate) fn storage_bytes(&self) -> Result<Vec<u8>, JournalError> {
66        let encoded = datum_codec::encode(&self.datum)?;
67        let mut out = b"SIMJOBJECT2".to_vec();
68        out.extend((encoded.len() as u64).to_be_bytes());
69        out.extend(encoded);
70        Ok(out)
71    }
72
73    pub(crate) fn from_storage_bytes(bytes: &[u8]) -> Result<Self, JournalError> {
74        if bytes.len() < 19 || &bytes[..11] != b"SIMJOBJECT2" {
75            return Err(JournalError::CorruptState("object format"));
76        }
77        let len = u64::from_be_bytes(
78            bytes[11..19]
79                .try_into()
80                .map_err(|_| JournalError::CorruptState("object length"))?,
81        ) as usize;
82        let encoded = bytes
83            .get(19..)
84            .filter(|tail| tail.len() == len)
85            .ok_or(JournalError::CorruptState("object length"))?;
86        let datum = datum_codec::decode(encoded)?;
87        let mut object = Self::from_datum(datum)?;
88        if let Some(exact) = exact_bytes(&object.datum) {
89            object.bytes = exact;
90        }
91        object.verify()?;
92        Ok(object)
93    }
94}
95
96fn exact_bytes(datum: &Datum) -> Option<Vec<u8>> {
97    let Datum::Node { tag, fields } = datum else {
98        return None;
99    };
100    if *tag != Symbol::qualified("journal", "exact-bytes-v1") || fields.len() != 1 {
101        return None;
102    }
103    match &fields[0] {
104        (name, Datum::Bytes(bytes)) if *name == Symbol::new("bytes") => Some(bytes.clone()),
105        _ => None,
106    }
107}
108
109/// Exact-byte physical identity used only for storage locators.
110pub(crate) fn storage_id(bytes: &[u8]) -> ContentId {
111    ContentId::from_bytes(
112        Symbol::qualified("journal", "sha256-storage-v1"),
113        Sha256::digest(bytes).into(),
114    )
115}