Skip to main content

ic_memory/ledger/
payload.rs

1const LEDGER_PAYLOAD_MAGIC: &[u8; 8] = b"ICMEMLED";
2const LEDGER_PAYLOAD_HEADER_LEN: usize = 8 + 8;
3
4///
5/// LedgerPayloadEnvelope
6///
7/// Logical ledger payload envelope embedded inside one physically committed
8/// generation. This layer is decoded after physical dual-slot recovery selects
9/// a committed generation and before any allocation-ledger DTO is decoded.
10///
11/// This is an advanced protocol byte wrapper, not an authority token. Decoding
12/// an envelope only classifies the logical payload; authority is established
13/// later when [`crate::LedgerCommitStore`] routes the payload, checks
14/// the current ledger format, validates committed ledger integrity, and returns
15/// [`crate::RecoveredLedger`].
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct LedgerPayloadEnvelope {
18    payload: Vec<u8>,
19}
20
21impl LedgerPayloadEnvelope {
22    /// Wrap a current logical ledger payload.
23    #[must_use]
24    pub const fn current(payload: Vec<u8>) -> Self {
25        Self { payload }
26    }
27
28    /// Manually encode the logical payload envelope.
29    ///
30    /// # Panics
31    ///
32    /// Panics only on a platform where an in-memory payload length cannot fit
33    /// into the envelope's `u64` length field.
34    #[must_use]
35    pub fn encode(&self) -> Vec<u8> {
36        self.try_encode()
37            .expect("payload length does not fit in the ledger envelope")
38    }
39
40    /// Try to encode the logical payload envelope.
41    pub fn try_encode(&self) -> Result<Vec<u8>, LedgerPayloadEnvelopeError> {
42        let total_len = LEDGER_PAYLOAD_HEADER_LEN
43            .checked_add(self.payload.len())
44            .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow {
45                len: self.payload.len(),
46            })?;
47        let payload_len = u64::try_from(self.payload.len()).map_err(|_| {
48            LedgerPayloadEnvelopeError::PayloadLengthOverflow {
49                len: self.payload.len(),
50            }
51        })?;
52
53        let mut bytes = Vec::with_capacity(total_len);
54        bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
55        bytes.extend_from_slice(&payload_len.to_le_bytes());
56        bytes.extend_from_slice(&self.payload);
57        Ok(bytes)
58    }
59
60    /// Manually decode the logical payload envelope.
61    pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
62        if bytes.len() < LEDGER_PAYLOAD_HEADER_LEN {
63            return Err(LedgerPayloadEnvelopeError::Truncated {
64                actual: bytes.len(),
65                minimum: LEDGER_PAYLOAD_HEADER_LEN,
66            });
67        }
68
69        let Some(magic) = bytes.get(0..8).and_then(|bytes| bytes.try_into().ok()) else {
70            return Err(LedgerPayloadEnvelopeError::Truncated {
71                actual: bytes.len(),
72                minimum: LEDGER_PAYLOAD_HEADER_LEN,
73            });
74        };
75        if &magic != LEDGER_PAYLOAD_MAGIC {
76            return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
77        }
78
79        let Some(payload_len) = bytes.get(8..16).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        let payload_len = u64::from_le_bytes(payload_len);
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            payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
100        })
101    }
102
103    /// Borrow the logical ledger payload bytes.
104    #[must_use]
105    pub fn payload(&self) -> &[u8] {
106        &self.payload
107    }
108}
109
110///
111/// LedgerPayloadEnvelopeError
112///
113/// Logical payload envelope could not be classified before ledger decode.
114#[non_exhaustive]
115#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
116pub enum LedgerPayloadEnvelopeError {
117    /// Not enough bytes for an envelope header.
118    #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
119    Truncated {
120        /// Bytes present.
121        actual: usize,
122        /// Minimum bytes required.
123        minimum: usize,
124    },
125    /// Magic bytes do not identify an `ic-memory` ledger payload.
126    #[error("ledger payload envelope has bad magic {found:?}")]
127    BadMagic {
128        /// Magic bytes found.
129        found: [u8; 8],
130    },
131    /// Declared payload length does not fit in this platform's address space.
132    #[error("ledger payload envelope length {len} is too large")]
133    PayloadTooLarge {
134        /// Declared payload length.
135        len: u64,
136    },
137    /// Declared payload length overflowed the total envelope length.
138    #[error("ledger payload envelope length {len} overflows total length")]
139    PayloadLengthOverflow {
140        /// Declared payload length.
141        len: usize,
142    },
143    /// Declared payload length does not match the bytes present.
144    #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
145    LengthMismatch {
146        /// Declared payload length.
147        declared: usize,
148        /// Actual payload length.
149        actual: usize,
150    },
151}