sim_lib_journal/
object.rs1use sha2::{Digest, Sha256};
2use sim_kernel::{ContentId, Datum, Symbol};
3
4use crate::{JournalError, datum_codec};
5
6#[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 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 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 pub fn datum(&self) -> &Datum {
42 &self.datum
43 }
44
45 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
109pub(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}