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    #[must_use]
30    pub fn encode(&self) -> Vec<u8> {
31        let mut bytes = Vec::with_capacity(LEDGER_PAYLOAD_HEADER_LEN + self.payload.len());
32        bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
33        bytes.extend_from_slice(
34            &u64::try_from(self.payload.len())
35                .expect("payload length does not fit in u64")
36                .to_le_bytes(),
37        );
38        bytes.extend_from_slice(&self.payload);
39        bytes
40    }
41
42    /// Manually decode the logical payload envelope.
43    pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
44        if bytes.len() < LEDGER_PAYLOAD_HEADER_LEN {
45            return Err(LedgerPayloadEnvelopeError::Truncated {
46                actual: bytes.len(),
47                minimum: LEDGER_PAYLOAD_HEADER_LEN,
48            });
49        }
50
51        let magic = <[u8; 8]>::try_from(&bytes[0..8]).expect("magic slice length");
52        if &magic != LEDGER_PAYLOAD_MAGIC {
53            return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
54        }
55
56        let payload_len = u64::from_le_bytes(bytes[8..16].try_into().expect("u64 slice"));
57        let payload_len = usize::try_from(payload_len)
58            .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
59        let expected_len = LEDGER_PAYLOAD_HEADER_LEN
60            .checked_add(payload_len)
61            .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
62        if bytes.len() != expected_len {
63            return Err(LedgerPayloadEnvelopeError::LengthMismatch {
64                declared: payload_len,
65                actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
66            });
67        }
68
69        Ok(Self {
70            payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
71        })
72    }
73
74    /// Borrow the logical ledger payload bytes.
75    #[must_use]
76    pub fn payload(&self) -> &[u8] {
77        &self.payload
78    }
79}
80
81///
82/// LedgerPayloadEnvelopeError
83///
84/// Logical payload envelope could not be classified before ledger decode.
85#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
86pub enum LedgerPayloadEnvelopeError {
87    /// Not enough bytes for an envelope header.
88    #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
89    Truncated {
90        /// Bytes present.
91        actual: usize,
92        /// Minimum bytes required.
93        minimum: usize,
94    },
95    /// Magic bytes do not identify an `ic-memory` ledger payload.
96    #[error("ledger payload envelope has bad magic {found:?}")]
97    BadMagic {
98        /// Magic bytes found.
99        found: [u8; 8],
100    },
101    /// Declared payload length does not fit in this platform's address space.
102    #[error("ledger payload envelope length {len} is too large")]
103    PayloadTooLarge {
104        /// Declared payload length.
105        len: u64,
106    },
107    /// Declared payload length overflowed the total envelope length.
108    #[error("ledger payload envelope length {len} overflows total length")]
109    PayloadLengthOverflow {
110        /// Declared payload length.
111        len: usize,
112    },
113    /// Declared payload length does not match the bytes present.
114    #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
115    LengthMismatch {
116        /// Declared payload length.
117        declared: usize,
118        /// Actual payload length.
119        actual: usize,
120    },
121}