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