Skip to main content

ic_memory/ledger/
payload.rs

1use super::{CURRENT_LEDGER_SCHEMA_VERSION, CURRENT_PHYSICAL_FORMAT_ID};
2
3const LEDGER_PAYLOAD_MAGIC: &[u8; 8] = b"ICMEMLED";
4const LEDGER_PAYLOAD_HEADER_LEN: usize = 8 + 2 + 4 + 4 + 8;
5
6/// Current logical ledger payload envelope version.
7pub const CURRENT_LEDGER_PAYLOAD_ENVELOPE_VERSION: u16 = 1;
8
9///
10/// LedgerPayloadEnvelope
11///
12/// Logical ledger payload envelope embedded inside one physically committed
13/// generation. This layer is decoded after physical dual-slot recovery selects
14/// a committed generation and before any allocation-ledger DTO is decoded.
15///
16/// This is an advanced protocol byte wrapper, not an authority token. Decoding
17/// an envelope only classifies the logical payload; authority is established
18/// later when [`crate::LedgerCommitStore`] routes the payload, checks
19/// compatibility, validates committed ledger integrity, and returns
20/// [`crate::RecoveredLedger`].
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct LedgerPayloadEnvelope {
23    envelope_version: u16,
24    ledger_schema_version: u32,
25    physical_format_id: u32,
26    payload: Vec<u8>,
27}
28
29impl LedgerPayloadEnvelope {
30    /// Wrap a current logical ledger payload.
31    #[must_use]
32    pub const fn current(payload: Vec<u8>) -> Self {
33        Self {
34            envelope_version: CURRENT_LEDGER_PAYLOAD_ENVELOPE_VERSION,
35            ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
36            physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
37            payload,
38        }
39    }
40
41    #[cfg(test)]
42    pub(crate) const fn from_parts(
43        envelope_version: u16,
44        ledger_schema_version: u32,
45        physical_format_id: u32,
46        payload: Vec<u8>,
47    ) -> Self {
48        Self {
49            envelope_version,
50            ledger_schema_version,
51            physical_format_id,
52            payload,
53        }
54    }
55
56    /// Manually encode the logical payload envelope.
57    #[must_use]
58    pub fn encode(&self) -> Vec<u8> {
59        let mut bytes = Vec::with_capacity(LEDGER_PAYLOAD_HEADER_LEN + self.payload.len());
60        bytes.extend_from_slice(LEDGER_PAYLOAD_MAGIC);
61        bytes.extend_from_slice(&self.envelope_version.to_le_bytes());
62        bytes.extend_from_slice(&self.ledger_schema_version.to_le_bytes());
63        bytes.extend_from_slice(&self.physical_format_id.to_le_bytes());
64        bytes.extend_from_slice(
65            &u64::try_from(self.payload.len())
66                .expect("payload length does not fit in u64")
67                .to_le_bytes(),
68        );
69        bytes.extend_from_slice(&self.payload);
70        bytes
71    }
72
73    /// Manually decode the logical payload envelope.
74    pub fn decode(bytes: &[u8]) -> Result<Self, LedgerPayloadEnvelopeError> {
75        if bytes.len() < LEDGER_PAYLOAD_HEADER_LEN {
76            return Err(LedgerPayloadEnvelopeError::Truncated {
77                actual: bytes.len(),
78                minimum: LEDGER_PAYLOAD_HEADER_LEN,
79            });
80        }
81
82        let magic = <[u8; 8]>::try_from(&bytes[0..8]).expect("magic slice length");
83        if &magic != LEDGER_PAYLOAD_MAGIC {
84            return Err(LedgerPayloadEnvelopeError::BadMagic { found: magic });
85        }
86
87        let envelope_version = u16::from_le_bytes(bytes[8..10].try_into().expect("u16 slice"));
88        let ledger_schema_version =
89            u32::from_le_bytes(bytes[10..14].try_into().expect("u32 slice"));
90        let physical_format_id = u32::from_le_bytes(bytes[14..18].try_into().expect("u32 slice"));
91        let payload_len = u64::from_le_bytes(bytes[18..26].try_into().expect("u64 slice"));
92        let payload_len = usize::try_from(payload_len)
93            .map_err(|_| LedgerPayloadEnvelopeError::PayloadTooLarge { len: payload_len })?;
94        let expected_len = LEDGER_PAYLOAD_HEADER_LEN
95            .checked_add(payload_len)
96            .ok_or(LedgerPayloadEnvelopeError::PayloadLengthOverflow { len: payload_len })?;
97        if bytes.len() != expected_len {
98            return Err(LedgerPayloadEnvelopeError::LengthMismatch {
99                declared: payload_len,
100                actual: bytes.len().saturating_sub(LEDGER_PAYLOAD_HEADER_LEN),
101            });
102        }
103
104        Ok(Self {
105            envelope_version,
106            ledger_schema_version,
107            physical_format_id,
108            payload: bytes[LEDGER_PAYLOAD_HEADER_LEN..].to_vec(),
109        })
110    }
111
112    /// Return the envelope format version.
113    #[must_use]
114    pub const fn envelope_version(&self) -> u16 {
115        self.envelope_version
116    }
117
118    /// Return the logical ledger schema version declared by the envelope.
119    #[must_use]
120    pub const fn ledger_schema_version(&self) -> u32 {
121        self.ledger_schema_version
122    }
123
124    /// Return the physical ledger format ID declared by the envelope.
125    #[must_use]
126    pub const fn physical_format_id(&self) -> u32 {
127        self.physical_format_id
128    }
129
130    /// Borrow the logical ledger payload bytes.
131    #[must_use]
132    pub fn payload(&self) -> &[u8] {
133        &self.payload
134    }
135}
136
137///
138/// LedgerPayloadEnvelopeError
139///
140/// Logical payload envelope could not be classified before ledger decode.
141#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
142pub enum LedgerPayloadEnvelopeError {
143    /// Not enough bytes for an envelope header.
144    #[error("ledger payload envelope is truncated: {actual} bytes, need at least {minimum}")]
145    Truncated {
146        /// Bytes present.
147        actual: usize,
148        /// Minimum bytes required.
149        minimum: usize,
150    },
151    /// Magic bytes do not identify an `ic-memory` ledger payload.
152    #[error("ledger payload envelope has bad magic {found:?}")]
153    BadMagic {
154        /// Magic bytes found.
155        found: [u8; 8],
156    },
157    /// Declared payload length does not fit in this platform's address space.
158    #[error("ledger payload envelope length {len} is too large")]
159    PayloadTooLarge {
160        /// Declared payload length.
161        len: u64,
162    },
163    /// Declared payload length overflowed the total envelope length.
164    #[error("ledger payload envelope length {len} overflows total length")]
165    PayloadLengthOverflow {
166        /// Declared payload length.
167        len: usize,
168    },
169    /// Declared payload length does not match the bytes present.
170    #[error("ledger payload envelope declared {declared} payload bytes but contained {actual}")]
171    LengthMismatch {
172        /// Declared payload length.
173        declared: usize,
174        /// Actual payload length.
175        actual: usize,
176    },
177}