Skip to main content

ic_memory/
registry.rs

1use crate::{
2    declaration::{AllocationDeclaration, DeclarationCollector, DeclarationSnapshot},
3    schema::SchemaMetadata,
4    slot::{
5        MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
6        MemoryManagerRangeAuthorityError, MemoryManagerRangeMode,
7    },
8};
9use std::sync::Mutex;
10
11#[cfg(test)]
12pub(crate) static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
13
14///
15/// StaticMemoryDeclaration
16///
17/// One allocation declaration registered by crate-level generated or macro
18/// code before bootstrap seals the declaration snapshot.
19///
20/// The `declaring_crate` field is policy metadata for integration layers such
21/// as Canic or IcyDB. `ic-memory` records it for collection, but generic ledger
22/// validation still relies on the caller's [`crate::AllocationPolicy`].
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct StaticMemoryDeclaration {
25    declaring_crate: String,
26    declaration: AllocationDeclaration,
27}
28
29impl StaticMemoryDeclaration {
30    /// Build one static declaration from raw parts.
31    pub fn new(declaring_crate: impl Into<String>, declaration: AllocationDeclaration) -> Self {
32        Self {
33            declaring_crate: declaring_crate.into(),
34            declaration,
35        }
36    }
37
38    /// Return the crate that registered this declaration.
39    #[must_use]
40    pub fn declaring_crate(&self) -> &str {
41        &self.declaring_crate
42    }
43
44    /// Borrow the allocation declaration.
45    #[must_use]
46    pub const fn declaration(&self) -> &AllocationDeclaration {
47        &self.declaration
48    }
49
50    /// Consume this registration and return the allocation declaration.
51    #[must_use]
52    pub fn into_declaration(self) -> AllocationDeclaration {
53        self.declaration
54    }
55}
56
57///
58/// StaticMemoryRangeDeclaration
59///
60/// One `MemoryManager` authority range registered by crate-level generated or
61/// macro code before bootstrap seals the declaration snapshot.
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct StaticMemoryRangeDeclaration {
64    declaring_crate: String,
65    record: MemoryManagerAuthorityRecord,
66}
67
68impl StaticMemoryRangeDeclaration {
69    /// Build one static range declaration.
70    #[must_use]
71    pub fn new(declaring_crate: impl Into<String>, record: MemoryManagerAuthorityRecord) -> Self {
72        Self {
73            declaring_crate: declaring_crate.into(),
74            record,
75        }
76    }
77
78    /// Return the crate that registered this range.
79    #[must_use]
80    pub fn declaring_crate(&self) -> &str {
81        &self.declaring_crate
82    }
83
84    /// Borrow the authority record.
85    #[must_use]
86    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
87        &self.record
88    }
89
90    /// Consume this registration and return the authority record.
91    #[must_use]
92    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
93        self.record
94    }
95}
96
97///
98/// StaticMemoryDeclarationError
99///
100/// Failure to register or collect static allocation declarations.
101#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
102pub enum StaticMemoryDeclarationError {
103    /// Static declaration registry lock was poisoned.
104    #[error("static memory declaration registry lock poisoned")]
105    RegistryPoisoned,
106    /// Bootstrap already sealed the declaration snapshot.
107    #[error("static memory declaration registry is already sealed")]
108    RegistrySealed,
109    /// Declaration validation failed.
110    #[error(transparent)]
111    Declaration(#[from] crate::DeclarationSnapshotError),
112    /// Range authority validation failed.
113    #[error(transparent)]
114    Range(#[from] MemoryManagerRangeAuthorityError),
115}
116
117#[derive(Debug, Default)]
118struct StaticMemoryDeclarationRegistry {
119    declarations: Vec<StaticMemoryDeclaration>,
120    ranges: Vec<StaticMemoryRangeDeclaration>,
121    sealed: bool,
122}
123
124static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
125    Mutex::new(StaticMemoryDeclarationRegistry {
126        declarations: Vec::new(),
127        ranges: Vec::new(),
128        sealed: false,
129    });
130
131/// Register one allocation declaration before bootstrap seals the snapshot.
132pub fn register_static_memory_declaration(
133    declaring_crate: impl Into<String>,
134    declaration: AllocationDeclaration,
135) -> Result<(), StaticMemoryDeclarationError> {
136    let mut registry = STATIC_MEMORY_DECLARATIONS
137        .lock()
138        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
139    if registry.sealed {
140        return Err(StaticMemoryDeclarationError::RegistrySealed);
141    }
142    registry
143        .declarations
144        .push(StaticMemoryDeclaration::new(declaring_crate, declaration));
145    Ok(())
146}
147
148/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
149pub fn register_static_memory_manager_range(
150    start: u8,
151    end: u8,
152    declaring_crate: impl Into<String>,
153    mode: MemoryManagerRangeMode,
154    purpose: Option<String>,
155) -> Result<(), StaticMemoryDeclarationError> {
156    let declaring_crate = declaring_crate.into();
157    let record = MemoryManagerAuthorityRecord::new(
158        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
159        declaring_crate.clone(),
160        mode,
161        purpose,
162    )?;
163    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(
164        declaring_crate,
165        record,
166    ))
167}
168
169/// Register one authority range declaration before bootstrap seals the snapshot.
170pub fn register_static_memory_range_declaration(
171    declaration: StaticMemoryRangeDeclaration,
172) -> Result<(), StaticMemoryDeclarationError> {
173    let mut registry = STATIC_MEMORY_DECLARATIONS
174        .lock()
175        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
176    if registry.sealed {
177        return Err(StaticMemoryDeclarationError::RegistrySealed);
178    }
179    registry.ranges.push(declaration);
180    Ok(())
181}
182
183/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
184pub fn register_static_memory_manager_declaration(
185    id: u8,
186    declaring_crate: impl Into<String>,
187    label: impl Into<String>,
188    stable_key: impl AsRef<str>,
189) -> Result<(), StaticMemoryDeclarationError> {
190    register_static_memory_manager_declaration_with_schema(
191        id,
192        declaring_crate,
193        label,
194        stable_key,
195        SchemaMetadata::default(),
196    )
197}
198
199/// Register one `MemoryManager` declaration with schema metadata.
200pub fn register_static_memory_manager_declaration_with_schema(
201    id: u8,
202    declaring_crate: impl Into<String>,
203    label: impl Into<String>,
204    stable_key: impl AsRef<str>,
205    schema: SchemaMetadata,
206) -> Result<(), StaticMemoryDeclarationError> {
207    let declaration =
208        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
209    register_static_memory_declaration(declaring_crate, declaration)
210}
211
212/// Return the currently registered static allocation declarations.
213pub fn static_memory_declarations()
214-> Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError> {
215    STATIC_MEMORY_DECLARATIONS
216        .lock()
217        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
218        .map(|registry| registry.declarations.clone())
219}
220
221/// Return the currently registered static range declarations.
222pub fn static_memory_range_declarations()
223-> Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError> {
224    STATIC_MEMORY_DECLARATIONS
225        .lock()
226        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
227        .map(|registry| registry.ranges.clone())
228}
229
230/// Return the currently registered static range declarations as an authority table.
231pub fn static_memory_range_authority()
232-> Result<MemoryManagerRangeAuthority, StaticMemoryDeclarationError> {
233    MemoryManagerRangeAuthority::from_records(
234        static_memory_range_declarations()?
235            .into_iter()
236            .map(StaticMemoryRangeDeclaration::into_record)
237            .collect(),
238    )
239    .map_err(StaticMemoryDeclarationError::Range)
240}
241
242/// Seal the static memory registry so later registration attempts fail closed.
243pub(crate) fn seal_static_memory_registry() -> Result<(), StaticMemoryDeclarationError> {
244    let mut registry = STATIC_MEMORY_DECLARATIONS
245        .lock()
246        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
247    registry.sealed = true;
248    Ok(())
249}
250
251/// Add currently registered static allocation declarations to a collector.
252pub fn collect_static_memory_declarations(
253    collector: &mut DeclarationCollector,
254) -> Result<(), StaticMemoryDeclarationError> {
255    for registration in static_memory_declarations()? {
256        collector.push(registration.into_declaration());
257    }
258    Ok(())
259}
260
261/// Seal currently registered static allocation declarations into a snapshot.
262///
263/// Sealing prevents later static registrations from being accepted. Callers may
264/// still call this function again to rebuild the same snapshot for idempotent
265/// bootstrap paths.
266pub fn static_memory_declaration_snapshot()
267-> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
268    let declarations = {
269        let mut registry = STATIC_MEMORY_DECLARATIONS
270            .lock()
271            .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
272        registry.sealed = true;
273        registry
274            .declarations
275            .iter()
276            .map(|registration| registration.declaration.clone())
277            .collect()
278    };
279    DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
280}
281
282#[cfg(test)]
283pub(crate) fn reset_static_memory_declarations_for_tests() {
284    let mut registry = STATIC_MEMORY_DECLARATIONS
285        .lock()
286        .expect("static memory declaration registry poisoned");
287    registry.declarations.clear();
288    registry.ranges.clear();
289    registry.sealed = false;
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn registers_and_seals_static_memory_declarations() {
298        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
299        reset_static_memory_declarations_for_tests();
300
301        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
302            .expect("register declaration");
303
304        let registrations = static_memory_declarations().expect("registrations");
305        assert_eq!(registrations.len(), 1);
306        assert_eq!(registrations[0].declaring_crate(), "icydb");
307        assert_eq!(
308            registrations[0].declaration().stable_key().as_str(),
309            "icydb.users.data.v1"
310        );
311
312        let snapshot = static_memory_declaration_snapshot().expect("snapshot");
313        assert_eq!(snapshot.len(), 1);
314
315        let err =
316            register_static_memory_manager_declaration(101, "icydb", "orders", "icydb.orders.v1")
317                .expect_err("late registration must fail");
318        assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
319    }
320
321    #[test]
322    fn registers_static_memory_ranges() {
323        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
324        reset_static_memory_declarations_for_tests();
325
326        register_static_memory_manager_range(
327            100,
328            109,
329            "crate_a",
330            MemoryManagerRangeMode::Reserved,
331            Some("crate A stores".to_string()),
332        )
333        .expect("register range");
334
335        let ranges = static_memory_range_declarations().expect("ranges");
336        assert_eq!(ranges.len(), 1);
337        assert_eq!(ranges[0].declaring_crate(), "crate_a");
338        assert_eq!(ranges[0].record().range.start(), 100);
339        assert_eq!(ranges[0].record().range.end(), 109);
340    }
341
342    #[test]
343    fn snapshot_rejects_duplicate_static_memory_declarations() {
344        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
345        reset_static_memory_declarations_for_tests();
346
347        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
348            .expect("register first declaration");
349        register_static_memory_manager_declaration(100, "icydb", "orders", "icydb.orders.v1")
350            .expect("register duplicate slot declaration");
351
352        let err = static_memory_declaration_snapshot().expect_err("duplicate slot must fail");
353        assert!(matches!(
354            err,
355            StaticMemoryDeclarationError::Declaration(
356                crate::DeclarationSnapshotError::DuplicateSlot(_)
357            )
358        ));
359    }
360}