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 if bytes.len() < LEDGER_PAYLOAD_MAGIC.len() {
73 return Err(LedgerPayloadEnvelopeError::Truncated {
74 actual: bytes.len(),
75 minimum: LEDGER_PAYLOAD_HEADER_LEN,
76 });
77 }
78
79 let Some(magic) = bytes.get(0..8).and_then(|bytes| bytes.try_into().ok()) else {
80 return Err(LedgerPayloadEnvelopeError::Truncated {
81 actual: bytes.len(),
82 minimum: LEDGER_PAYLOAD_HEADER_LEN,
83 });
84 };
85 if &magic != LEDGER_PAYLOAD_MAGIC {
86 return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
87 }
88
89 let Some(format_marker) = bytes.get(8..12).and_then(|bytes| bytes.try_into().ok()) else {
90 return Err(LedgerPayloadEnvelopeError::Truncated {
91 actual: bytes.len(),
92 minimum: LEDGER_PAYLOAD_HEADER_LEN,
93 });
94 };
95 if &format_marker != LEDGER_PAYLOAD_FORMAT_MARKER {
96 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
97 marker: format_marker,
98 version: None,
99 });
100 }
101
102 let Some(format_version) = bytes.get(12..16).and_then(|bytes| bytes.try_into().ok()) else {
103 return Err(LedgerPayloadEnvelopeError::Truncated {
104 actual: bytes.len(),
105 minimum: LEDGER_PAYLOAD_HEADER_LEN,
106 });
107 };
108 let format_version = u32::from_le_bytes(format_version);
109 if format_version != LEDGER_PAYLOAD_FORMAT_VERSION {
110 return Err(LedgerPayloadEnvelopeError::UnsupportedFormat {
111 marker: format_marker,
112 version: Some(format_version),
113 });
114 }
115
116 let Some(payload_len) = bytes.get(16..24).and_then(|bytes| bytes.try_into().ok()) else {
117 return Err(LedgerPayloadEnvelopeError::Truncated {
118 actual: bytes.len(),
119 minimum: LEDGER_PAYLOAD_HEADER_LEN,
120 });
121 };
122 let payload_len = u64::from_le_bytes(payload_len);
123 let payload_len = usize::try_from(payload_len)
124 .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
125 if payload_len > crate::constants::MAX_LEDGER_BYTES {
126 return Err(LedgerPayloadEnvelopeError::PayloadTooLarge {
127 len: payload_len as u64,
128 });
129 }
130 let expected_len = LEDGER_PAYLOAD_HEADER_LEN
131 .checked_add(payload_len)
132 .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
133 if bytes.len() != expected_len {
134 return Err(LedgerPayloadEnvelopeError::LengthMismatch {
135 declared: payload_len,
136 actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
137 });
138 }
139
140 Ok(Self {
141 payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
142 })
143 }
144
145 #[must_use]
147 pub fn payload(&self) -> &[u8] {
148 &self.payload
149 }
150}
151
152#[non_exhaustive]
157#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
158pub enum LedgerPayloadEnvelopeError {
159 #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
161 Truncated {
162 actual: usize,
164 minimum: usize,
166 },
167 #[error("ledger payload envelope has bad magic {found:?}")]
169 BadMagic {
170 found: [u8; 8],
172 },
173 #[error("unsupported ic-memory ledger payload format (marker={marker:?}, version={version:?})")]
176 UnsupportedFormat {
177 marker: [u8; 4],
179 version: Option<u32>,
181 },
182 #[error("ledger payload envelope length {len} is too large")]
184 PayloadTooLarge {
185 len: u64,
187 },
188 #[error("ledger payload envelope length {len} overflows total length")]
190 PayloadLengthOverflow {
191 len: usize,
193 },
194 #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
196 LengthMismatch {
197 declared: usize,
199 actual: usize,
201 },
202}