ic_memory/ledger/
payload.rs1use super::{CURRENT_LEDGER_SCHEMA_VERSION, CURRENT_PHYSICAL_FORMAT_ID};
2
3const LEDGER_PAYLOAD_MAGIC: &[u8; 8] = b"ICMEMLED";
4const LEDGER_PAYLOAD_HEADER_LEN: usize = 8 + 2 + 4 + 4 + 8;
5
6pub const CURRENT_LEDGER_PAYLOAD_ENVELOPE_VERSION: u16 = 1;
8
9#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct LedgerPayloadEnvelope {
17 envelope_version: u16,
18 ledger_schema_version: u32,
19 physical_format_id: u32,
20 payload: Vec<u8>,
21}
22
23impl LedgerPayloadEnvelope {
24 #[must_use]
26 pub const fn current(payload: Vec<u8>) -> Self {
27 Self {
28 envelope_version: CURRENT_LEDGER_PAYLOAD_ENVELOPE_VERSION,
29 ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
30 physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
31 payload,
32 }
33 }
34
35 #[cfg(test)]
36 pub(crate) const fn from_parts(
37 envelope_version: u16,
38 ledger_schema_version: u32,
39 physical_format_id: u32,
40 payload: Vec<u8>,
41 ) -> Self {
42 Self {
43 envelope_version,
44 ledger_schema_version,
45 physical_format_id,
46 payload,
47 }
48 }
49
50 #[must_use]
52 pub fn encode(&self) -> Vec<u8> {
53 let mut bytes = Vec::with_capacity(LEDGER_PAYLOAD_HEADER_LEN + self.payload.len());
54 bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
55 bytes.extend_from_slice(&self.envelope_version.to_le_bytes());
56 bytes.extend_from_slice(&self.ledger_schema_version.to_le_bytes());
57 bytes.extend_from_slice(&self.physical_format_id.to_le_bytes());
58 bytes.extend_from_slice(
59 &u64::try_from(self.payload.len())
60 .expect("payload length does not fit in u64")
61 .to_le_bytes(),
62 );
63 bytes.extend_from_slice(&self.payload);
64 bytes
65 }
66
67 pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
69 if bytes.len() < LEDGER_PAYLOAD_HEADER_LEN {
70 return Err(LedgerPayloadEnvelopeError::Truncated {
71 actual: bytes.len(),
72 minimum: LEDGER_PAYLOAD_HEADER_LEN,
73 });
74 }
75
76 let magic = <[u8; 8]>::try_from(&bytes[0..8]).expect("magic slice length");
77 if &magic != LEDGER_PAYLOAD_MAGIC {
78 return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
79 }
80
81 let envelope_version = u16::from_le_bytes(bytes[8..10].try_into().expect("u16 slice"));
82 let ledger_schema_version =
83 u32::from_le_bytes(bytes[10..14].try_into().expect("u32 slice"));
84 let physical_format_id = u32::from_le_bytes(bytes[14..18].try_into().expect("u32 slice"));
85 let payload_len = u64::from_le_bytes(bytes[18..26].try_into().expect("u64 slice"));
86 let payload_len = usize::try_from(payload_len)
87 .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
88 let expected_len = LEDGER_PAYLOAD_HEADER_LEN
89 .checked_add(payload_len)
90 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
91 if bytes.len() != expected_len {
92 return Err(LedgerPayloadEnvelopeError::LengthMismatch {
93 declared: payload_len,
94 actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
95 });
96 }
97
98 Ok(Self {
99 envelope_version,
100 ledger_schema_version,
101 physical_format_id,
102 payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
103 })
104 }
105
106 #[must_use]
108 pub const fn envelope_version(&self) -> u16 {
109 self.envelope_version
110 }
111
112 #[must_use]
114 pub const fn ledger_schema_version(&self) -> u32 {
115 self.ledger_schema_version
116 }
117
118 #[must_use]
120 pub const fn physical_format_id(&self) -> u32 {
121 self.physical_format_id
122 }
123
124 #[must_use]
126 pub fn payload(&self) -> &[u8] {
127 &self.payload
128 }
129}
130
131#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
136pub enum LedgerPayloadEnvelopeError {
137 #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
139 Truncated {
140 actual: usize,
142 minimum: usize,
144 },
145 #[error("ledger payload envelope has bad magic {found:?}")]
147 BadMagic {
148 found: [u8; 8],
150 },
151 #[error("ledger payload envelope length {len} is too large")]
153 PayloadTooLarge {
154 len: u64,
156 },
157 #[error("ledger payload envelope length {len} overflows total length")]
159 PayloadLengthOverflow {
160 len: usize,
162 },
163 #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
165 LengthMismatch {
166 declared: usize,
168 actual: usize,
170 },
171}