Skip to main content

ic_memory/
registry.rs

1use crate::{
2    constants::DIAGNOSTIC_STRING_MAX_BYTES,
3    declaration::{AllocationDeclaration, DeclarationCollector, DeclarationSnapshot},
4    schema::SchemaMetadata,
5    slot::{
6        IC_MEMORY_AUTHORITY_OWNER, MemoryManagerAuthorityRecord, MemoryManagerIdRange,
7        MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError, MemoryManagerRangeMode,
8        is_ic_memory_stable_key,
9    },
10};
11use std::sync::{Mutex, MutexGuard};
12
13#[cfg(test)]
14pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
15
16///
17/// StaticMemoryDeclaration
18///
19/// One allocation declaration registered by crate-level generated or macro
20/// code before bootstrap seals the declaration snapshot.
21///
22/// The `authority` field is policy metadata for integration layers such as
23/// Canic or IcyDB. The default runtime uses it to match declarations against
24/// registered range claims before it calls the caller's
25/// [`crate::AllocationPolicy`].
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct StaticMemoryDeclaration {
28    authority: String,
29    declaration: AllocationDeclaration,
30}
31
32impl StaticMemoryDeclaration {
33    /// Build one static declaration from raw parts.
34    pub fn new(
35        authority: impl Into<String>,
36        declaration: AllocationDeclaration,
37    ) -> Result<Self, StaticMemoryDeclarationError> {
38        let authority = authority.into();
39        validate_external_authority(&authority)?;
40        declaration.validate()?;
41        if is_ic_memory_stable_key(declaration.stable_key().as_str()) {
42            return Err(StaticMemoryDeclarationError::ReservedStableKey {
43                stable_key: declaration.stable_key().as_str().to_string(),
44            });
45        }
46        Ok(Self {
47            authority,
48            declaration,
49        })
50    }
51
52    /// Return the authority that registered this declaration.
53    #[must_use]
54    pub fn authority(&self) -> &str {
55        &self.authority
56    }
57
58    /// Borrow the allocation declaration.
59    #[must_use]
60    pub const fn declaration(&self) -> &AllocationDeclaration {
61        &self.declaration
62    }
63
64    /// Consume this registration and return the allocation declaration.
65    #[must_use]
66    pub fn into_declaration(self) -> AllocationDeclaration {
67        self.declaration
68    }
69}
70
71///
72/// StaticMemoryRangeDeclaration
73///
74/// One `MemoryManager` authority range registered by crate-level generated or
75/// macro code before bootstrap seals the declaration snapshot. In the default
76/// runtime, registered user ranges are authoritative generic range policy:
77/// declarations must stay inside the authority's claimed range before
78/// caller-supplied policy runs.
79#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct StaticMemoryRangeDeclaration {
81    record: MemoryManagerAuthorityRecord,
82}
83
84impl StaticMemoryRangeDeclaration {
85    /// Build one static range declaration from a validated authority record.
86    pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
87        validate_external_authority(record.authority())?;
88        record.validate()?;
89        Ok(Self { record })
90    }
91
92    /// Return the authority that registered this range.
93    #[must_use]
94    pub fn authority(&self) -> &str {
95        self.record.authority()
96    }
97
98    /// Borrow the authority record.
99    #[must_use]
100    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
101        &self.record
102    }
103
104    /// Consume this registration and return the authority record.
105    #[must_use]
106    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
107        self.record
108    }
109}
110
111///
112/// StaticMemoryDeclarationError
113///
114/// Failure to register or collect static allocation declarations.
115#[non_exhaustive]
116#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
117pub enum StaticMemoryDeclarationError {
118    /// Static declaration registry lock was poisoned.
119    #[error("static memory declaration registry lock poisoned")]
120    RegistryPoisoned,
121    /// Bootstrap already sealed the declaration snapshot.
122    #[error("static memory declaration registry is already sealed")]
123    RegistrySealed,
124    /// Declaration validation failed.
125    #[error(transparent)]
126    Declaration(#[from] crate::DeclarationSnapshotError),
127    /// Range authority validation failed.
128    #[error(transparent)]
129    Range(#[from] MemoryManagerRangeAuthorityError),
130    /// External registration attempted to use an invalid authority identifier.
131    #[error("authority {reason}")]
132    InvalidAuthority {
133        /// Validation failure.
134        reason: &'static str,
135    },
136    /// External registration attempted to impersonate the internal authority.
137    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
138    ReservedAuthority {
139        /// Reserved authority identifier.
140        authority: String,
141    },
142    /// External registration attempted to claim the internal stable-key namespace.
143    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
144    ReservedStableKey {
145        /// Reserved stable key.
146        stable_key: String,
147    },
148}
149
150#[derive(Debug, Default)]
151struct StaticMemoryDeclarationRegistry {
152    declarations: Vec<StaticMemoryDeclaration>,
153    ranges: Vec<StaticMemoryRangeDeclaration>,
154    sealed: bool,
155}
156
157static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
158    Mutex::new(StaticMemoryDeclarationRegistry {
159        declarations: Vec::new(),
160        ranges: Vec::new(),
161        sealed: false,
162    });
163
164fn lock_registry()
165-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
166    STATIC_MEMORY_DECLARATIONS
167        .lock()
168        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
169}
170
171const fn ensure_unsealed(
172    registry: &StaticMemoryDeclarationRegistry,
173) -> Result<(), StaticMemoryDeclarationError> {
174    if registry.sealed {
175        return Err(StaticMemoryDeclarationError::RegistrySealed);
176    }
177    Ok(())
178}
179
180fn with_unsealed_registry(
181    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
182) -> Result<(), StaticMemoryDeclarationError> {
183    let mut registry = lock_registry()?;
184    ensure_unsealed(&registry)?;
185    op(&mut registry);
186    Ok(())
187}
188
189/// Register one allocation declaration before bootstrap seals the snapshot.
190pub fn register_static_memory_declaration(
191    authority: impl Into<String>,
192    declaration: AllocationDeclaration,
193) -> Result<(), StaticMemoryDeclarationError> {
194    let registration = StaticMemoryDeclaration::new(authority, declaration)?;
195    with_unsealed_registry(|registry| {
196        registry.declarations.push(registration);
197    })
198}
199
200/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
201pub fn register_static_memory_manager_range(
202    start: u8,
203    end: u8,
204    authority: impl Into<String>,
205    mode: MemoryManagerRangeMode,
206    purpose: Option<String>,
207) -> Result<(), StaticMemoryDeclarationError> {
208    let authority = authority.into();
209    let record = MemoryManagerAuthorityRecord::new(
210        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
211        authority,
212        mode,
213        purpose,
214    )?;
215    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
216}
217
218/// Register one authority range declaration before bootstrap seals the snapshot.
219pub fn register_static_memory_range_declaration(
220    declaration: StaticMemoryRangeDeclaration,
221) -> Result<(), StaticMemoryDeclarationError> {
222    validate_external_authority(declaration.authority())?;
223    with_unsealed_registry(|registry| {
224        registry.ranges.push(declaration);
225    })
226}
227
228fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
229    if value == IC_MEMORY_AUTHORITY_OWNER {
230        return Err(StaticMemoryDeclarationError::ReservedAuthority {
231            authority: value.to_string(),
232        });
233    }
234    if value.is_empty() {
235        return Err(StaticMemoryDeclarationError::InvalidAuthority {
236            reason: "must not be empty",
237        });
238    }
239    if value.len() > DIAGNOSTIC_STRING_MAX_BYTES {
240        return Err(StaticMemoryDeclarationError::InvalidAuthority {
241            reason: "must be at most 256 bytes",
242        });
243    }
244    if !value.is_ascii() {
245        return Err(StaticMemoryDeclarationError::InvalidAuthority {
246            reason: "must be ASCII",
247        });
248    }
249    if value.bytes().any(|byte| byte.is_ascii_control()) {
250        return Err(StaticMemoryDeclarationError::InvalidAuthority {
251            reason: "must not contain ASCII control characters",
252        });
253    }
254    Ok(())
255}
256
257/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
258pub fn register_static_memory_manager_declaration(
259    id: u8,
260    authority: impl Into<String>,
261    label: impl Into<String>,
262    stable_key: impl AsRef<str>,
263) -> Result<(), StaticMemoryDeclarationError> {
264    register_static_memory_manager_declaration_with_schema(
265        id,
266        authority,
267        label,
268        stable_key,
269        SchemaMetadata::default(),
270    )
271}
272
273/// Register one `MemoryManager` declaration with schema metadata.
274pub fn register_static_memory_manager_declaration_with_schema(
275    id: u8,
276    authority: impl Into<String>,
277    label: impl Into<String>,
278    stable_key: impl AsRef<str>,
279    schema: SchemaMetadata,
280) -> Result<(), StaticMemoryDeclarationError> {
281    let declaration =
282        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
283    register_static_memory_declaration(authority, declaration)
284}
285
286/// Return the currently registered static allocation declarations.
287pub fn static_memory_declarations()
288-> Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError> {
289    Ok(lock_registry()?.declarations.clone())
290}
291
292/// Return the currently registered static range declarations.
293pub fn static_memory_range_declarations()
294-> Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError> {
295    Ok(lock_registry()?.ranges.clone())
296}
297
298/// Return the currently registered static range declarations as an authority table.
299pub fn static_memory_range_authority()
300-> Result<MemoryManagerRangeAuthority, StaticMemoryDeclarationError> {
301    MemoryManagerRangeAuthority::from_records(
302        static_memory_range_declarations()?
303            .into_iter()
304            .map(StaticMemoryRangeDeclaration::into_record)
305            .collect(),
306    )
307    .map_err(StaticMemoryDeclarationError::Range)
308}
309
310/// Seal the static memory registry so later registration attempts fail closed.
311pub fn seal_static_memory_registry() -> Result<(), StaticMemoryDeclarationError> {
312    let mut registry = lock_registry()?;
313    registry.sealed = true;
314    Ok(())
315}
316
317/// Add currently registered static allocation declarations to a collector.
318pub fn collect_static_memory_declarations(
319    collector: &mut DeclarationCollector,
320) -> Result<(), StaticMemoryDeclarationError> {
321    for registration in static_memory_declarations()? {
322        collector.push(registration.into_declaration());
323    }
324    Ok(())
325}
326
327/// Seal currently registered static allocation declarations into a snapshot.
328///
329/// Sealing prevents later static registrations from being accepted. Callers may
330/// still call this function again to rebuild the same snapshot for idempotent
331/// bootstrap paths.
332pub fn static_memory_declaration_snapshot()
333-> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
334    let declarations = {
335        let mut registry = lock_registry()?;
336        registry.sealed = true;
337        registry
338            .declarations
339            .iter()
340            .map(|registration| registration.declaration.clone())
341            .collect()
342    };
343    DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
344}
345
346#[cfg(test)]
347pub fn reset_static_memory_declarations_for_tests() {
348    let mut registry = STATIC_MEMORY_DECLARATIONS
349        .lock()
350        .expect("static memory declaration registry poisoned");
351    registry.declarations.clear();
352    registry.ranges.clear();
353    registry.sealed = false;
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn registers_and_seals_static_memory_declarations() {
362        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
363        reset_static_memory_declarations_for_tests();
364
365        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
366            .expect("register declaration");
367
368        let registrations = static_memory_declarations().expect("registrations");
369        assert_eq!(registrations.len(), 1);
370        assert_eq!(registrations[0].authority(), "icydb");
371        assert_eq!(
372            registrations[0].declaration().stable_key().as_str(),
373            "icydb.users.data.v1"
374        );
375
376        let snapshot = static_memory_declaration_snapshot().expect("snapshot");
377        assert_eq!(snapshot.len(), 1);
378
379        let err =
380            register_static_memory_manager_declaration(101, "icydb", "orders", "icydb.orders.v1")
381                .expect_err("late registration must fail");
382        assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
383    }
384
385    #[test]
386    fn registers_static_memory_ranges() {
387        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
388        reset_static_memory_declarations_for_tests();
389
390        register_static_memory_manager_range(
391            100,
392            109,
393            "crate_a",
394            MemoryManagerRangeMode::Reserved,
395            Some("crate A stores".to_string()),
396        )
397        .expect("register range");
398
399        let ranges = static_memory_range_declarations().expect("ranges");
400        assert_eq!(ranges.len(), 1);
401        assert_eq!(ranges[0].authority(), "crate_a");
402        assert_eq!(ranges[0].record().range().start(), 100);
403        assert_eq!(ranges[0].record().range().end(), 109);
404    }
405
406    #[test]
407    fn static_range_declaration_uses_record_authority() {
408        let record = MemoryManagerAuthorityRecord::new(
409            MemoryManagerIdRange::new(100, 109).expect("range"),
410            "record_authority",
411            MemoryManagerRangeMode::Reserved,
412            None,
413        )
414        .expect("record");
415
416        let range = StaticMemoryRangeDeclaration::new(record).expect("external range");
417
418        assert_eq!(range.authority(), "record_authority");
419    }
420
421    #[test]
422    fn static_declaration_rejects_invalid_decoded_declaration() {
423        let mut declaration = AllocationDeclaration::memory_manager("app.users.v1", 100, "users")
424            .expect("declaration");
425        declaration.slot = crate::AllocationSlotDescriptor::memory_manager_unchecked(
426            crate::MEMORY_MANAGER_INVALID_ID,
427        );
428
429        let err = StaticMemoryDeclaration::new("app", declaration)
430            .expect_err("decoded invalid declaration must fail at the registry boundary");
431
432        assert!(matches!(
433            err,
434            StaticMemoryDeclarationError::Declaration(
435                crate::DeclarationSnapshotError::MemoryManagerSlot(
436                    crate::MemoryManagerSlotError::InvalidMemoryManagerId { id }
437                )
438            ) if id == crate::MEMORY_MANAGER_INVALID_ID
439        ));
440    }
441
442    #[test]
443    fn static_range_declaration_rejects_invalid_decoded_record() {
444        let mut record = MemoryManagerAuthorityRecord::new(
445            MemoryManagerIdRange::new(100, 109).expect("range"),
446            "app",
447            MemoryManagerRangeMode::Reserved,
448            None,
449        )
450        .expect("record");
451        record.range = MemoryManagerIdRange {
452            start: 109,
453            end: 100,
454        };
455
456        let err = StaticMemoryRangeDeclaration::new(record)
457            .expect_err("decoded invalid range record must fail at the registry boundary");
458
459        assert!(matches!(
460            err,
461            StaticMemoryDeclarationError::Range(MemoryManagerRangeAuthorityError::Range(_))
462        ));
463    }
464
465    #[test]
466    fn snapshot_rejects_duplicate_static_memory_declarations() {
467        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
468        reset_static_memory_declarations_for_tests();
469
470        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
471            .expect("register first declaration");
472        register_static_memory_manager_declaration(100, "icydb", "orders", "icydb.orders.v1")
473            .expect("register duplicate slot declaration");
474
475        let err = static_memory_declaration_snapshot().expect_err("duplicate slot must fail");
476        assert!(matches!(
477            err,
478            StaticMemoryDeclarationError::Declaration(
479                crate::DeclarationSnapshotError::DuplicateSlot(_)
480            )
481        ));
482    }
483
484    #[test]
485    fn external_registration_rejects_internal_stable_key_namespace() {
486        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
487        reset_static_memory_declarations_for_tests();
488
489        let err = register_static_memory_manager_declaration(
490            1,
491            "external",
492            "governance",
493            "ic_memory.spoof.v1",
494        )
495        .expect_err("internal stable key must be unavailable externally");
496
497        assert!(matches!(
498            err,
499            StaticMemoryDeclarationError::ReservedStableKey { stable_key }
500                if stable_key == "ic_memory.spoof.v1"
501        ));
502    }
503
504    #[test]
505    fn external_registration_rejects_internal_authority_identity() {
506        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
507        reset_static_memory_declarations_for_tests();
508
509        let declaration_err = register_static_memory_manager_declaration(
510            100,
511            IC_MEMORY_AUTHORITY_OWNER,
512            "users",
513            "app.users.v1",
514        )
515        .expect_err("internal declaration authority must be unavailable externally");
516        let range_err = register_static_memory_manager_range(
517            100,
518            109,
519            IC_MEMORY_AUTHORITY_OWNER,
520            MemoryManagerRangeMode::Reserved,
521            None,
522        )
523        .expect_err("internal range authority must be unavailable externally");
524
525        assert!(matches!(
526            declaration_err,
527            StaticMemoryDeclarationError::ReservedAuthority { .. }
528        ));
529        assert!(matches!(
530            range_err,
531            StaticMemoryDeclarationError::ReservedAuthority { .. }
532        ));
533    }
534}