Skip to main content

ic_memory/
registry.rs

1use crate::{
2    constants::DIAGNOSTIC_STRING_MAX_BYTES,
3    declaration::{AllocationDeclaration, DeclarationSnapshot},
4    schema::SchemaMetadata,
5    slot::{
6        IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
7        IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_LEDGER_ID,
8        MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
9        MemoryManagerRangeAuthorityError, MemoryManagerRangeMode, is_ic_memory_stable_key,
10    },
11};
12use serde::{Deserialize, Serialize};
13use std::{
14    collections::BTreeMap,
15    panic::{AssertUnwindSafe, catch_unwind},
16    sync::{Arc, Mutex, MutexGuard},
17    thread::ThreadId,
18};
19
20#[cfg(test)]
21pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
22
23///
24/// StaticMemoryDeclaration
25///
26/// One allocation declaration registered by crate-level generated or macro
27/// code before the linked declaration registry seals its snapshot.
28///
29/// The `authority` field is policy metadata for integration layers such as
30/// Canic or IcyDB. Each `MemoryRuntime` uses it to match declarations against
31/// registered range claims before it calls the caller's
32/// [`crate::AllocationPolicy`].
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct StaticMemoryDeclaration {
35    authority: String,
36    declaration: AllocationDeclaration,
37}
38
39impl StaticMemoryDeclaration {
40    /// Build one static declaration from raw parts.
41    pub fn new(
42        authority: impl Into<String>,
43        declaration: AllocationDeclaration,
44    ) -> Result<Self, StaticMemoryDeclarationError> {
45        let authority = authority.into();
46        validate_external_authority(&authority)?;
47        declaration.validate()?;
48        if is_ic_memory_stable_key(declaration.stable_key().as_str()) {
49            return Err(StaticMemoryDeclarationError::ReservedStableKey {
50                stable_key: declaration.stable_key().as_str().to_string(),
51            });
52        }
53        Ok(Self {
54            authority,
55            declaration,
56        })
57    }
58
59    /// Return the authority that registered this declaration.
60    #[must_use]
61    pub fn authority(&self) -> &str {
62        &self.authority
63    }
64
65    /// Borrow the allocation declaration.
66    #[must_use]
67    pub const fn declaration(&self) -> &AllocationDeclaration {
68        &self.declaration
69    }
70
71    /// Consume this registration and return the allocation declaration.
72    #[must_use]
73    pub fn into_declaration(self) -> AllocationDeclaration {
74        self.declaration
75    }
76}
77
78///
79/// StaticMemoryRangeDeclaration
80///
81/// One `MemoryManager` authority range registered by crate-level generated or
82/// macro code before the linked registry seals the declaration snapshot. In a
83/// `MemoryRuntime`, registered user ranges are authoritative generic range policy:
84/// declarations must stay inside the authority's claimed range before
85/// caller-supplied policy runs.
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct StaticMemoryRangeDeclaration {
88    record: MemoryManagerAuthorityRecord,
89}
90
91impl StaticMemoryRangeDeclaration {
92    /// Build one static range declaration from a validated authority record.
93    pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
94        validate_external_authority(record.authority())?;
95        record.validate()?;
96        Ok(Self { record })
97    }
98
99    /// Return the authority that registered this range.
100    #[must_use]
101    pub fn authority(&self) -> &str {
102        self.record.authority()
103    }
104
105    /// Borrow the authority record.
106    #[must_use]
107    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
108        &self.record
109    }
110
111    /// Consume this registration and return the authority record.
112    #[must_use]
113    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
114        self.record
115    }
116}
117
118///
119/// StaticMemoryDeclarationError
120///
121/// Failure to register or collect static allocation declarations.
122#[non_exhaustive]
123#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
124pub enum StaticMemoryDeclarationError {
125    /// Static declaration registry lock was poisoned.
126    #[error("static memory declaration registry lock poisoned")]
127    RegistryPoisoned,
128    /// Bootstrap already sealed the declaration snapshot.
129    #[error("static memory declaration registry is already sealed")]
130    RegistrySealed,
131    /// Snapshot sealing was called recursively from an eager hook.
132    #[error("static memory declaration snapshot sealing is already active on this thread")]
133    ReentrantSealing,
134    /// Internal declaration-registry lifecycle state was inconsistent.
135    #[error("static memory declaration registry lifecycle is internally inconsistent")]
136    InconsistentLifecycle,
137    /// A deferred eager initialization hook panicked while declarations were sealing.
138    #[error("static memory declaration eager-init hook panicked")]
139    EagerInitPanicked,
140    /// Declaration validation failed.
141    #[error(transparent)]
142    Declaration(#[from] crate::DeclarationSnapshotError),
143    /// Range authority validation failed.
144    #[error(transparent)]
145    Range(#[from] MemoryManagerRangeAuthorityError),
146    /// Canonical sealed-snapshot diagnostic fingerprint encoding failed.
147    #[error("failed to encode canonical sealed declaration fingerprint material: {message}")]
148    SnapshotFingerprintEncoding {
149        /// Encoder failure.
150        message: String,
151    },
152    /// External registration attempted to use an invalid authority identifier.
153    #[error("authority {reason}")]
154    InvalidAuthority {
155        /// Validation failure.
156        reason: &'static str,
157    },
158    /// External registration attempted to impersonate the internal authority.
159    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
160    ReservedAuthority {
161        /// Reserved authority identifier.
162        authority: String,
163    },
164    /// External registration attempted to claim the internal stable-key namespace.
165    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
166    ReservedStableKey {
167        /// Reserved stable key.
168        stable_key: String,
169    },
170}
171
172///
173/// SealedDeclarationSnapshot
174///
175/// Immutable, canonical linked-program allocation declarations and range
176/// authority supplied to each concrete [`crate::MemoryRuntime`].
177///
178/// Sealing runs generated registration hooks and eager declaration hooks
179/// exactly once. Clones share the same immutable snapshot. This value contains
180/// declaration authority only; it contains no memory handles, recovery state,
181/// bootstrap lifecycle, or committed allocation capability.
182///
183
184#[derive(Clone, Debug, Eq, PartialEq)]
185pub struct SealedDeclarationSnapshot {
186    inner: Arc<SealedDeclarationSnapshotInner>,
187}
188
189///
190/// SealedDeclarationFingerprint
191///
192/// Deterministic non-cryptographic fingerprint of one canonical sealed
193/// declaration snapshot.
194///
195/// The fingerprint covers canonical allocation declarations, their linked-code
196/// authorities, and the effective range-authority table. It is diagnostic
197/// metadata for comparing in-memory bootstrap bindings, not persisted
198/// allocation authority or an adversarial integrity proof.
199///
200
201#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
202#[serde(deny_unknown_fields)]
203pub struct SealedDeclarationFingerprint {
204    algorithm_version: u8,
205    value: u64,
206}
207
208impl SealedDeclarationFingerprint {
209    /// Return the diagnostic fingerprint algorithm version.
210    #[must_use]
211    pub const fn algorithm_version(&self) -> u8 {
212        self.algorithm_version
213    }
214
215    /// Return the non-cryptographic fingerprint value.
216    #[must_use]
217    pub const fn value(&self) -> u64 {
218        self.value
219    }
220}
221
222#[derive(Debug, Eq, PartialEq)]
223struct SealedDeclarationSnapshotInner {
224    allocation_snapshot: DeclarationSnapshot,
225    registered_declarations: Vec<StaticMemoryDeclaration>,
226    registered_ranges: Vec<StaticMemoryRangeDeclaration>,
227    range_authority: MemoryManagerRangeAuthority,
228    declaration_authority: BTreeMap<String, RuntimeDeclarationAuthority>,
229    fingerprint: SealedDeclarationFingerprint,
230}
231
232#[derive(Clone, Debug, Eq, PartialEq)]
233pub enum RuntimeDeclarationAuthority {
234    Internal,
235    External(String),
236}
237
238impl SealedDeclarationSnapshot {
239    /// Borrow the canonical allocation snapshot, including runtime governance.
240    #[must_use]
241    pub fn allocation_snapshot(&self) -> &DeclarationSnapshot {
242        &self.inner.allocation_snapshot
243    }
244
245    /// Borrow canonical external declarations registered by linked code.
246    #[must_use]
247    pub fn registered_declarations(&self) -> &[StaticMemoryDeclaration] {
248        &self.inner.registered_declarations
249    }
250
251    /// Borrow canonical external range declarations registered by linked code.
252    #[must_use]
253    pub fn registered_ranges(&self) -> &[StaticMemoryRangeDeclaration] {
254        &self.inner.registered_ranges
255    }
256
257    /// Borrow the effective range authority, including runtime governance.
258    #[must_use]
259    pub fn range_authority(&self) -> &MemoryManagerRangeAuthority {
260        &self.inner.range_authority
261    }
262
263    /// Return the deterministic fingerprint of this sealed declaration meaning.
264    #[must_use]
265    pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
266        self.inner.fingerprint
267    }
268
269    pub(crate) fn declaration_authority(&self) -> &BTreeMap<String, RuntimeDeclarationAuthority> {
270        &self.inner.declaration_authority
271    }
272
273    pub(crate) fn user_ranges_registered(&self) -> bool {
274        !self.inner.registered_ranges.is_empty()
275    }
276
277    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
278        Arc::ptr_eq(&self.inner, &other.inner)
279    }
280}
281
282type StaticRegistrationHook = fn() -> Result<(), StaticMemoryDeclarationError>;
283
284#[derive(Debug)]
285struct StaticMemoryDeclarationRegistry {
286    declarations: Vec<StaticMemoryDeclaration>,
287    ranges: Vec<StaticMemoryRangeDeclaration>,
288    registration_hooks: Vec<StaticRegistrationHook>,
289    eager_init_hooks: Vec<fn()>,
290    lifecycle: StaticRegistryLifecycle,
291}
292
293#[derive(Debug)]
294enum StaticRegistryLifecycle {
295    Open,
296    Sealing {
297        owner: ThreadId,
298        deferred_error: Option<StaticMemoryDeclarationError>,
299    },
300    Sealed(SealedDeclarationSnapshot),
301    Failed(StaticMemoryDeclarationError),
302}
303
304static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
305    Mutex::new(StaticMemoryDeclarationRegistry {
306        declarations: Vec::new(),
307        ranges: Vec::new(),
308        registration_hooks: Vec::new(),
309        eager_init_hooks: Vec::new(),
310        lifecycle: StaticRegistryLifecycle::Open,
311    });
312
313static STATIC_MEMORY_SEAL: Mutex<()> = Mutex::new(());
314
315fn lock_registry()
316-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
317    STATIC_MEMORY_DECLARATIONS
318        .lock()
319        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
320}
321
322fn ensure_registration_open(
323    registry: &StaticMemoryDeclarationRegistry,
324) -> Result<(), StaticMemoryDeclarationError> {
325    match &registry.lifecycle {
326        StaticRegistryLifecycle::Open => Ok(()),
327        StaticRegistryLifecycle::Sealing { owner, .. } if *owner == std::thread::current().id() => {
328            Ok(())
329        }
330        StaticRegistryLifecycle::Sealing { .. }
331        | StaticRegistryLifecycle::Sealed(_)
332        | StaticRegistryLifecycle::Failed(_) => Err(StaticMemoryDeclarationError::RegistrySealed),
333    }
334}
335
336fn with_unsealed_registry(
337    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
338) -> Result<(), StaticMemoryDeclarationError> {
339    let mut registry = lock_registry()?;
340    ensure_registration_open(&registry)?;
341    op(&mut registry);
342    Ok(())
343}
344
345/// Queue a generated registration hook for the fallible sealing phase.
346///
347/// Static constructors cannot return an error. A late deferral is therefore
348/// retained in registry state and returned by snapshot sealing.
349#[doc(hidden)]
350pub fn defer_static_memory_registration(hook: StaticRegistrationHook) {
351    defer_constructor_registration(|registry| {
352        registry.registration_hooks.push(hook);
353    });
354}
355
356/// Queue a declaration-only hook to run immediately before snapshot sealing.
357///
358/// Static constructors cannot return an error. A late deferral is therefore
359/// retained in registry state and returned by snapshot sealing.
360#[doc(hidden)]
361pub fn defer_eager_init(hook: fn()) {
362    defer_constructor_registration(|registry| {
363        registry.eager_init_hooks.push(hook);
364    });
365}
366
367fn defer_constructor_registration(op: impl FnOnce(&mut StaticMemoryDeclarationRegistry)) {
368    let Ok(mut registry) = STATIC_MEMORY_DECLARATIONS.lock() else {
369        // Mutex poisoning is itself durable evidence of the registration
370        // failure and is reported by the next snapshot request.
371        return;
372    };
373    if matches!(registry.lifecycle, StaticRegistryLifecycle::Open) {
374        op(&mut registry);
375        return;
376    }
377    match &mut registry.lifecycle {
378        StaticRegistryLifecycle::Sealing { deferred_error, .. } => {
379            if deferred_error.is_none() {
380                *deferred_error = Some(StaticMemoryDeclarationError::RegistrySealed);
381            }
382        }
383        StaticRegistryLifecycle::Sealed(_) => {
384            registry.lifecycle =
385                StaticRegistryLifecycle::Failed(StaticMemoryDeclarationError::RegistrySealed);
386        }
387        StaticRegistryLifecycle::Failed(_) | StaticRegistryLifecycle::Open => {}
388    }
389}
390
391/// Register one allocation declaration before bootstrap seals the snapshot.
392pub fn register_static_memory_declaration(
393    authority: impl Into<String>,
394    declaration: AllocationDeclaration,
395) -> Result<(), StaticMemoryDeclarationError> {
396    let registration = StaticMemoryDeclaration::new(authority, declaration)?;
397    with_unsealed_registry(|registry| {
398        registry.declarations.push(registration);
399    })
400}
401
402/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
403pub fn register_static_memory_manager_range(
404    start: u8,
405    end: u8,
406    authority: impl Into<String>,
407    mode: MemoryManagerRangeMode,
408    purpose: Option<String>,
409) -> Result<(), StaticMemoryDeclarationError> {
410    let authority = authority.into();
411    let record = MemoryManagerAuthorityRecord::new(
412        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
413        authority,
414        mode,
415        purpose,
416    )?;
417    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
418}
419
420/// Register one authority range declaration before bootstrap seals the snapshot.
421pub fn register_static_memory_range_declaration(
422    declaration: StaticMemoryRangeDeclaration,
423) -> Result<(), StaticMemoryDeclarationError> {
424    validate_external_authority(declaration.authority())?;
425    with_unsealed_registry(|registry| {
426        registry.ranges.push(declaration);
427    })
428}
429
430fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
431    if value == IC_MEMORY_AUTHORITY_OWNER {
432        return Err(StaticMemoryDeclarationError::ReservedAuthority {
433            authority: value.to_string(),
434        });
435    }
436    if value.is_empty() {
437        return Err(StaticMemoryDeclarationError::InvalidAuthority {
438            reason: "must not be empty",
439        });
440    }
441    if value.len() > DIAGNOSTIC_STRING_MAX_BYTES {
442        return Err(StaticMemoryDeclarationError::InvalidAuthority {
443            reason: "must be at most 256 bytes",
444        });
445    }
446    if !value.is_ascii() {
447        return Err(StaticMemoryDeclarationError::InvalidAuthority {
448            reason: "must be ASCII",
449        });
450    }
451    if value.bytes().any(|byte| byte.is_ascii_control()) {
452        return Err(StaticMemoryDeclarationError::InvalidAuthority {
453            reason: "must not contain ASCII control characters",
454        });
455    }
456    Ok(())
457}
458
459/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
460pub fn register_static_memory_manager_declaration(
461    id: u8,
462    authority: impl Into<String>,
463    label: impl Into<String>,
464    stable_key: impl AsRef<str>,
465) -> Result<(), StaticMemoryDeclarationError> {
466    register_static_memory_manager_declaration_with_schema(
467        id,
468        authority,
469        label,
470        stable_key,
471        SchemaMetadata::default(),
472    )
473}
474
475/// Register one `MemoryManager` declaration with schema metadata.
476pub fn register_static_memory_manager_declaration_with_schema(
477    id: u8,
478    authority: impl Into<String>,
479    label: impl Into<String>,
480    stable_key: impl AsRef<str>,
481    schema: SchemaMetadata,
482) -> Result<(), StaticMemoryDeclarationError> {
483    let declaration =
484        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
485    register_static_memory_declaration(authority, declaration)
486}
487
488/// Seal and return the canonical linked-program declaration snapshot.
489///
490/// The first caller runs deferred generated registrations and eager hooks,
491/// canonicalizes declarations and ranges, validates duplicates and range
492/// authority, and publishes one immutable snapshot. Concurrent and subsequent
493/// callers receive clones backed by that same snapshot.
494pub fn sealed_declaration_snapshot()
495-> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
496    {
497        let registry = lock_registry()?;
498        match &registry.lifecycle {
499            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
500            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
501            StaticRegistryLifecycle::Sealing { owner, .. }
502                if *owner == std::thread::current().id() =>
503            {
504                return Err(StaticMemoryDeclarationError::ReentrantSealing);
505            }
506            StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealing { .. } => {}
507        }
508    }
509
510    let _seal = STATIC_MEMORY_SEAL
511        .lock()
512        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
513    let (registration_hooks, eager_init_hooks) = {
514        let mut registry = lock_registry()?;
515        match &registry.lifecycle {
516            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
517            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
518            StaticRegistryLifecycle::Sealing { .. } => {
519                return Err(StaticMemoryDeclarationError::ReentrantSealing);
520            }
521            StaticRegistryLifecycle::Open => {}
522        }
523        registry.lifecycle = StaticRegistryLifecycle::Sealing {
524            owner: std::thread::current().id(),
525            deferred_error: None,
526        };
527        (
528            std::mem::take(&mut registry.registration_hooks),
529            std::mem::take(&mut registry.eager_init_hooks),
530        )
531    };
532
533    for hook in registration_hooks {
534        let result = catch_unwind(AssertUnwindSafe(hook))
535            .map_err(|_| StaticMemoryDeclarationError::EagerInitPanicked)
536            .and_then(std::convert::identity);
537        if let Err(err) = result {
538            return fail_sealing(err);
539        }
540    }
541    for hook in eager_init_hooks {
542        if catch_unwind(AssertUnwindSafe(hook)).is_err() {
543            return fail_sealing(StaticMemoryDeclarationError::EagerInitPanicked);
544        }
545    }
546
547    let mut registry = lock_registry()?;
548    let deferred_error = match &registry.lifecycle {
549        StaticRegistryLifecycle::Sealing { deferred_error, .. } => deferred_error.clone(),
550        StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
551        StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealed(_) => {
552            return Err(StaticMemoryDeclarationError::InconsistentLifecycle);
553        }
554    };
555    if let Some(err) = deferred_error {
556        registry.lifecycle = StaticRegistryLifecycle::Failed(err.clone());
557        return Err(err);
558    }
559    let snapshot = match build_sealed_snapshot(&registry.declarations, &registry.ranges) {
560        Ok(snapshot) => snapshot,
561        Err(err) => {
562            registry.lifecycle = StaticRegistryLifecycle::Failed(err.clone());
563            return Err(err);
564        }
565    };
566    registry.lifecycle = StaticRegistryLifecycle::Sealed(snapshot.clone());
567    Ok(snapshot)
568}
569
570fn fail_sealing<T>(err: StaticMemoryDeclarationError) -> Result<T, StaticMemoryDeclarationError> {
571    let mut registry = lock_registry()?;
572    let failure = match &registry.lifecycle {
573        StaticRegistryLifecycle::Sealing {
574            deferred_error: Some(deferred_error),
575            ..
576        } => deferred_error.clone(),
577        StaticRegistryLifecycle::Open
578        | StaticRegistryLifecycle::Sealing {
579            deferred_error: None,
580            ..
581        }
582        | StaticRegistryLifecycle::Sealed(_) => err,
583        StaticRegistryLifecycle::Failed(failure) => failure.clone(),
584    };
585    registry.lifecycle = StaticRegistryLifecycle::Failed(failure.clone());
586    Err(failure)
587}
588
589fn build_sealed_snapshot(
590    declarations: &[StaticMemoryDeclaration],
591    ranges: &[StaticMemoryRangeDeclaration],
592) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
593    let mut registered_declarations = declarations.to_vec();
594    registered_declarations.sort_by(|left, right| {
595        left.declaration()
596            .stable_key()
597            .cmp(right.declaration().stable_key())
598            .then_with(|| left.declaration().slot().cmp(right.declaration().slot()))
599            .then_with(|| left.authority().cmp(right.authority()))
600    });
601
602    let mut registered_ranges = ranges.to_vec();
603    registered_ranges.sort_by(|left, right| {
604        let left = left.record();
605        let right = right.record();
606        left.range()
607            .start()
608            .cmp(&right.range().start())
609            .then_with(|| left.range().end().cmp(&right.range().end()))
610            .then_with(|| left.authority().cmp(right.authority()))
611            .then_with(|| range_mode_order(left.mode()).cmp(&range_mode_order(right.mode())))
612            .then_with(|| left.purpose().cmp(&right.purpose()))
613    });
614
615    let mut allocation_declarations = Vec::with_capacity(registered_declarations.len() + 1);
616    allocation_declarations.push(internal_ledger_declaration()?);
617    allocation_declarations.extend(
618        registered_declarations
619            .iter()
620            .map(|registration| registration.declaration().clone()),
621    );
622    let allocation_snapshot = DeclarationSnapshot::new(allocation_declarations)?;
623
624    let mut authority_records = Vec::with_capacity(registered_ranges.len() + 1);
625    authority_records.push(internal_ledger_range()?);
626    authority_records.extend(
627        registered_ranges
628            .iter()
629            .map(|registration| registration.record().clone()),
630    );
631    let range_authority = MemoryManagerRangeAuthority::from_records(authority_records)?;
632    let fingerprint = sealed_declaration_fingerprint(
633        &allocation_snapshot,
634        &registered_declarations,
635        range_authority.authorities(),
636    )?;
637
638    let mut declaration_authority = BTreeMap::new();
639    declaration_authority.insert(
640        IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
641        RuntimeDeclarationAuthority::Internal,
642    );
643    for registration in &registered_declarations {
644        declaration_authority.insert(
645            registration.declaration().stable_key().as_str().to_string(),
646            RuntimeDeclarationAuthority::External(registration.authority().to_string()),
647        );
648    }
649
650    Ok(SealedDeclarationSnapshot {
651        inner: Arc::new(SealedDeclarationSnapshotInner {
652            allocation_snapshot,
653            registered_declarations,
654            registered_ranges,
655            range_authority,
656            declaration_authority,
657            fingerprint,
658        }),
659    })
660}
661
662#[derive(Serialize)]
663struct FingerprintDeclaration<'a> {
664    authority: &'a str,
665    declaration: &'a AllocationDeclaration,
666}
667
668#[derive(Serialize)]
669struct SealedDeclarationFingerprintMaterial<'a> {
670    format: &'static str,
671    allocation_snapshot: &'a DeclarationSnapshot,
672    registered_declarations: Vec<FingerprintDeclaration<'a>>,
673    effective_ranges: &'a [MemoryManagerAuthorityRecord],
674}
675
676fn sealed_declaration_fingerprint(
677    allocation_snapshot: &DeclarationSnapshot,
678    registered_declarations: &[StaticMemoryDeclaration],
679    effective_ranges: &[MemoryManagerAuthorityRecord],
680) -> Result<SealedDeclarationFingerprint, StaticMemoryDeclarationError> {
681    let material = SealedDeclarationFingerprintMaterial {
682        format: "ic-memory.sealed-declaration-fingerprint.v1",
683        allocation_snapshot,
684        registered_declarations: registered_declarations
685            .iter()
686            .map(|registration| FingerprintDeclaration {
687                authority: registration.authority(),
688                declaration: registration.declaration(),
689            })
690            .collect(),
691        effective_ranges,
692    };
693    let mut bytes = Vec::new();
694    ciborium::into_writer(&material, &mut bytes).map_err(|err| {
695        StaticMemoryDeclarationError::SnapshotFingerprintEncoding {
696            message: err.to_string(),
697        }
698    })?;
699
700    let value = bytes
701        .into_iter()
702        .fold(FINGERPRINT_FNV_OFFSET, fingerprint_hash_byte);
703    Ok(SealedDeclarationFingerprint {
704        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
705        value,
706    })
707}
708
709const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
710const FINGERPRINT_FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
711const FINGERPRINT_FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
712
713const fn fingerprint_hash_byte(hash: u64, byte: u8) -> u64 {
714    (hash ^ byte as u64).wrapping_mul(FINGERPRINT_FNV_PRIME)
715}
716
717const fn range_mode_order(mode: MemoryManagerRangeMode) -> u8 {
718    match mode {
719        MemoryManagerRangeMode::Reserved => 0,
720        MemoryManagerRangeMode::Allowed => 1,
721    }
722}
723
724fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
725    AllocationDeclaration::memory_manager(
726        IC_MEMORY_LEDGER_STABLE_KEY,
727        MEMORY_MANAGER_LEDGER_ID,
728        IC_MEMORY_LEDGER_LABEL,
729    )
730}
731
732fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
733{
734    MemoryManagerAuthorityRecord::new(
735        MemoryManagerIdRange::new(MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_GOVERNANCE_MAX_ID)?,
736        IC_MEMORY_AUTHORITY_OWNER,
737        MemoryManagerRangeMode::Reserved,
738        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
739    )
740}
741
742#[cfg(test)]
743pub fn reset_static_memory_declarations_for_tests() {
744    let mut registry = STATIC_MEMORY_DECLARATIONS
745        .lock()
746        .expect("static memory declaration registry poisoned");
747    registry.declarations.clear();
748    registry.ranges.clear();
749    registry.registration_hooks.clear();
750    registry.eager_init_hooks.clear();
751    registry.lifecycle = StaticRegistryLifecycle::Open;
752}
753
754#[cfg(test)]
755mod tests;