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;
5const LEDGER_PAYLOAD_HEADER_LEN: usize = 8 + 4 + 4 + 8;
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 let total_len = LEDGER_PAYLOAD_HEADER_LEN
46 .checked_add(self.payload.len())
47 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow {
48 len: self.payload.len(),
49 })?;
50 let payload_len = u64::try_from(self.payload.len()).map_err(|_| {
51 LedgerPayloadEnvelopeError::PayloadLengthOverflow {
52 len: self.payload.len(),
53 }
54 })?;
55
56 let mut bytes = Vec::with_capacity(total_len);
57 bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
58 bytes.extend_from_slice(LEDGER_PAYLOAD_FORMAT_MARKER);
59 bytes.extend_from_slice(&LEDGER_PAYLOAD_FORMAT_VERSION.to_le_bytes());
60 bytes.extend_from_slice(&payload_len.to_le_bytes());
61 bytes.extend_from_slice(&self.payload);
62 Ok(bytes)
63 }
64
65 pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
67 if bytes.len() < LEDGER_PAYLOAD_MAGIC.len() {
68 return Err(LedgerPayloadEnvelopeError::Truncated {
69 actual: bytes.len(),
70 minimum: LEDGER_PAYLOAD_HEADER_LEN,
71 });
72 }
73
74 let Some(magic) = bytes.get(0..8).and_then(|bytes| bytes.try_into().ok()) else {
75 return Err(LedgerPayloadEnvelopeError::Truncated {
76 actual: bytes.len(),
77 minimum: LEDGER_PAYLOAD_HEADER_LEN,
78 });
79 };
80 if &magic != LEDGER_PAYLOAD_MAGIC {
81 return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
82 }
83
84 let Some(format_marker) = bytes.get(8..12).and_then(|bytes| bytes.try_into().ok()) else {
85 return Err(LedgerPayloadEnvelopeError::Truncated {
86 actual: bytes.len(),
87 minimum: LEDGER_PAYLOAD_HEADER_LEN,
88 });
89 };
90 if &format_marker != LEDGER_PAYLOAD_FORMAT_MARKER {
91 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
92 marker: format_marker,
93 version: None,
94 });
95 }
96
97 let Some(format_version) = bytes.get(12..16).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 let format_version = u32::from_le_bytes(format_version);
104 if format_version != LEDGER_PAYLOAD_FORMAT_VERSION {
105 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
106 marker: format_marker,
107 version: Some(format_version),
108 });
109 }
110
111 let Some(payload_len) = bytes.get(16..24).and_then(|bytes| bytes.try_into().ok()) else {
112 return Err(LedgerPayloadEnvelopeError::Truncated {
113 actual: bytes.len(),
114 minimum: LEDGER_PAYLOAD_HEADER_LEN,
115 });
116 };
117 let payload_len = u64::from_le_bytes(payload_len);
118 let payload_len = usize::try_from(payload_len)
119 .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
120 let expected_len = LEDGER_PAYLOAD_HEADER_LEN
121 .checked_add(payload_len)
122 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
123 if bytes.len() != expected_len {
124 return Err(LedgerPayloadEnvelopeError::LengthMismatch {
125 declared: payload_len,
126 actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
127 });
128 }
129
130 Ok(Self {
131 payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
132 })
133 }
134
135 #[must_use]
137 pub fn payload(&self) -> &[u8] {
138 &self.payload
139 }
140}
141
142#[non_exhaustive]
147#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
148pub enum LedgerPayloadEnvelopeError {
149 #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
151 Truncated {
152 actual: usize,
154 minimum: usize,
156 },
157 #[error("ledger payload envelope has bad magic {found:?}")]
159 BadMagic {
160 found: [u8; 8],
162 },
163 #[error("unsupported ic-memory ledger payload format (marker={marker:?}, version={version:?})")]
166 UnsupportedFormat {
167 marker: [u8; 4],
169 version: Option<u32>,
171 },
172 #[error("ledger payload envelope length {len} is too large")]
174 PayloadTooLarge {
175 len: u64,
177 },
178 #[error("ledger payload envelope length {len} overflows total length")]
180 PayloadLengthOverflow {
181 len: usize,
183 },
184 #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
186 LengthMismatch {
187 declared: usize,
189 actual: usize,
191 },
192}