Skip to main content

ic_memory/
registry.rs

1use crate::{
2    declaration::{AllocationDeclaration, DeclarationSnapshot},
3    schema::SchemaMetadata,
4    slot::{
5        IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
6        IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
7        MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
8        MemoryManagerRangeMode, is_ic_memory_stable_key, memory_manager_governance_range,
9    },
10    text::validate_diagnostic_text,
11};
12use serde::{Deserialize, Serialize};
13use std::{
14    panic::{AssertUnwindSafe, catch_unwind},
15    sync::{Arc, Mutex, MutexGuard},
16    thread::ThreadId,
17};
18
19#[cfg(test)]
20pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
21
22///
23/// StaticMemoryDeclaration
24///
25/// One allocation declaration registered by crate-level generated or macro
26/// code before the linked declaration registry seals its snapshot.
27///
28/// The `authority` field is policy metadata for integration layers such as
29/// Canic or IcyDB. Each `MemoryRuntime` uses it to match declarations against
30/// registered range claims before it calls the caller's
31/// [`crate::AllocationPolicy`].
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct StaticMemoryDeclaration {
34    authority: String,
35    declaration: AllocationDeclaration,
36}
37
38impl StaticMemoryDeclaration {
39    /// Build one static declaration from raw parts.
40    pub fn new(
41        authority: impl Into<String>,
42        declaration: AllocationDeclaration,
43    ) -> Result<Self, StaticMemoryDeclarationError> {
44        let authority = authority.into();
45        validate_external_authority(&authority)?;
46        declaration.validate()?;
47        if is_ic_memory_stable_key(declaration.stable_key().as_str()) {
48            return Err(StaticMemoryDeclarationError::ReservedStableKey {
49                stable_key: declaration.stable_key().as_str().to_string(),
50            });
51        }
52        Ok(Self {
53            authority,
54            declaration,
55        })
56    }
57
58    /// Return the authority that registered this declaration.
59    #[must_use]
60    pub fn authority(&self) -> &str {
61        &self.authority
62    }
63
64    /// Borrow the allocation declaration.
65    #[must_use]
66    pub const fn declaration(&self) -> &AllocationDeclaration {
67        &self.declaration
68    }
69
70    /// Consume this registration and return the allocation declaration.
71    #[must_use]
72    pub fn into_declaration(self) -> AllocationDeclaration {
73        self.declaration
74    }
75}
76
77///
78/// MemoryRequest
79///
80/// Key-only request resolved after ledger recovery. New keys require an explicit
81/// Allowed range owned by this authority; known keys retain their durable slot.
82///
83
84#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
85pub struct MemoryRequest {
86    authority: String,
87    stable_key: crate::StableKey,
88    schema: SchemaMetadata,
89}
90
91impl MemoryRequest {
92    /// Build a checked logical request before sealing.
93    pub fn new(
94        authority: impl Into<String>,
95        stable_key: &str,
96        schema: SchemaMetadata,
97    ) -> Result<Self, StaticMemoryDeclarationError> {
98        let authority = authority.into();
99        validate_external_authority(&authority)?;
100        let stable_key =
101            crate::StableKey::parse(stable_key).map_err(crate::DeclarationSnapshotError::Key)?;
102        schema
103            .validate()
104            .map_err(crate::DeclarationSnapshotError::SchemaMetadata)?;
105        if is_ic_memory_stable_key(stable_key.as_str()) {
106            return Err(StaticMemoryDeclarationError::ReservedStableKey {
107                stable_key: stable_key.as_str().to_string(),
108            });
109        }
110        Ok(Self {
111            authority,
112            stable_key,
113            schema,
114        })
115    }
116
117    /// Attach schema metadata from the immutable, integrity-checked recovered ledger.
118    pub(crate) const fn with_schema(mut self, schema: SchemaMetadata) -> Self {
119        self.schema = schema;
120        self
121    }
122
123    /// Borrow the requested durable key.
124    #[must_use]
125    pub const fn stable_key(&self) -> &crate::StableKey {
126        &self.stable_key
127    }
128
129    /// Borrow the requested diagnostic schema metadata.
130    #[must_use]
131    pub const fn schema(&self) -> &SchemaMetadata {
132        &self.schema
133    }
134
135    /// Borrow the declaring authority.
136    #[must_use]
137    pub fn authority(&self) -> &str {
138        &self.authority
139    }
140}
141
142/// Register a key-only request before the linked snapshot seals.
143pub fn register_memory_request(request: MemoryRequest) -> Result<(), StaticMemoryDeclarationError> {
144    with_unsealed_registry(|registry| registry.requests.push(request))
145}
146
147///
148/// StaticMemoryRangeDeclaration
149///
150/// One `MemoryManager` authority range registered by crate-level generated or
151/// macro code before the linked registry seals the declaration snapshot. In a
152/// `MemoryRuntime`, registered user ranges are authoritative generic range policy:
153/// declarations must stay inside the authority's claimed range before
154/// caller-supplied policy runs.
155#[derive(Clone, Debug, Eq, PartialEq)]
156pub struct StaticMemoryRangeDeclaration {
157    record: MemoryManagerAuthorityRecord,
158}
159
160impl StaticMemoryRangeDeclaration {
161    /// Build one static range declaration from a validated authority record.
162    pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
163        validate_external_authority(record.authority())?;
164        record.validate()?;
165        Ok(Self { record })
166    }
167
168    /// Return the authority that registered this range.
169    #[must_use]
170    pub fn authority(&self) -> &str {
171        self.record.authority()
172    }
173
174    /// Borrow the authority record.
175    #[must_use]
176    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
177        &self.record
178    }
179
180    /// Consume this registration and return the authority record.
181    #[must_use]
182    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
183        self.record
184    }
185}
186
187///
188/// StaticMemoryDeclarationError
189///
190/// Failure to register or collect static allocation declarations.
191#[non_exhaustive]
192#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
193pub enum StaticMemoryDeclarationError {
194    #[error("at most 254 external declarations and ranges are supported")]
195    TooManyDeclarations,
196    #[error("duplicate requested stable key {stable_key}")]
197    DuplicateRequest { stable_key: crate::StableKey },
198    /// Static declaration registry lock was poisoned.
199    #[error("static memory declaration registry lock poisoned")]
200    RegistryPoisoned,
201    /// Bootstrap already sealed the declaration snapshot.
202    #[error("static memory declaration registry is already sealed")]
203    RegistrySealed,
204    /// Snapshot sealing was called recursively from an eager hook.
205    #[error("static memory declaration snapshot sealing is already active on this thread")]
206    ReentrantSealing,
207    /// A deferred eager initialization hook panicked while declarations were sealing.
208    #[error("static memory declaration eager-init hook panicked")]
209    EagerInitPanicked,
210    /// Declaration validation failed.
211    #[error(transparent)]
212    Declaration(#[from] crate::DeclarationSnapshotError),
213    /// Range authority validation failed.
214    #[error(transparent)]
215    Range(#[from] MemoryManagerRangeAuthorityError),
216    /// External registration attempted to use an invalid authority identifier.
217    #[error("authority {reason}")]
218    InvalidAuthority {
219        /// Validation failure.
220        reason: &'static str,
221    },
222    /// External registration attempted to impersonate the internal authority.
223    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
224    ReservedAuthority {
225        /// Reserved authority identifier.
226        authority: String,
227    },
228    /// External registration attempted to claim the internal stable-key namespace.
229    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
230    ReservedStableKey {
231        /// Reserved stable key.
232        stable_key: String,
233    },
234}
235
236///
237/// SealedDeclarationSnapshot
238///
239/// Immutable, canonical linked-program allocation declarations and range
240/// authority supplied to each concrete [`crate::MemoryRuntime`].
241///
242/// Sealing runs generated registration hooks and eager declaration hooks
243/// exactly once. Clones share the same immutable snapshot. This value contains
244/// declaration authority only; it contains no memory handles, recovery state,
245/// bootstrap lifecycle, or committed allocation capability.
246///
247
248#[derive(Clone, Debug, Eq, PartialEq)]
249pub struct SealedDeclarationSnapshot {
250    inner: Arc<SealedDeclarationSnapshotInner>,
251}
252
253///
254/// SealedDeclarationFingerprint
255///
256/// Deterministic non-cryptographic fingerprint of one canonical sealed
257/// declaration snapshot.
258///
259/// The fingerprint covers canonical allocation declarations, their linked-code
260/// authorities, and the effective range-authority table. It is diagnostic
261/// metadata for comparing in-memory bootstrap bindings, not persisted
262/// allocation authority or an adversarial integrity proof.
263///
264
265#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
266#[serde(deny_unknown_fields)]
267pub struct SealedDeclarationFingerprint {
268    algorithm_version: u8,
269    value: u64,
270}
271
272impl SealedDeclarationFingerprint {
273    /// Return the diagnostic fingerprint algorithm version.
274    #[must_use]
275    pub const fn algorithm_version(&self) -> u8 {
276        self.algorithm_version
277    }
278
279    /// Return the non-cryptographic fingerprint value.
280    #[must_use]
281    pub const fn value(&self) -> u64 {
282        self.value
283    }
284}
285
286#[derive(Debug, Eq, PartialEq)]
287struct SealedDeclarationSnapshotInner {
288    allocation_snapshot: DeclarationSnapshot,
289    requests: Vec<MemoryRequest>,
290    registered_declarations: Vec<StaticMemoryDeclaration>,
291    registered_ranges: Vec<StaticMemoryRangeDeclaration>,
292    range_authority: MemoryManagerRangeAuthority,
293    fingerprint: SealedDeclarationFingerprint,
294}
295
296impl SealedDeclarationSnapshot {
297    /// Seal explicitly owned inputs with the same rules as the linked registry.
298    pub fn new(
299        declarations: &[StaticMemoryDeclaration],
300        ranges: &[StaticMemoryRangeDeclaration],
301        requests: &[MemoryRequest],
302    ) -> Result<Self, StaticMemoryDeclarationError> {
303        build_snapshot(declarations, ranges, requests)
304    }
305
306    /// Borrow canonical unresolved key-only requests.
307    #[must_use]
308    pub fn requests(&self) -> &[MemoryRequest] {
309        &self.inner.requests
310    }
311
312    pub(crate) fn resolve(
313        &self,
314        ledger: &crate::AllocationLedger,
315        historical: Vec<MemoryRequest>,
316    ) -> Result<Self, crate::MemoryResolutionError> {
317        if self.requests().is_empty() && historical.is_empty() {
318            return Ok(self.clone());
319        }
320        if self.registered_declarations().len() + self.requests().len() + historical.len() > 254 {
321            return Err(StaticMemoryDeclarationError::TooManyDeclarations.into());
322        }
323        let mut declarations = self.registered_declarations().to_vec();
324        let mut occupied = [false; 255];
325        for record in ledger.allocation_history().records() {
326            occupied[usize::from(
327                record
328                    .slot()
329                    .memory_manager_id()
330                    .expect("validated ledger slot"),
331            )] = true;
332        }
333        for fixed in &declarations {
334            occupied[usize::from(
335                fixed
336                    .declaration()
337                    .slot()
338                    .memory_manager_id()
339                    .expect("checked slot"),
340            )] = true;
341        }
342        // Only the original requests can allocate new slots and they are already
343        // canonical. Admission selections are known-only: all their slots are
344        // occupied above regardless of selection order. Final declarations are
345        // canonicalized and checked together below.
346        for request in self.requests().iter().chain(&historical) {
347            let historical = ledger
348                .allocation_history()
349                .records()
350                .iter()
351                .find(|record| record.stable_key() == &request.stable_key);
352            let id = if let Some(record) = historical {
353                record
354                    .slot()
355                    .memory_manager_id()
356                    .expect("validated ledger slot")
357            } else {
358                (0..255_u8)
359                    .find(|id| {
360                        !occupied[usize::from(*id)]
361                            && self.range_authority().authorities().iter().any(|range| {
362                                range.authority() == request.authority
363                                    && range.mode() == MemoryManagerRangeMode::Allowed
364                                    && range.range().contains(*id)
365                            })
366                    })
367                    .ok_or_else(|| crate::MemoryResolutionError::Exhausted {
368                        stable_key: request.stable_key.clone(),
369                        authority: request.authority.clone(),
370                    })?
371            };
372            let slot = crate::AllocationSlotDescriptor::memory_manager(id).expect("usable id");
373            // Logical requests always need an explicit current grant, including recovered keys.
374            self.range_authority()
375                .validate_slot_authority(&slot, &request.authority)
376                .map_err(crate::MemoryResolutionError::Range)?;
377            occupied[usize::from(id)] = true;
378            // Request construction checked authority/key/schema, and recovery
379            // checked historical schemas. Reuse those fields and the checked
380            // slot; the final snapshot still validates all declarations together.
381            declarations.push(StaticMemoryDeclaration {
382                authority: request.authority.clone(),
383                declaration: AllocationDeclaration {
384                    stable_key: request.stable_key.clone(),
385                    slot,
386                    label: None,
387                    schema: request.schema.clone(),
388                },
389            });
390        }
391        Ok(build_snapshot(
392            &declarations,
393            self.registered_ranges(),
394            &[],
395        )?)
396    }
397
398    /// Borrow fixed declarations, including runtime governance. Key-only requests
399    /// are resolved by the runtime after recovery; inspect committed allocations
400    /// for the complete resolved set.
401    #[must_use]
402    pub fn allocation_snapshot(&self) -> &DeclarationSnapshot {
403        &self.inner.allocation_snapshot
404    }
405
406    /// Borrow canonical external declarations registered by linked code.
407    #[must_use]
408    pub fn registered_declarations(&self) -> &[StaticMemoryDeclaration] {
409        &self.inner.registered_declarations
410    }
411
412    /// Borrow canonical external range declarations registered by linked code.
413    #[must_use]
414    pub fn registered_ranges(&self) -> &[StaticMemoryRangeDeclaration] {
415        &self.inner.registered_ranges
416    }
417
418    /// Borrow the effective range authority, including runtime governance.
419    #[must_use]
420    pub fn range_authority(&self) -> &MemoryManagerRangeAuthority {
421        &self.inner.range_authority
422    }
423
424    /// Return the deterministic fingerprint of this sealed declaration meaning.
425    #[must_use]
426    pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
427        self.inner.fingerprint
428    }
429
430    pub(crate) fn registered_declaration(
431        &self,
432        key: &crate::StableKey,
433    ) -> Option<&StaticMemoryDeclaration> {
434        let declarations = self.registered_declarations();
435        // Sealing establishes unique keys in ascending canonical order.
436        declarations
437            .binary_search_by(|registration| registration.declaration().stable_key().cmp(key))
438            .ok()
439            .map(|index| &declarations[index])
440    }
441
442    pub(crate) fn user_ranges_registered(&self) -> bool {
443        !self.inner.registered_ranges.is_empty()
444    }
445
446    #[cfg(test)]
447    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
448        Arc::ptr_eq(&self.inner, &other.inner)
449    }
450}
451
452type StaticRegistrationHook = fn() -> Result<(), StaticMemoryDeclarationError>;
453
454#[derive(Debug)]
455struct StaticMemoryDeclarationRegistry {
456    declarations: Vec<StaticMemoryDeclaration>,
457    requests: Vec<MemoryRequest>,
458    ranges: Vec<StaticMemoryRangeDeclaration>,
459    registration_hooks: Vec<StaticRegistrationHook>,
460    eager_init_hooks: Vec<fn()>,
461    lifecycle: StaticRegistryLifecycle,
462}
463
464impl StaticMemoryDeclarationRegistry {
465    fn finish_sealing(
466        &mut self,
467        result: Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError>,
468    ) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
469        // Only the immutable snapshot or terminal error remains useful.
470        self.declarations = Vec::new();
471        self.requests = Vec::new();
472        self.ranges = Vec::new();
473        self.registration_hooks = Vec::new();
474        self.eager_init_hooks = Vec::new();
475        self.lifecycle = match &result {
476            Ok(snapshot) => StaticRegistryLifecycle::Sealed(snapshot.clone()),
477            Err(error) => StaticRegistryLifecycle::Failed(error.clone()),
478        };
479        result
480    }
481}
482
483#[derive(Debug)]
484enum StaticRegistryLifecycle {
485    Open,
486    Sealing {
487        owner: ThreadId,
488        deferred_error: Option<StaticMemoryDeclarationError>,
489    },
490    Sealed(SealedDeclarationSnapshot),
491    Failed(StaticMemoryDeclarationError),
492}
493
494static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
495    Mutex::new(StaticMemoryDeclarationRegistry {
496        declarations: Vec::new(),
497        requests: Vec::new(),
498        ranges: Vec::new(),
499        registration_hooks: Vec::new(),
500        eager_init_hooks: Vec::new(),
501        lifecycle: StaticRegistryLifecycle::Open,
502    });
503
504static STATIC_MEMORY_SEAL: Mutex<()> = Mutex::new(());
505
506fn lock_registry()
507-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
508    STATIC_MEMORY_DECLARATIONS
509        .lock()
510        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
511}
512
513fn ensure_registration_open(
514    registry: &StaticMemoryDeclarationRegistry,
515) -> Result<(), StaticMemoryDeclarationError> {
516    match &registry.lifecycle {
517        StaticRegistryLifecycle::Open => Ok(()),
518        StaticRegistryLifecycle::Sealing { owner, .. } if *owner == std::thread::current().id() => {
519            Ok(())
520        }
521        StaticRegistryLifecycle::Sealing { .. }
522        | StaticRegistryLifecycle::Sealed(_)
523        | StaticRegistryLifecycle::Failed(_) => Err(StaticMemoryDeclarationError::RegistrySealed),
524    }
525}
526
527fn with_unsealed_registry(
528    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
529) -> Result<(), StaticMemoryDeclarationError> {
530    let mut registry = lock_registry()?;
531    ensure_registration_open(&registry)?;
532    op(&mut registry);
533    Ok(())
534}
535
536/// Queue a generated registration hook for the fallible sealing phase.
537///
538/// Static constructors cannot return an error. A late deferral is therefore
539/// retained in registry state and returned by snapshot sealing.
540#[doc(hidden)]
541pub fn defer_static_memory_registration(hook: StaticRegistrationHook) {
542    defer_constructor_registration(|registry| {
543        registry.registration_hooks.push(hook);
544    });
545}
546
547/// Queue a declaration-only hook to run immediately before snapshot sealing.
548///
549/// Static constructors cannot return an error. A late deferral is therefore
550/// retained in registry state and returned by snapshot sealing.
551#[doc(hidden)]
552pub fn defer_eager_init(hook: fn()) {
553    defer_constructor_registration(|registry| {
554        registry.eager_init_hooks.push(hook);
555    });
556}
557
558fn defer_constructor_registration(op: impl FnOnce(&mut StaticMemoryDeclarationRegistry)) {
559    let Ok(mut registry) = STATIC_MEMORY_DECLARATIONS.lock() else {
560        // Mutex poisoning is itself durable evidence of the registration
561        // failure and is reported by the next snapshot request.
562        return;
563    };
564    if matches!(registry.lifecycle, StaticRegistryLifecycle::Open) {
565        op(&mut registry);
566        return;
567    }
568    match &mut registry.lifecycle {
569        StaticRegistryLifecycle::Sealing { deferred_error, .. } => {
570            if deferred_error.is_none() {
571                *deferred_error = Some(StaticMemoryDeclarationError::RegistrySealed);
572            }
573        }
574        StaticRegistryLifecycle::Sealed(_) => {
575            registry.lifecycle =
576                StaticRegistryLifecycle::Failed(StaticMemoryDeclarationError::RegistrySealed);
577        }
578        StaticRegistryLifecycle::Failed(_) | StaticRegistryLifecycle::Open => {}
579    }
580}
581
582/// Register one allocation declaration before bootstrap seals the snapshot.
583pub fn register_static_memory_declaration(
584    authority: impl Into<String>,
585    declaration: AllocationDeclaration,
586) -> Result<(), StaticMemoryDeclarationError> {
587    let registration = StaticMemoryDeclaration::new(authority, declaration)?;
588    with_unsealed_registry(|registry| {
589        registry.declarations.push(registration);
590    })
591}
592
593/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
594pub fn register_static_memory_manager_range(
595    start: u8,
596    end: u8,
597    authority: impl Into<String>,
598    mode: MemoryManagerRangeMode,
599    purpose: Option<String>,
600) -> Result<(), StaticMemoryDeclarationError> {
601    let authority = authority.into();
602    let record = MemoryManagerAuthorityRecord::new(
603        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
604        authority,
605        mode,
606        purpose,
607    )?;
608    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
609}
610
611/// Register one authority range declaration before bootstrap seals the snapshot.
612pub fn register_static_memory_range_declaration(
613    declaration: StaticMemoryRangeDeclaration,
614) -> Result<(), StaticMemoryDeclarationError> {
615    validate_external_authority(declaration.authority())?;
616    with_unsealed_registry(|registry| {
617        registry.ranges.push(declaration);
618    })
619}
620
621fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
622    if value == IC_MEMORY_AUTHORITY_OWNER {
623        return Err(StaticMemoryDeclarationError::ReservedAuthority {
624            authority: value.to_string(),
625        });
626    }
627    validate_diagnostic_text(value).map_err(|error| {
628        StaticMemoryDeclarationError::InvalidAuthority {
629            reason: error.reason(),
630        }
631    })
632}
633
634/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
635pub fn register_static_memory_manager_declaration(
636    id: u8,
637    authority: impl Into<String>,
638    label: impl Into<String>,
639    stable_key: impl AsRef<str>,
640) -> Result<(), StaticMemoryDeclarationError> {
641    register_static_memory_manager_declaration_with_schema(
642        id,
643        authority,
644        label,
645        stable_key,
646        SchemaMetadata::default(),
647    )
648}
649
650/// Register one `MemoryManager` declaration with schema metadata.
651pub fn register_static_memory_manager_declaration_with_schema(
652    id: u8,
653    authority: impl Into<String>,
654    label: impl Into<String>,
655    stable_key: impl AsRef<str>,
656    schema: SchemaMetadata,
657) -> Result<(), StaticMemoryDeclarationError> {
658    let declaration =
659        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
660    register_static_memory_declaration(authority, declaration)
661}
662
663/// Seal and return the canonical linked-program declaration snapshot.
664///
665/// The first caller runs deferred generated registrations and eager hooks,
666/// canonicalizes declarations and ranges, validates duplicates and range
667/// authority, and publishes one immutable snapshot. Concurrent and subsequent
668/// callers receive clones backed by that same snapshot.
669///
670/// # Panics
671///
672/// Panics only if a private governance-metadata, sealing or fingerprint-encoding
673/// invariant is broken.
674pub fn sealed_declaration_snapshot()
675-> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
676    {
677        let registry = lock_registry()?;
678        match &registry.lifecycle {
679            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
680            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
681            StaticRegistryLifecycle::Sealing { owner, .. }
682                if *owner == std::thread::current().id() =>
683            {
684                return Err(StaticMemoryDeclarationError::ReentrantSealing);
685            }
686            StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealing { .. } => {}
687        }
688    }
689
690    let _seal = STATIC_MEMORY_SEAL
691        .lock()
692        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
693    let (registration_hooks, eager_init_hooks) = {
694        let mut registry = lock_registry()?;
695        match &registry.lifecycle {
696            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
697            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
698            StaticRegistryLifecycle::Sealing { .. } => {
699                return Err(StaticMemoryDeclarationError::ReentrantSealing);
700            }
701            StaticRegistryLifecycle::Open => {}
702        }
703        registry.lifecycle = StaticRegistryLifecycle::Sealing {
704            owner: std::thread::current().id(),
705            deferred_error: None,
706        };
707        (
708            std::mem::take(&mut registry.registration_hooks),
709            std::mem::take(&mut registry.eager_init_hooks),
710        )
711    };
712
713    for hook in registration_hooks {
714        let result = catch_unwind(AssertUnwindSafe(hook))
715            .map_err(|_| StaticMemoryDeclarationError::EagerInitPanicked)
716            .and_then(std::convert::identity);
717        if let Err(err) = result {
718            return fail_sealing(err);
719        }
720    }
721    for hook in eager_init_hooks {
722        if catch_unwind(AssertUnwindSafe(hook)).is_err() {
723            return fail_sealing(StaticMemoryDeclarationError::EagerInitPanicked);
724        }
725    }
726
727    let mut registry = lock_registry()?;
728    let deferred_error = match &registry.lifecycle {
729        StaticRegistryLifecycle::Sealing { deferred_error, .. } => deferred_error.clone(),
730        StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
731        StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealed(_) => {
732            unreachable!("seal lock preserves the in-progress registry lifecycle");
733        }
734    };
735    let result = match deferred_error {
736        Some(error) => Err(error),
737        None => build_snapshot(&registry.declarations, &registry.ranges, &registry.requests),
738    };
739    registry.finish_sealing(result)
740}
741
742fn fail_sealing(
743    err: StaticMemoryDeclarationError,
744) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
745    let mut registry = lock_registry()?;
746    let failure = match &registry.lifecycle {
747        StaticRegistryLifecycle::Sealing {
748            deferred_error: Some(deferred_error),
749            ..
750        } => deferred_error.clone(),
751        StaticRegistryLifecycle::Open
752        | StaticRegistryLifecycle::Sealing {
753            deferred_error: None,
754            ..
755        }
756        | StaticRegistryLifecycle::Sealed(_) => err,
757        StaticRegistryLifecycle::Failed(failure) => failure.clone(),
758    };
759    registry.finish_sealing(Err(failure))
760}
761
762fn build_snapshot(
763    declarations: &[StaticMemoryDeclaration],
764    ranges: &[StaticMemoryRangeDeclaration],
765    requests: &[MemoryRequest],
766) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
767    if declarations.len().saturating_add(requests.len()) > 254 || ranges.len() > 254 {
768        return Err(StaticMemoryDeclarationError::TooManyDeclarations);
769    }
770    let mut requests = requests.to_vec();
771    // Accepted keys are unique; equal keys reject below, so stability adds no meaning.
772    requests.sort_unstable_by(|a, b| a.stable_key.cmp(&b.stable_key));
773    let mut keys = std::collections::BTreeSet::new();
774    keys.extend(declarations.iter().map(|d| d.declaration().stable_key()));
775    for key in requests.iter().map(|r| &r.stable_key) {
776        if !keys.insert(key) {
777            return Err(StaticMemoryDeclarationError::DuplicateRequest {
778                stable_key: key.clone(),
779            });
780        }
781    }
782    let mut registered_declarations = declarations.to_vec();
783    registered_declarations.sort_by(|left, right| {
784        left.declaration()
785            .stable_key()
786            .cmp(right.declaration().stable_key())
787            .then_with(|| left.declaration().slot().cmp(right.declaration().slot()))
788            .then_with(|| left.authority().cmp(right.authority()))
789    });
790
791    let mut registered_ranges = ranges.to_vec();
792    registered_ranges.sort_by(|left, right| {
793        let left = left.record();
794        let right = right.record();
795        left.range()
796            .start()
797            .cmp(&right.range().start())
798            .then_with(|| left.range().end().cmp(&right.range().end()))
799            .then_with(|| left.authority().cmp(right.authority()))
800            .then_with(|| range_mode_order(left.mode()).cmp(&range_mode_order(right.mode())))
801            .then_with(|| left.purpose().cmp(&right.purpose()))
802    });
803
804    let mut allocation_declarations = Vec::with_capacity(registered_declarations.len() + 1);
805    allocation_declarations.push(internal_ledger_declaration());
806    allocation_declarations.extend(
807        registered_declarations
808            .iter()
809            .map(|registration| registration.declaration().clone()),
810    );
811    let allocation_snapshot = DeclarationSnapshot::new(allocation_declarations)?;
812
813    let mut authority_records = Vec::with_capacity(registered_ranges.len() + 1);
814    authority_records.push(internal_ledger_range());
815    authority_records.extend(
816        registered_ranges
817            .iter()
818            .map(|registration| registration.record().clone()),
819    );
820    let range_authority = MemoryManagerRangeAuthority::from_records(authority_records)?;
821    let fingerprint = sealed_declaration_fingerprint(
822        &allocation_snapshot,
823        &registered_declarations,
824        range_authority.authorities(),
825        &requests,
826    );
827
828    Ok(SealedDeclarationSnapshot {
829        inner: Arc::new(SealedDeclarationSnapshotInner {
830            allocation_snapshot,
831            requests,
832            registered_declarations,
833            registered_ranges,
834            range_authority,
835            fingerprint,
836        }),
837    })
838}
839
840#[derive(Serialize)]
841struct FingerprintDeclaration<'a> {
842    authority: &'a str,
843    declaration: &'a AllocationDeclaration,
844}
845
846#[derive(Serialize)]
847struct SealedDeclarationFingerprintMaterial<'a> {
848    format: &'static str,
849    allocation_snapshot: &'a DeclarationSnapshot,
850    registered_declarations: Vec<FingerprintDeclaration<'a>>,
851    effective_ranges: &'a [MemoryManagerAuthorityRecord],
852    requests: &'a [MemoryRequest],
853}
854
855fn sealed_declaration_fingerprint(
856    allocation_snapshot: &DeclarationSnapshot,
857    registered_declarations: &[StaticMemoryDeclaration],
858    effective_ranges: &[MemoryManagerAuthorityRecord],
859    requests: &[MemoryRequest],
860) -> SealedDeclarationFingerprint {
861    let material = SealedDeclarationFingerprintMaterial {
862        format: "ic-memory.sealed-declaration-fingerprint.v1",
863        allocation_snapshot,
864        registered_declarations: registered_declarations
865            .iter()
866            .map(|registration| FingerprintDeclaration {
867                authority: registration.authority(),
868                declaration: registration.declaration(),
869            })
870            .collect(),
871        effective_ranges,
872        requests,
873    };
874    let mut bytes = Vec::new();
875    // Concrete derived serializers and a Vec writer have no recoverable failures.
876    ciborium::into_writer(&material, &mut bytes)
877        .expect("sealed declaration fingerprint encodes into Vec");
878
879    SealedDeclarationFingerprint {
880        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
881        value: crate::hash::fnv64(crate::hash::FNV_OFFSET, &bytes),
882    }
883}
884
885const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
886const fn range_mode_order(mode: MemoryManagerRangeMode) -> u8 {
887    match mode {
888        MemoryManagerRangeMode::Reserved => 0,
889        MemoryManagerRangeMode::Allowed => 1,
890    }
891}
892
893fn internal_ledger_declaration() -> AllocationDeclaration {
894    AllocationDeclaration::memory_manager(
895        IC_MEMORY_LEDGER_STABLE_KEY,
896        MEMORY_MANAGER_LEDGER_ID,
897        IC_MEMORY_LEDGER_LABEL,
898    )
899    .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
900}
901
902fn internal_ledger_range() -> MemoryManagerAuthorityRecord {
903    MemoryManagerAuthorityRecord::new(
904        memory_manager_governance_range(),
905        IC_MEMORY_AUTHORITY_OWNER,
906        MemoryManagerRangeMode::Reserved,
907        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
908    )
909    .unwrap_or_else(|_| unreachable!("built-in governance range metadata constants are valid"))
910}
911
912#[cfg(test)]
913pub fn reset_static_memory_declarations_for_tests() {
914    let mut registry = STATIC_MEMORY_DECLARATIONS
915        .lock()
916        .expect("static memory declaration registry poisoned");
917    registry.declarations.clear();
918    registry.requests.clear();
919    registry.ranges.clear();
920    registry.registration_hooks.clear();
921    registry.eager_init_hooks.clear();
922    registry.lifecycle = StaticRegistryLifecycle::Open;
923}
924
925#[cfg(test)]
926mod tests;