ic_memory/ledger/
payload.rs1const LEDGER_PAYLOAD_MAGIC: &[u8; 8] = b"ICMEMLED";
2const LEDGER_PAYLOAD_FORMAT_MARKER: &[u8; 4] = b"ICMF";
3pub const LEDGER_PAYLOAD_FORMAT_VERSION: u32 = 1;
5use crate::constants::LEDGER_PAYLOAD_HEADER_LEN;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct LedgerPayloadEnvelope {
21 payload: Vec<u8>,
22}
23
24impl LedgerPayloadEnvelope {
25 #[must_use]
27 pub const fn current(payload: Vec<u8>) -> Self {
28 Self { payload }
29 }
30
31 #[must_use]
38 pub fn encode(&self) -> Vec<u8> {
39 self.try_encode()
40 .expect("payload length does not fit in the ledger envelope")
41 }
42
43 pub fn try_encode(&self) -> Result<Vec<u8>, LedgerPayloadEnvelopeError> {
45 if self.payload.len() > crate::constants::MAX_LEDGER_BYTES {
46 return Err(LedgerPayloadEnvelopeError::PayloadTooLarge {
47 len: self.payload.len() as u64,
48 });
49 }
50 let total_len = LEDGER_PAYLOAD_HEADER_LEN
51 .checked_add(self.payload.len())
52 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow {
53 len: self.payload.len(),
54 })?;
55 let payload_len = u64::try_from(self.payload.len()).map_err(|_| {
56 LedgerPayloadEnvelopeError::PayloadLengthOverflow {
57 len: self.payload.len(),
58 }
59 })?;
60
61 let mut bytes = Vec::with_capacity(total_len);
62 bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
63 bytes.extend_from_slice(LEDGER_PAYLOAD_FORMAT_MARKER);
64 bytes.extend_from_slice(&LEDGER_PAYLOAD_FORMAT_VERSION.to_le_bytes());
65 bytes.extend_from_slice(&payload_len.to_le_bytes());
66 bytes.extend_from_slice(&self.payload);
67 Ok(bytes)
68 }
69
70 pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
72 Ok(Self {
73 payload: Self::decode_payload(bytes)?.to_vec(),
74 })
75 }
76
77 pub(super) fn decode_payload(bytes: &[u8]) -> Result<&[u8], LedgerPayloadEnvelopeError> {
80 if bytes.len() < LEDGER_PAYLOAD_MAGIC.len() {
81 return Err(LedgerPayloadEnvelopeError::Truncated {
82 actual: bytes.len(),
83 minimum: LEDGER_PAYLOAD_HEADER_LEN,
84 });
85 }
86
87 let Some(magic) = bytes.get(0..8).and_then(|bytes| bytes.try_into().ok()) else {
88 return Err(LedgerPayloadEnvelopeError::Truncated {
89 actual: bytes.len(),
90 minimum: LEDGER_PAYLOAD_HEADER_LEN,
91 });
92 };
93 if &magic != LEDGER_PAYLOAD_MAGIC {
94 return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
95 }
96
97 let Some(format_marker) = bytes.get(8..12).and_then(|bytes| bytes.try_into().ok()) else {
98 return Err(LedgerPayloadEnvelopeError::Truncated {
99 actual: bytes.len(),
100 minimum: LEDGER_PAYLOAD_HEADER_LEN,
101 });
102 };
103 if &format_marker != LEDGER_PAYLOAD_FORMAT_MARKER {
104 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
105 marker: format_marker,
106 version: None,
107 });
108 }
109
110 let Some(format_version) = bytes.get(12..16).and_then(|bytes| bytes.try_into().ok()) else {
111 return Err(LedgerPayloadEnvelopeError::Truncated {
112 actual: bytes.len(),
113 minimum: LEDGER_PAYLOAD_HEADER_LEN,
114 });
115 };
116 let format_version = u32::from_le_bytes(format_version);
117 if format_version != LEDGER_PAYLOAD_FORMAT_VERSION {
118 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
119 marker: format_marker,
120 version: Some(format_version),
121 });
122 }
123
124 let Some(payload_len) = bytes.get(16..24).and_then(|bytes| bytes.try_into().ok()) else {
125 return Err(LedgerPayloadEnvelopeError::Truncated {
126 actual: bytes.len(),
127 minimum: LEDGER_PAYLOAD_HEADER_LEN,
128 });
129 };
130 let payload_len = u64::from_le_bytes(payload_len);
131 let payload_len = usize::try_from(payload_len)
132 .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
133 if payload_len > crate::constants::MAX_LEDGER_BYTES {
134 return Err(LedgerPayloadEnvelopeError::PayloadTooLarge {
135 len: payload_len as u64,
136 });
137 }
138 let expected_len = LEDGER_PAYLOAD_HEADER_LEN
139 .checked_add(payload_len)
140 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
141 if bytes.len() != expected_len {
142 return Err(LedgerPayloadEnvelopeError::LengthMismatch {
143 declared: payload_len,
144 actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
145 });
146 }
147
148 Ok(&bytes[LEDGER_PAYLOAD_HEADER_LEN..])
149 }
150
151 #[must_use]
153 pub fn payload(&self) -> &[u8] {
154 &self.payload
155 }
156}
157
158#[non_exhaustive]
163#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
164pub enum LedgerPayloadEnvelopeError {
165 #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
167 Truncated {
168 actual: usize,
170 minimum: usize,
172 },
173 #[error("ledger payload envelope has bad magic {found:?}")]
175 BadMagic {
176 found: [u8; 8],
178 },
179 #[error("unsupported ic-memory ledger payload format (marker={marker:?}, version={version:?})")]
182 UnsupportedFormat {
183 marker: [u8; 4],
185 version: Option<u32>,
187 },
188 #[error("ledger payload envelope length {len} is too large")]
190 PayloadTooLarge {
191 len: u64,
193 },
194 #[error("ledger payload envelope length {len} overflows total length")]
196 PayloadLengthOverflow {
197 len: usize,
199 },
200 #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
202 LengthMismatch {
203 declared: usize,
205 actual: usize,
207 },
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn borrowed_payload_decode_shares_storage_through_the_byte_ceiling() {
216 for len in [0, 3, crate::constants::MAX_LEDGER_BYTES] {
217 let bytes = LedgerPayloadEnvelope::current(vec![42; len])
218 .try_encode()
219 .expect("bounded envelope");
220 let payload = LedgerPayloadEnvelope::decode_payload(&bytes).expect("valid envelope");
221
222 assert_eq!(payload.len(), len);
223 assert_eq!(
224 payload.as_ptr(),
225 bytes[LEDGER_PAYLOAD_HEADER_LEN..].as_ptr()
226 );
227 assert!(payload.iter().all(|byte| *byte == 42));
228 }
229 }
230}