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            // Logical requests always need an explicit current grant, including recovered keys.
373            self.range_authority()
374                .validate_slot_authority(
375                    &crate::AllocationSlotDescriptor::memory_manager(id).expect("usable id"),
376                    &request.authority,
377                )
378                .map_err(crate::MemoryResolutionError::Range)?;
379            occupied[usize::from(id)] = true;
380            declarations.push(StaticMemoryDeclaration::new(
381                request.authority.clone(),
382                AllocationDeclaration::memory_manager_unlabeled_with_schema(
383                    request.stable_key.as_str(),
384                    id,
385                    request.schema.clone(),
386                )?,
387            )?);
388        }
389        Ok(build_snapshot(
390            &declarations,
391            self.registered_ranges(),
392            &[],
393        )?)
394    }
395
396    /// Borrow fixed declarations, including runtime governance. Key-only requests
397    /// are resolved by the runtime after recovery; inspect committed allocations
398    /// for the complete resolved set.
399    #[must_use]
400    pub fn allocation_snapshot(&self) -> &DeclarationSnapshot {
401        &self.inner.allocation_snapshot
402    }
403
404    /// Borrow canonical external declarations registered by linked code.
405    #[must_use]
406    pub fn registered_declarations(&self) -> &[StaticMemoryDeclaration] {
407        &self.inner.registered_declarations
408    }
409
410    /// Borrow canonical external range declarations registered by linked code.
411    #[must_use]
412    pub fn registered_ranges(&self) -> &[StaticMemoryRangeDeclaration] {
413        &self.inner.registered_ranges
414    }
415
416    /// Borrow the effective range authority, including runtime governance.
417    #[must_use]
418    pub fn range_authority(&self) -> &MemoryManagerRangeAuthority {
419        &self.inner.range_authority
420    }
421
422    /// Return the deterministic fingerprint of this sealed declaration meaning.
423    #[must_use]
424    pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
425        self.inner.fingerprint
426    }
427
428    pub(crate) fn registered_declaration(
429        &self,
430        key: &crate::StableKey,
431    ) -> Option<&StaticMemoryDeclaration> {
432        let declarations = self.registered_declarations();
433        // Sealing establishes unique keys in ascending canonical order.
434        declarations
435            .binary_search_by(|registration| registration.declaration().stable_key().cmp(key))
436            .ok()
437            .map(|index| &declarations[index])
438    }
439
440    pub(crate) fn user_ranges_registered(&self) -> bool {
441        !self.inner.registered_ranges.is_empty()
442    }
443
444    #[cfg(test)]
445    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
446        Arc::ptr_eq(&self.inner, &other.inner)
447    }
448}
449
450type StaticRegistrationHook = fn() -> Result<(), StaticMemoryDeclarationError>;
451
452#[derive(Debug)]
453struct StaticMemoryDeclarationRegistry {
454    declarations: Vec<StaticMemoryDeclaration>,
455    requests: Vec<MemoryRequest>,
456    ranges: Vec<StaticMemoryRangeDeclaration>,
457    registration_hooks: Vec<StaticRegistrationHook>,
458    eager_init_hooks: Vec<fn()>,
459    lifecycle: StaticRegistryLifecycle,
460}
461
462impl StaticMemoryDeclarationRegistry {
463    fn finish_sealing(
464        &mut self,
465        result: Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError>,
466    ) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
467        // Only the immutable snapshot or terminal error remains useful.
468        self.declarations = Vec::new();
469        self.requests = Vec::new();
470        self.ranges = Vec::new();
471        self.registration_hooks = Vec::new();
472        self.eager_init_hooks = Vec::new();
473        self.lifecycle = match &result {
474            Ok(snapshot) => StaticRegistryLifecycle::Sealed(snapshot.clone()),
475            Err(error) => StaticRegistryLifecycle::Failed(error.clone()),
476        };
477        result
478    }
479}
480
481#[derive(Debug)]
482enum StaticRegistryLifecycle {
483    Open,
484    Sealing {
485        owner: ThreadId,
486        deferred_error: Option<StaticMemoryDeclarationError>,
487    },
488    Sealed(SealedDeclarationSnapshot),
489    Failed(StaticMemoryDeclarationError),
490}
491
492static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
493    Mutex::new(StaticMemoryDeclarationRegistry {
494        declarations: Vec::new(),
495        requests: Vec::new(),
496        ranges: Vec::new(),
497        registration_hooks: Vec::new(),
498        eager_init_hooks: Vec::new(),
499        lifecycle: StaticRegistryLifecycle::Open,
500    });
501
502static STATIC_MEMORY_SEAL: Mutex<()> = Mutex::new(());
503
504fn lock_registry()
505-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
506    STATIC_MEMORY_DECLARATIONS
507        .lock()
508        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
509}
510
511fn ensure_registration_open(
512    registry: &StaticMemoryDeclarationRegistry,
513) -> Result<(), StaticMemoryDeclarationError> {
514    match &registry.lifecycle {
515        StaticRegistryLifecycle::Open => Ok(()),
516        StaticRegistryLifecycle::Sealing { owner, .. } if *owner == std::thread::current().id() => {
517            Ok(())
518        }
519        StaticRegistryLifecycle::Sealing { .. }
520        | StaticRegistryLifecycle::Sealed(_)
521        | StaticRegistryLifecycle::Failed(_) => Err(StaticMemoryDeclarationError::RegistrySealed),
522    }
523}
524
525fn with_unsealed_registry(
526    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
527) -> Result<(), StaticMemoryDeclarationError> {
528    let mut registry = lock_registry()?;
529    ensure_registration_open(&registry)?;
530    op(&mut registry);
531    Ok(())
532}
533
534/// Queue a generated registration hook for the fallible sealing phase.
535///
536/// Static constructors cannot return an error. A late deferral is therefore
537/// retained in registry state and returned by snapshot sealing.
538#[doc(hidden)]
539pub fn defer_static_memory_registration(hook: StaticRegistrationHook) {
540    defer_constructor_registration(|registry| {
541        registry.registration_hooks.push(hook);
542    });
543}
544
545/// Queue a declaration-only hook to run immediately before snapshot sealing.
546///
547/// Static constructors cannot return an error. A late deferral is therefore
548/// retained in registry state and returned by snapshot sealing.
549#[doc(hidden)]
550pub fn defer_eager_init(hook: fn()) {
551    defer_constructor_registration(|registry| {
552        registry.eager_init_hooks.push(hook);
553    });
554}
555
556fn defer_constructor_registration(op: impl FnOnce(&mut StaticMemoryDeclarationRegistry)) {
557    let Ok(mut registry) = STATIC_MEMORY_DECLARATIONS.lock() else {
558        // Mutex poisoning is itself durable evidence of the registration
559        // failure and is reported by the next snapshot request.
560        return;
561    };
562    if matches!(registry.lifecycle, StaticRegistryLifecycle::Open) {
563        op(&mut registry);
564        return;
565    }
566    match &mut registry.lifecycle {
567        StaticRegistryLifecycle::Sealing { deferred_error, .. } => {
568            if deferred_error.is_none() {
569                *deferred_error = Some(StaticMemoryDeclarationError::RegistrySealed);
570            }
571        }
572        StaticRegistryLifecycle::Sealed(_) => {
573            registry.lifecycle =
574                StaticRegistryLifecycle::Failed(StaticMemoryDeclarationError::RegistrySealed);
575        }
576        StaticRegistryLifecycle::Failed(_) | StaticRegistryLifecycle::Open => {}
577    }
578}
579
580/// Register one allocation declaration before bootstrap seals the snapshot.
581pub fn register_static_memory_declaration(
582    authority: impl Into<String>,
583    declaration: AllocationDeclaration,
584) -> Result<(), StaticMemoryDeclarationError> {
585    let registration = StaticMemoryDeclaration::new(authority, declaration)?;
586    with_unsealed_registry(|registry| {
587        registry.declarations.push(registration);
588    })
589}
590
591/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
592pub fn register_static_memory_manager_range(
593    start: u8,
594    end: u8,
595    authority: impl Into<String>,
596    mode: MemoryManagerRangeMode,
597    purpose: Option<String>,
598) -> Result<(), StaticMemoryDeclarationError> {
599    let authority = authority.into();
600    let record = MemoryManagerAuthorityRecord::new(
601        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
602        authority,
603        mode,
604        purpose,
605    )?;
606    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
607}
608
609/// Register one authority range declaration before bootstrap seals the snapshot.
610pub fn register_static_memory_range_declaration(
611    declaration: StaticMemoryRangeDeclaration,
612) -> Result<(), StaticMemoryDeclarationError> {
613    validate_external_authority(declaration.authority())?;
614    with_unsealed_registry(|registry| {
615        registry.ranges.push(declaration);
616    })
617}
618
619fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
620    if value == IC_MEMORY_AUTHORITY_OWNER {
621        return Err(StaticMemoryDeclarationError::ReservedAuthority {
622            authority: value.to_string(),
623        });
624    }
625    validate_diagnostic_text(value).map_err(|error| {
626        StaticMemoryDeclarationError::InvalidAuthority {
627            reason: error.reason(),
628        }
629    })
630}
631
632/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
633pub fn register_static_memory_manager_declaration(
634    id: u8,
635    authority: impl Into<String>,
636    label: impl Into<String>,
637    stable_key: impl AsRef<str>,
638) -> Result<(), StaticMemoryDeclarationError> {
639    register_static_memory_manager_declaration_with_schema(
640        id,
641        authority,
642        label,
643        stable_key,
644        SchemaMetadata::default(),
645    )
646}
647
648/// Register one `MemoryManager` declaration with schema metadata.
649pub fn register_static_memory_manager_declaration_with_schema(
650    id: u8,
651    authority: impl Into<String>,
652    label: impl Into<String>,
653    stable_key: impl AsRef<str>,
654    schema: SchemaMetadata,
655) -> Result<(), StaticMemoryDeclarationError> {
656    let declaration =
657        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
658    register_static_memory_declaration(authority, declaration)
659}
660
661/// Seal and return the canonical linked-program declaration snapshot.
662///
663/// The first caller runs deferred generated registrations and eager hooks,
664/// canonicalizes declarations and ranges, validates duplicates and range
665/// authority, and publishes one immutable snapshot. Concurrent and subsequent
666/// callers receive clones backed by that same snapshot.
667///
668/// # Panics
669///
670/// Panics only if a private governance-metadata, sealing or fingerprint-encoding
671/// invariant is broken.
672pub fn sealed_declaration_snapshot()
673-> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
674    {
675        let registry = lock_registry()?;
676        match &registry.lifecycle {
677            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
678            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
679            StaticRegistryLifecycle::Sealing { owner, .. }
680                if *owner == std::thread::current().id() =>
681            {
682                return Err(StaticMemoryDeclarationError::ReentrantSealing);
683            }
684            StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealing { .. } => {}
685        }
686    }
687
688    let _seal = STATIC_MEMORY_SEAL
689        .lock()
690        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
691    let (registration_hooks, eager_init_hooks) = {
692        let mut registry = lock_registry()?;
693        match &registry.lifecycle {
694            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
695            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
696            StaticRegistryLifecycle::Sealing { .. } => {
697                return Err(StaticMemoryDeclarationError::ReentrantSealing);
698            }
699            StaticRegistryLifecycle::Open => {}
700        }
701        registry.lifecycle = StaticRegistryLifecycle::Sealing {
702            owner: std::thread::current().id(),
703            deferred_error: None,
704        };
705        (
706            std::mem::take(&mut registry.registration_hooks),
707            std::mem::take(&mut registry.eager_init_hooks),
708        )
709    };
710
711    for hook in registration_hooks {
712        let result = catch_unwind(AssertUnwindSafe(hook))
713            .map_err(|_| StaticMemoryDeclarationError::EagerInitPanicked)
714            .and_then(std::convert::identity);
715        if let Err(err) = result {
716            return fail_sealing(err);
717        }
718    }
719    for hook in eager_init_hooks {
720        if catch_unwind(AssertUnwindSafe(hook)).is_err() {
721            return fail_sealing(StaticMemoryDeclarationError::EagerInitPanicked);
722        }
723    }
724
725    let mut registry = lock_registry()?;
726    let deferred_error = match &registry.lifecycle {
727        StaticRegistryLifecycle::Sealing { deferred_error, .. } => deferred_error.clone(),
728        StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
729        StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealed(_) => {
730            unreachable!("seal lock preserves the in-progress registry lifecycle");
731        }
732    };
733    let result = match deferred_error {
734        Some(error) => Err(error),
735        None => build_snapshot(&registry.declarations, &registry.ranges, &registry.requests),
736    };
737    registry.finish_sealing(result)
738}
739
740fn fail_sealing(
741    err: StaticMemoryDeclarationError,
742) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
743    let mut registry = lock_registry()?;
744    let failure = match &registry.lifecycle {
745        StaticRegistryLifecycle::Sealing {
746            deferred_error: Some(deferred_error),
747            ..
748        } => deferred_error.clone(),
749        StaticRegistryLifecycle::Open
750        | StaticRegistryLifecycle::Sealing {
751            deferred_error: None,
752            ..
753        }
754        | StaticRegistryLifecycle::Sealed(_) => err,
755        StaticRegistryLifecycle::Failed(failure) => failure.clone(),
756    };
757    registry.finish_sealing(Err(failure))
758}
759
760fn build_snapshot(
761    declarations: &[StaticMemoryDeclaration],
762    ranges: &[StaticMemoryRangeDeclaration],
763    requests: &[MemoryRequest],
764) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
765    if declarations.len().saturating_add(requests.len()) > 254 || ranges.len() > 254 {
766        return Err(StaticMemoryDeclarationError::TooManyDeclarations);
767    }
768    let mut requests = requests.to_vec();
769    // Accepted keys are unique; equal keys reject below, so stability adds no meaning.
770    requests.sort_unstable_by(|a, b| a.stable_key.cmp(&b.stable_key));
771    let mut keys = std::collections::BTreeSet::new();
772    keys.extend(declarations.iter().map(|d| d.declaration().stable_key()));
773    for key in requests.iter().map(|r| &r.stable_key) {
774        if !keys.insert(key) {
775            return Err(StaticMemoryDeclarationError::DuplicateRequest {
776                stable_key: key.clone(),
777            });
778        }
779    }
780    let mut registered_declarations = declarations.to_vec();
781    registered_declarations.sort_by(|left, right| {
782        left.declaration()
783            .stable_key()
784            .cmp(right.declaration().stable_key())
785            .then_with(|| left.declaration().slot().cmp(right.declaration().slot()))
786            .then_with(|| left.authority().cmp(right.authority()))
787    });
788
789    let mut registered_ranges = ranges.to_vec();
790    registered_ranges.sort_by(|left, right| {
791        let left = left.record();
792        let right = right.record();
793        left.range()
794            .start()
795            .cmp(&right.range().start())
796            .then_with(|| left.range().end().cmp(&right.range().end()))
797            .then_with(|| left.authority().cmp(right.authority()))
798            .then_with(|| range_mode_order(left.mode()).cmp(&range_mode_order(right.mode())))
799            .then_with(|| left.purpose().cmp(&right.purpose()))
800    });
801
802    let mut allocation_declarations = Vec::with_capacity(registered_declarations.len() + 1);
803    allocation_declarations.push(internal_ledger_declaration());
804    allocation_declarations.extend(
805        registered_declarations
806            .iter()
807            .map(|registration| registration.declaration().clone()),
808    );
809    let allocation_snapshot = DeclarationSnapshot::new(allocation_declarations)?;
810
811    let mut authority_records = Vec::with_capacity(registered_ranges.len() + 1);
812    authority_records.push(internal_ledger_range());
813    authority_records.extend(
814        registered_ranges
815            .iter()
816            .map(|registration| registration.record().clone()),
817    );
818    let range_authority = MemoryManagerRangeAuthority::from_records(authority_records)?;
819    let fingerprint = sealed_declaration_fingerprint(
820        &allocation_snapshot,
821        &registered_declarations,
822        range_authority.authorities(),
823        &requests,
824    );
825
826    Ok(SealedDeclarationSnapshot {
827        inner: Arc::new(SealedDeclarationSnapshotInner {
828            allocation_snapshot,
829            requests,
830            registered_declarations,
831            registered_ranges,
832            range_authority,
833            fingerprint,
834        }),
835    })
836}
837
838#[derive(Serialize)]
839struct FingerprintDeclaration<'a> {
840    authority: &'a str,
841    declaration: &'a AllocationDeclaration,
842}
843
844#[derive(Serialize)]
845struct SealedDeclarationFingerprintMaterial<'a> {
846    format: &'static str,
847    allocation_snapshot: &'a DeclarationSnapshot,
848    registered_declarations: Vec<FingerprintDeclaration<'a>>,
849    effective_ranges: &'a [MemoryManagerAuthorityRecord],
850    requests: &'a [MemoryRequest],
851}
852
853fn sealed_declaration_fingerprint(
854    allocation_snapshot: &DeclarationSnapshot,
855    registered_declarations: &[StaticMemoryDeclaration],
856    effective_ranges: &[MemoryManagerAuthorityRecord],
857    requests: &[MemoryRequest],
858) -> SealedDeclarationFingerprint {
859    let material = SealedDeclarationFingerprintMaterial {
860        format: "ic-memory.sealed-declaration-fingerprint.v1",
861        allocation_snapshot,
862        registered_declarations: registered_declarations
863            .iter()
864            .map(|registration| FingerprintDeclaration {
865                authority: registration.authority(),
866                declaration: registration.declaration(),
867            })
868            .collect(),
869        effective_ranges,
870        requests,
871    };
872    let mut bytes = Vec::new();
873    // Concrete derived serializers and a Vec writer have no recoverable failures.
874    ciborium::into_writer(&material, &mut bytes)
875        .expect("sealed declaration fingerprint encodes into Vec");
876
877    SealedDeclarationFingerprint {
878        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
879        value: crate::hash::fnv64(crate::hash::FNV_OFFSET, &bytes),
880    }
881}
882
883const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
884const fn range_mode_order(mode: MemoryManagerRangeMode) -> u8 {
885    match mode {
886        MemoryManagerRangeMode::Reserved => 0,
887        MemoryManagerRangeMode::Allowed => 1,
888    }
889}
890
891fn internal_ledger_declaration() -> AllocationDeclaration {
892    AllocationDeclaration::memory_manager(
893        IC_MEMORY_LEDGER_STABLE_KEY,
894        MEMORY_MANAGER_LEDGER_ID,
895        IC_MEMORY_LEDGER_LABEL,
896    )
897    .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
898}
899
900fn internal_ledger_range() -> MemoryManagerAuthorityRecord {
901    MemoryManagerAuthorityRecord::new(
902        memory_manager_governance_range(),
903        IC_MEMORY_AUTHORITY_OWNER,
904        MemoryManagerRangeMode::Reserved,
905        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
906    )
907    .unwrap_or_else(|_| unreachable!("built-in governance range metadata constants are valid"))
908}
909
910#[cfg(test)]
911pub fn reset_static_memory_declarations_for_tests() {
912    let mut registry = STATIC_MEMORY_DECLARATIONS
913        .lock()
914        .expect("static memory declaration registry poisoned");
915    registry.declarations.clear();
916    registry.requests.clear();
917    registry.ranges.clear();
918    registry.registration_hooks.clear();
919    registry.eager_init_hooks.clear();
920    registry.lifecycle = StaticRegistryLifecycle::Open;
921}
922
923#[cfg(test)]
924mod tests;