Skip to main content

ic_memory/
declaration.rs

1use crate::{
2    key::{StableKey, StableKeyError},
3    schema::SchemaMetadata,
4    slot::{MemoryManagerSlot, MemoryManagerSlotError},
5    text::{DiagnosticTextError, validate_diagnostic_text},
6};
7use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
8
9///
10/// AllocationDeclaration
11///
12/// Checked runtime claim that a stable key should own an allocation slot.
13///
14/// Declarations are supplied by the current binary before opening storage.
15/// Construction and decoding check the stable key, label, slot and schema
16/// metadata. A declaration becomes authoritative only after validation against
17/// the recovered ledger and commitment in a generation.
18///
19
20#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
21#[serde(deny_unknown_fields)]
22pub struct AllocationDeclaration {
23    /// Durable stable key.
24    pub(crate) stable_key: StableKey,
25    /// Claimed allocation slot.
26    pub(crate) slot: MemoryManagerSlot,
27    /// Optional diagnostic label.
28    #[serde(deserialize_with = "deserialize_label")]
29    pub(crate) label: Option<String>,
30    /// Optional diagnostic schema metadata.
31    pub(crate) schema: SchemaMetadata,
32}
33
34impl AllocationDeclaration {
35    /// Build a declaration from raw parts after validating diagnostic metadata.
36    pub fn new(
37        stable_key: impl AsRef<str>,
38        slot: MemoryManagerSlot,
39        label: Option<String>,
40        schema: SchemaMetadata,
41    ) -> Result<Self, DeclarationSnapshotError> {
42        let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
43        validate_label(label.as_deref())?;
44        Ok(Self {
45            stable_key,
46            slot,
47            label,
48            schema,
49        })
50    }
51
52    /// Build a `MemoryManager` declaration with a diagnostic label.
53    pub fn memory_manager(
54        stable_key: impl AsRef<str>,
55        id: u8,
56        label: impl Into<String>,
57    ) -> Result<Self, DeclarationSnapshotError> {
58        Self::memory_manager_with_schema(stable_key, id, label, SchemaMetadata::default())
59    }
60
61    /// Build an unlabeled `MemoryManager` declaration.
62    pub fn memory_manager_unlabeled(
63        stable_key: impl AsRef<str>,
64        id: u8,
65    ) -> Result<Self, DeclarationSnapshotError> {
66        Self::memory_manager_unlabeled_with_schema(stable_key, id, SchemaMetadata::default())
67    }
68
69    /// Build a `MemoryManager` declaration with a diagnostic label and schema metadata.
70    pub fn memory_manager_with_schema(
71        stable_key: impl AsRef<str>,
72        id: u8,
73        label: impl Into<String>,
74        schema: SchemaMetadata,
75    ) -> Result<Self, DeclarationSnapshotError> {
76        let slot =
77            MemoryManagerSlot::new(id).map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
78        Self::new(stable_key, slot, Some(label.into()), schema)
79    }
80
81    /// Build an unlabeled `MemoryManager` declaration with schema metadata.
82    pub fn memory_manager_unlabeled_with_schema(
83        stable_key: impl AsRef<str>,
84        id: u8,
85        schema: SchemaMetadata,
86    ) -> Result<Self, DeclarationSnapshotError> {
87        let slot =
88            MemoryManagerSlot::new(id).map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
89        Self::new(stable_key, slot, None, schema)
90    }
91
92    /// Return the durable stable key claimed by this declaration.
93    #[must_use]
94    pub const fn stable_key(&self) -> &StableKey {
95        &self.stable_key
96    }
97
98    /// Return the allocation slot claimed by this declaration.
99    #[must_use]
100    pub const fn slot(&self) -> &MemoryManagerSlot {
101        &self.slot
102    }
103
104    /// Return the optional diagnostic label.
105    #[must_use]
106    pub fn label(&self) -> Option<&str> {
107        self.label.as_deref()
108    }
109
110    /// Return the optional schema metadata.
111    #[must_use]
112    pub const fn schema(&self) -> &SchemaMetadata {
113        &self.schema
114    }
115}
116
117///
118/// DeclarationSnapshot
119///
120/// Immutable runtime declaration snapshot ready for policy and ownership validation.
121///
122/// Construction and decoding enforce the declaration count and unique keys and
123/// slots. A snapshot is not permission to open storage.
124/// [`crate::MemoryRuntime::bootstrap`] owns validation and persistence before
125/// publishing committed allocation authority.
126///
127
128#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
129pub struct DeclarationSnapshot {
130    /// Runtime declarations.
131    declarations: Vec<AllocationDeclaration>,
132}
133
134impl<'de> Deserialize<'de> for DeclarationSnapshot {
135    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
136        #[derive(Deserialize)]
137        #[serde(rename = "DeclarationSnapshot", deny_unknown_fields)]
138        struct Snapshot {
139            #[serde(deserialize_with = "crate::cbor::deserialize_records")]
140            declarations: Vec<AllocationDeclaration>,
141        }
142
143        let snapshot = Snapshot::deserialize(deserializer)?;
144        Self::new(snapshot.declarations).map_err(D::Error::custom)
145    }
146}
147
148impl DeclarationSnapshot {
149    /// Create and validate a declaration snapshot.
150    pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
151        validate_declaration_set(&declarations)?;
152        Ok(Self { declarations })
153    }
154
155    /// Return true when the snapshot has no declarations.
156    #[must_use]
157    pub const fn is_empty(&self) -> bool {
158        self.declarations.is_empty()
159    }
160
161    /// Return the number of declarations in the snapshot.
162    #[must_use]
163    pub const fn len(&self) -> usize {
164        self.declarations.len()
165    }
166
167    /// Borrow the sealed declarations.
168    #[must_use]
169    pub fn declarations(&self) -> &[AllocationDeclaration] {
170        &self.declarations
171    }
172
173    pub(crate) fn into_declarations(self) -> Vec<AllocationDeclaration> {
174        self.declarations
175    }
176}
177
178///
179/// DeclarationSnapshotError
180///
181/// Declaration snapshot validation failure.
182#[non_exhaustive]
183#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
184pub enum DeclarationSnapshotError {
185    #[error("at most 255 allocation declarations are supported")]
186    TooManyDeclarations,
187    /// Stable-key grammar failure.
188    #[error(transparent)]
189    Key(StableKeyError),
190    /// `MemoryManager` slot validation failure.
191    #[error(transparent)]
192    MemoryManagerSlot(MemoryManagerSlotError),
193    /// A stable key appeared more than once in one snapshot.
194    #[error("stable key '{0}' is declared more than once")]
195    DuplicateStableKey(StableKey),
196    /// An allocation slot appeared more than once in one snapshot.
197    #[error("allocation slot '{0:?}' is declared more than once")]
198    DuplicateSlot(MemoryManagerSlot),
199    /// Present declaration labels must be non-empty.
200    #[error("allocation declaration label must not be empty when present")]
201    EmptyLabel,
202    /// Declaration labels must stay bounded for durable ledger storage.
203    #[error("allocation declaration label must be at most 256 bytes")]
204    LabelTooLong,
205    /// Declaration labels must not require Unicode normalization.
206    #[error("allocation declaration label must be ASCII")]
207    NonAsciiLabel,
208    /// Declaration labels must be printable metadata.
209    #[error("allocation declaration label must not contain ASCII control characters")]
210    ControlCharacterLabel,
211}
212
213fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
214    let Some(label) = label else {
215        return Ok(());
216    };
217    validate_diagnostic_text(label).map_err(|error| match error {
218        DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyLabel,
219        DiagnosticTextError::TooLong => DeclarationSnapshotError::LabelTooLong,
220        DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiLabel,
221        DiagnosticTextError::ControlCharacter => DeclarationSnapshotError::ControlCharacterLabel,
222    })
223}
224
225// Decode explicitly present optional labels through the same constructor rule.
226// Retain the owned string; no intermediate label type or compatibility default.
227fn deserialize_label<'de, D: Deserializer<'de>>(
228    deserializer: D,
229) -> Result<Option<String>, D::Error> {
230    let label = Option::<String>::deserialize(deserializer)?;
231    validate_label(label.as_deref()).map_err(D::Error::custom)?;
232    Ok(label)
233}
234
235fn validate_declaration_set(
236    declarations: &[AllocationDeclaration],
237) -> Result<(), DeclarationSnapshotError> {
238    if declarations.len() > crate::constants::MAX_ALLOCATIONS {
239        return Err(DeclarationSnapshotError::TooManyDeclarations);
240    }
241    let duplicate_keys = crate::key::duplicate_key_positions(
242        declarations
243            .iter()
244            .enumerate()
245            .map(|(index, declaration)| (&declaration.stable_key, index))
246            .collect(),
247    );
248    let mut slots = [false; crate::constants::MAX_ALLOCATIONS];
249
250    for (index, declaration) in declarations.iter().enumerate() {
251        let occupied = &mut slots[usize::from(declaration.slot.id())];
252        if *occupied {
253            return Err(DeclarationSnapshotError::DuplicateSlot(
254                declaration.slot.clone(),
255            ));
256        }
257        *occupied = true;
258        if duplicate_keys[index] {
259            return Err(DeclarationSnapshotError::DuplicateStableKey(
260                declaration.stable_key.clone(),
261            ));
262        }
263    }
264
265    Ok(())
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::slot::MemoryManagerSlot;
272
273    fn declaration(key: &str, id: u8) -> AllocationDeclaration {
274        AllocationDeclaration::new(
275            key,
276            MemoryManagerSlot::new(id).expect("usable slot"),
277            None,
278            SchemaMetadata::default(),
279        )
280        .expect("declaration")
281    }
282
283    #[test]
284    fn declaration_rejects_unbounded_label_metadata() {
285        let err = AllocationDeclaration::new(
286            "app.users.v1",
287            MemoryManagerSlot::new(100).expect("usable slot"),
288            Some("x".repeat(257)),
289            SchemaMetadata::default(),
290        )
291        .expect_err("label too long");
292
293        assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
294    }
295
296    #[test]
297    fn memory_manager_declaration_constructor_builds_common_declaration() {
298        let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
299            .expect("declaration");
300
301        assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
302        assert_eq!(
303            declaration.slot,
304            MemoryManagerSlot::new(100).expect("usable slot")
305        );
306        assert_eq!(declaration.label.as_deref(), Some("orders"));
307        assert_eq!(declaration.schema, SchemaMetadata::default());
308    }
309
310    #[test]
311    fn memory_manager_declaration_constructor_rejects_invalid_slot() {
312        let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
313            .expect_err("sentinel must fail");
314
315        assert!(matches!(
316            err,
317            DeclarationSnapshotError::MemoryManagerSlot(_)
318        ));
319    }
320
321    #[test]
322    fn snapshot_decode_rejects_unusable_memory_manager_slot() {
323        let snapshot = DeclarationSnapshot::new(vec![declaration("app.orders.v1", 100)]).unwrap();
324        let mut value = serde_json::to_value(snapshot).unwrap();
325        value["declarations"][0]["slot"]["slot"]["MemoryManagerId"] = serde_json::json!(255);
326        assert!(serde_json::from_value::<DeclarationSnapshot>(value).is_err());
327    }
328
329    #[test]
330    fn rejects_duplicate_keys() {
331        let err = DeclarationSnapshot::new(vec![
332            declaration("app.users.v1", 100),
333            declaration("app.users.v1", 101),
334        ])
335        .expect_err("duplicate key");
336
337        assert_eq!(
338            err,
339            DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
340        );
341    }
342
343    #[test]
344    fn rejects_duplicate_slots() {
345        for second_key in ["app.orders.v1", "app.users.v1"] {
346            let err = DeclarationSnapshot::new(vec![
347                declaration("app.users.v1", 100),
348                declaration(second_key, 100),
349            ])
350            .expect_err("duplicate slot precedes duplicate key");
351
352            assert_eq!(
353                err,
354                DeclarationSnapshotError::DuplicateSlot(MemoryManagerSlot::new(100).unwrap())
355            );
356        }
357    }
358}