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    borrow::Cow,
15    panic::{AssertUnwindSafe, catch_unwind},
16    sync::{Arc, Mutex, MutexGuard},
17    thread::ThreadId,
18};
19
20#[cfg(test)]
21pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
22
23///
24/// StaticMemoryDeclaration
25///
26/// One allocation declaration registered by crate-level generated or macro
27/// code before the linked declaration registry seals its snapshot.
28///
29/// The `authority` field is policy metadata for integration layers such as
30/// Canic or IcyDB. Each `MemoryRuntime` uses it to match declarations against
31/// registered range claims before it calls the caller's
32/// [`crate::AllocationPolicy`].
33///
34
35#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
36pub struct StaticMemoryDeclaration {
37    authority: String,
38    declaration: AllocationDeclaration,
39}
40
41impl StaticMemoryDeclaration {
42    /// Build one static declaration from raw parts.
43    pub fn new(
44        authority: impl Into<String>,
45        declaration: AllocationDeclaration,
46    ) -> Result<Self, StaticMemoryDeclarationError> {
47        let authority = authority.into();
48        validate_external_authority(&authority)?;
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        if is_ic_memory_stable_key(stable_key.as_str()) {
105            return Err(StaticMemoryDeclarationError::ReservedStableKey {
106                stable_key: stable_key.as_str().to_string(),
107            });
108        }
109        Ok(Self {
110            authority,
111            stable_key,
112            schema,
113        })
114    }
115
116    /// Attach schema metadata from the immutable, integrity-checked recovered ledger.
117    pub(crate) const fn with_schema(mut self, schema: SchemaMetadata) -> Self {
118        self.schema = schema;
119        self
120    }
121
122    /// Borrow the requested durable key.
123    #[must_use]
124    pub const fn stable_key(&self) -> &crate::StableKey {
125        &self.stable_key
126    }
127
128    /// Borrow the requested diagnostic schema metadata.
129    #[must_use]
130    pub const fn schema(&self) -> &SchemaMetadata {
131        &self.schema
132    }
133
134    /// Borrow the declaring authority.
135    #[must_use]
136    pub fn authority(&self) -> &str {
137        &self.authority
138    }
139}
140
141/// Register a key-only request before the linked snapshot seals.
142pub fn register_memory_request(request: MemoryRequest) -> Result<(), StaticMemoryDeclarationError> {
143    with_unsealed_registry(|registry| registry.requests.push(request))
144}
145
146///
147/// StaticMemoryRangeDeclaration
148///
149/// One `MemoryManager` authority range registered by crate-level generated or
150/// macro code before the linked registry seals the declaration snapshot. In a
151/// `MemoryRuntime`, registered user ranges are authoritative generic range policy:
152/// declarations must stay inside the authority's claimed range before
153/// caller-supplied policy runs.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct StaticMemoryRangeDeclaration {
156    record: MemoryManagerAuthorityRecord,
157}
158
159impl StaticMemoryRangeDeclaration {
160    /// Build one static range declaration from a validated authority record.
161    pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
162        // The record owns text validity; linked registration owns governance.
163        reject_internal_authority(record.authority())?;
164        Ok(Self { record })
165    }
166
167    /// Return the authority that registered this range.
168    #[must_use]
169    pub fn authority(&self) -> &str {
170        self.record.authority()
171    }
172
173    /// Borrow the authority record.
174    #[must_use]
175    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
176        &self.record
177    }
178
179    /// Consume this registration and return the authority record.
180    #[must_use]
181    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
182        self.record
183    }
184}
185
186///
187/// StaticMemoryDeclarationError
188///
189/// Failure to register or collect static allocation declarations.
190#[non_exhaustive]
191#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
192pub enum StaticMemoryDeclarationError {
193    #[error("at most 254 external declarations and ranges are supported")]
194    TooManyDeclarations,
195    #[error("duplicate requested stable key {stable_key}")]
196    DuplicateRequest { stable_key: crate::StableKey },
197    /// Static declaration registry lock was poisoned.
198    #[error("static memory declaration registry lock poisoned")]
199    RegistryPoisoned,
200    /// Bootstrap already sealed the declaration snapshot.
201    #[error("static memory declaration registry is already sealed")]
202    RegistrySealed,
203    /// Snapshot sealing was called recursively from an eager hook.
204    #[error("static memory declaration snapshot sealing is already active on this thread")]
205    ReentrantSealing,
206    /// A deferred eager initialization hook panicked while declarations were sealing.
207    #[error("static memory declaration eager-init hook panicked")]
208    EagerInitPanicked,
209    /// Declaration validation failed.
210    #[error(transparent)]
211    Declaration(#[from] crate::DeclarationSnapshotError),
212    /// Range authority validation failed.
213    #[error(transparent)]
214    Range(#[from] MemoryManagerRangeAuthorityError),
215    /// External registration attempted to use an invalid authority identifier.
216    #[error("authority {reason}")]
217    InvalidAuthority {
218        /// Validation failure.
219        reason: &'static str,
220    },
221    /// External registration attempted to impersonate the internal authority.
222    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
223    ReservedAuthority {
224        /// Reserved authority identifier.
225        authority: String,
226    },
227    /// External registration attempted to claim the internal stable-key namespace.
228    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
229    ReservedStableKey {
230        /// Reserved stable key.
231        stable_key: String,
232    },
233}
234
235///
236/// SealedDeclarationSnapshot
237///
238/// Immutable, canonical linked-program allocation declarations and range
239/// authority supplied to each concrete [`crate::MemoryRuntime`].
240///
241/// Sealing runs generated registration hooks and eager declaration hooks
242/// exactly once. Clones share the same immutable snapshot. This value contains
243/// declaration authority only; it contains no memory handles, recovery state,
244/// bootstrap lifecycle, or committed allocation capability.
245///
246
247#[derive(Clone, Debug, Eq, PartialEq)]
248pub struct SealedDeclarationSnapshot {
249    inner: Arc<SealedDeclarationSnapshotInner>,
250}
251
252///
253/// SealedDeclarationFingerprint
254///
255/// Deterministic non-cryptographic fingerprint of one canonical sealed
256/// declaration snapshot.
257///
258/// The fingerprint covers canonical allocation declarations, their linked-code
259/// authorities, and the effective range-authority table. It is diagnostic
260/// metadata for comparing in-memory bootstrap bindings, not persisted
261/// allocation authority or an adversarial integrity proof.
262///
263
264#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
265#[serde(deny_unknown_fields)]
266pub struct SealedDeclarationFingerprint {
267    algorithm_version: u8,
268    value: u64,
269}
270
271impl SealedDeclarationFingerprint {
272    /// Return the diagnostic fingerprint algorithm version.
273    #[must_use]
274    pub const fn algorithm_version(&self) -> u8 {
275        self.algorithm_version
276    }
277
278    /// Return the non-cryptographic fingerprint value.
279    #[must_use]
280    pub const fn value(&self) -> u64 {
281        self.value
282    }
283}
284
285#[derive(Debug, Eq, PartialEq)]
286struct SealedDeclarationSnapshotInner {
287    allocation_snapshot: DeclarationSnapshot,
288    requests: Vec<MemoryRequest>,
289    registered_declarations: Vec<StaticMemoryDeclaration>,
290    registered_ranges: Vec<StaticMemoryRangeDeclaration>,
291    range_authority: MemoryManagerRangeAuthority,
292    fingerprint: SealedDeclarationFingerprint,
293}
294
295impl SealedDeclarationSnapshot {
296    /// Seal explicitly owned inputs with the same rules as the linked registry.
297    pub fn new(
298        declarations: &[StaticMemoryDeclaration],
299        ranges: &[StaticMemoryRangeDeclaration],
300        requests: &[MemoryRequest],
301    ) -> Result<Self, StaticMemoryDeclarationError> {
302        build_snapshot(
303            Cow::Borrowed(declarations),
304            Cow::Borrowed(ranges),
305            Cow::Borrowed(requests),
306        )
307    }
308
309    /// Borrow canonical unresolved key-only requests.
310    #[must_use]
311    pub fn requests(&self) -> &[MemoryRequest] {
312        &self.inner.requests
313    }
314
315    pub(crate) fn resolve(
316        &self,
317        ledger: &crate::AllocationLedger,
318        historical: Vec<MemoryRequest>,
319    ) -> Result<Self, crate::MemoryResolutionError> {
320        if self.requests().is_empty() && historical.is_empty() {
321            return Ok(self.clone());
322        }
323        if self.registered_declarations().len() + self.requests().len() + historical.len() > 254 {
324            return Err(StaticMemoryDeclarationError::TooManyDeclarations.into());
325        }
326        let mut declarations = self.registered_declarations().to_vec();
327        let mut occupied = [false; 255];
328        for record in ledger.records() {
329            occupied[usize::from(record.slot().id())] = true;
330        }
331        for fixed in &declarations {
332            occupied[usize::from(fixed.declaration().slot().id())] = true;
333        }
334        // Only the original requests can allocate new slots and they are already
335        // canonical. Admission selections are known-only: all their slots are
336        // occupied above regardless of selection order. Final declarations are
337        // canonicalized and checked together below.
338        for request in self
339            .requests()
340            .iter()
341            .map(Cow::Borrowed)
342            .chain(historical.into_iter().map(Cow::Owned))
343        {
344            let historical = ledger
345                .records()
346                .iter()
347                .find(|record| record.stable_key() == &request.stable_key);
348            let id = if let Some(record) = historical {
349                let id = record.slot().id();
350                // Historical assignment is not current authorization. Fresh
351                // placement below obtains its authorization from the grant
352                // that supplies the ID.
353                self.range_authority()
354                    .validate_id_authority(id, &request.authority)
355                    .map_err(crate::MemoryResolutionError::Range)?;
356                id
357            } else {
358                // Validated ranges are disjoint and ascending, so walking only
359                // this authority's Allowed grants preserves lowest-ID placement.
360                self.range_authority()
361                    .authorities()
362                    .iter()
363                    .filter(|range| {
364                        range.authority() == request.authority
365                            && range.mode() == MemoryManagerRangeMode::Allowed
366                    })
367                    .flat_map(|range| range.range().start()..=range.range().end())
368                    .find(|id| !occupied[usize::from(*id)])
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::MemoryManagerSlot::new(id).expect("usable id");
375            occupied[usize::from(id)] = true;
376            // Request construction checked authority/key/schema, and recovery
377            // checked historical schemas. Copy borrowed source requests only;
378            // owned selections move their fields into the final declarations.
379            // The final snapshot still validates all declarations together.
380            let request = request.into_owned();
381            declarations.push(StaticMemoryDeclaration {
382                authority: request.authority,
383                declaration: AllocationDeclaration {
384                    stable_key: request.stable_key,
385                    slot,
386                    label: None,
387                    schema: request.schema,
388                },
389            });
390        }
391        Ok(build_snapshot(
392            Cow::Owned(declarations),
393            Cow::Borrowed(self.registered_ranges()),
394            Cow::Owned(Vec::new()),
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    with_unsealed_registry(|registry| {
616        registry.ranges.push(declaration);
617    })
618}
619
620fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
621    reject_internal_authority(value)?;
622    validate_diagnostic_text(value).map_err(|error| {
623        StaticMemoryDeclarationError::InvalidAuthority {
624            reason: error.reason(),
625        }
626    })
627}
628
629fn reject_internal_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
630    if value == IC_MEMORY_AUTHORITY_OWNER {
631        return Err(StaticMemoryDeclarationError::ReservedAuthority {
632            authority: value.to_string(),
633        });
634    }
635    Ok(())
636}
637
638/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
639pub fn register_static_memory_manager_declaration(
640    id: u8,
641    authority: impl Into<String>,
642    label: impl Into<String>,
643    stable_key: impl AsRef<str>,
644) -> Result<(), StaticMemoryDeclarationError> {
645    register_static_memory_manager_declaration_with_schema(
646        id,
647        authority,
648        label,
649        stable_key,
650        SchemaMetadata::default(),
651    )
652}
653
654/// Register one `MemoryManager` declaration with schema metadata.
655pub fn register_static_memory_manager_declaration_with_schema(
656    id: u8,
657    authority: impl Into<String>,
658    label: impl Into<String>,
659    stable_key: impl AsRef<str>,
660    schema: SchemaMetadata,
661) -> Result<(), StaticMemoryDeclarationError> {
662    let declaration =
663        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
664    register_static_memory_declaration(authority, declaration)
665}
666
667/// Seal and return the canonical linked-program declaration snapshot.
668///
669/// The first caller runs deferred generated registrations and eager hooks,
670/// canonicalizes declarations and ranges, validates duplicates and range
671/// authority, and publishes one immutable snapshot. Concurrent and subsequent
672/// callers receive clones backed by that same snapshot.
673///
674/// # Panics
675///
676/// Panics only if a private governance-metadata, sealing or fingerprint-encoding
677/// invariant is broken.
678pub fn sealed_declaration_snapshot()
679-> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
680    {
681        let registry = lock_registry()?;
682        match &registry.lifecycle {
683            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
684            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
685            StaticRegistryLifecycle::Sealing { owner, .. }
686                if *owner == std::thread::current().id() =>
687            {
688                return Err(StaticMemoryDeclarationError::ReentrantSealing);
689            }
690            StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealing { .. } => {}
691        }
692    }
693
694    let _seal = STATIC_MEMORY_SEAL
695        .lock()
696        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
697    let (registration_hooks, eager_init_hooks) = {
698        let mut registry = lock_registry()?;
699        match &registry.lifecycle {
700            StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
701            StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
702            StaticRegistryLifecycle::Sealing { .. } => {
703                return Err(StaticMemoryDeclarationError::ReentrantSealing);
704            }
705            StaticRegistryLifecycle::Open => {}
706        }
707        registry.lifecycle = StaticRegistryLifecycle::Sealing {
708            owner: std::thread::current().id(),
709            deferred_error: None,
710        };
711        (
712            std::mem::take(&mut registry.registration_hooks),
713            std::mem::take(&mut registry.eager_init_hooks),
714        )
715    };
716
717    for hook in registration_hooks {
718        let result = catch_unwind(AssertUnwindSafe(hook))
719            .map_err(|_| StaticMemoryDeclarationError::EagerInitPanicked)
720            .and_then(std::convert::identity);
721        if let Err(err) = result {
722            return fail_sealing(err);
723        }
724    }
725    for hook in eager_init_hooks {
726        if catch_unwind(AssertUnwindSafe(hook)).is_err() {
727            return fail_sealing(StaticMemoryDeclarationError::EagerInitPanicked);
728        }
729    }
730
731    let mut registry = lock_registry()?;
732    let deferred_error = match &registry.lifecycle {
733        StaticRegistryLifecycle::Sealing { deferred_error, .. } => deferred_error.clone(),
734        StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
735        StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealed(_) => {
736            unreachable!("seal lock preserves the in-progress registry lifecycle");
737        }
738    };
739    let result = match deferred_error {
740        Some(error) => Err(error),
741        None => build_snapshot(
742            Cow::Owned(std::mem::take(&mut registry.declarations)),
743            Cow::Owned(std::mem::take(&mut registry.ranges)),
744            Cow::Owned(std::mem::take(&mut registry.requests)),
745        ),
746    };
747    registry.finish_sealing(result)
748}
749
750fn fail_sealing(
751    err: StaticMemoryDeclarationError,
752) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
753    let mut registry = lock_registry()?;
754    let failure = match &registry.lifecycle {
755        StaticRegistryLifecycle::Sealing {
756            deferred_error: Some(deferred_error),
757            ..
758        } => deferred_error.clone(),
759        StaticRegistryLifecycle::Open
760        | StaticRegistryLifecycle::Sealing {
761            deferred_error: None,
762            ..
763        }
764        | StaticRegistryLifecycle::Sealed(_) => err,
765        StaticRegistryLifecycle::Failed(failure) => failure.clone(),
766    };
767    registry.finish_sealing(Err(failure))
768}
769
770fn build_snapshot(
771    declarations: Cow<'_, [StaticMemoryDeclaration]>,
772    ranges: Cow<'_, [StaticMemoryRangeDeclaration]>,
773    requests: Cow<'_, [MemoryRequest]>,
774) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
775    if declarations.len().saturating_add(requests.len()) > 254 || ranges.len() > 254 {
776        return Err(StaticMemoryDeclarationError::TooManyDeclarations);
777    }
778    // Borrowed public inputs stay untouched. Registry sealing and resolution
779    // transfer vectors they would otherwise discard after this build.
780    let mut requests = requests.into_owned();
781    // Accepted keys are unique; equal keys reject below, so stability adds no meaning.
782    requests.sort_unstable_by(|a, b| a.stable_key.cmp(&b.stable_key));
783    let mut registered_declarations = declarations.into_owned();
784    // Comparator ties share key and slot, so snapshot uniqueness rejects them.
785    registered_declarations.sort_unstable_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    // Canonical vectors already supply both membership and adjacency. Check
794    // each request in key order so fixed/request and request/request conflicts
795    // preserve their shared duplicate-error precedence.
796    for (index, request) in requests.iter().enumerate() {
797        if (index > 0 && requests[index - 1].stable_key == request.stable_key)
798            || registered_declarations
799                .binary_search_by(|d| d.declaration().stable_key().cmp(&request.stable_key))
800                .is_ok()
801        {
802            return Err(StaticMemoryDeclarationError::DuplicateRequest {
803                stable_key: request.stable_key.clone(),
804            });
805        }
806    }
807
808    let mut registered_ranges = ranges.into_owned();
809    // Equal bounds reject as overlaps, so metadata cannot distinguish accepted
810    // ranges. Keep bound ordering for deterministic overlap diagnostics.
811    registered_ranges.sort_unstable_by(|left, right| {
812        let left = left.record();
813        let right = right.record();
814        left.range()
815            .start()
816            .cmp(&right.range().start())
817            .then_with(|| left.range().end().cmp(&right.range().end()))
818    });
819
820    let mut allocation_declarations = Vec::with_capacity(registered_declarations.len() + 1);
821    allocation_declarations.push(internal_ledger_declaration());
822    allocation_declarations.extend(
823        registered_declarations
824            .iter()
825            .map(|registration| registration.declaration().clone()),
826    );
827    let allocation_snapshot = DeclarationSnapshot::new(allocation_declarations)?;
828
829    let mut authority_records = Vec::with_capacity(registered_ranges.len() + 1);
830    authority_records.push(internal_ledger_range());
831    authority_records.extend(
832        registered_ranges
833            .iter()
834            .map(|registration| registration.record().clone()),
835    );
836    let range_authority = MemoryManagerRangeAuthority::from_records(authority_records)?;
837    let fingerprint = sealed_declaration_fingerprint(
838        &allocation_snapshot,
839        &registered_declarations,
840        range_authority.authorities(),
841        &requests,
842    );
843
844    Ok(SealedDeclarationSnapshot {
845        inner: Arc::new(SealedDeclarationSnapshotInner {
846            allocation_snapshot,
847            requests,
848            registered_declarations,
849            registered_ranges,
850            range_authority,
851            fingerprint,
852        }),
853    })
854}
855
856#[derive(Serialize)]
857struct SealedDeclarationFingerprintMaterial<'a> {
858    format: &'static str,
859    allocation_snapshot: &'a DeclarationSnapshot,
860    registered_declarations: &'a [StaticMemoryDeclaration],
861    effective_ranges: &'a [MemoryManagerAuthorityRecord],
862    requests: &'a [MemoryRequest],
863}
864
865// Fingerprints need the canonical encoded bytes only as input to the hash;
866// keep no payload buffer after serialization.
867struct FingerprintWriter(u64);
868
869impl std::io::Write for FingerprintWriter {
870    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
871        self.0 = crate::hash::fnv64(self.0, bytes);
872        Ok(bytes.len())
873    }
874
875    fn flush(&mut self) -> std::io::Result<()> {
876        Ok(())
877    }
878}
879
880fn sealed_declaration_fingerprint(
881    allocation_snapshot: &DeclarationSnapshot,
882    registered_declarations: &[StaticMemoryDeclaration],
883    effective_ranges: &[MemoryManagerAuthorityRecord],
884    requests: &[MemoryRequest],
885) -> SealedDeclarationFingerprint {
886    let material = SealedDeclarationFingerprintMaterial {
887        format: "ic-memory.sealed-declaration-fingerprint.v1",
888        allocation_snapshot,
889        registered_declarations,
890        effective_ranges,
891        requests,
892    };
893    let mut writer = FingerprintWriter(crate::hash::FNV_OFFSET);
894    // Concrete derived serializers and this hash writer have no recoverable failures.
895    ciborium::into_writer(&material, &mut writer)
896        .expect("sealed declaration fingerprint encodes into hash");
897
898    SealedDeclarationFingerprint {
899        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
900        value: writer.0,
901    }
902}
903
904const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
905
906fn internal_ledger_declaration() -> AllocationDeclaration {
907    AllocationDeclaration::memory_manager(
908        IC_MEMORY_LEDGER_STABLE_KEY,
909        MEMORY_MANAGER_LEDGER_ID,
910        IC_MEMORY_LEDGER_LABEL,
911    )
912    .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
913}
914
915fn internal_ledger_range() -> MemoryManagerAuthorityRecord {
916    MemoryManagerAuthorityRecord::new(
917        memory_manager_governance_range(),
918        IC_MEMORY_AUTHORITY_OWNER,
919        MemoryManagerRangeMode::Reserved,
920        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
921    )
922    .unwrap_or_else(|_| unreachable!("built-in governance range metadata constants are valid"))
923}
924
925#[cfg(test)]
926pub fn reset_static_memory_declarations_for_tests() {
927    let mut registry = STATIC_MEMORY_DECLARATIONS
928        .lock()
929        .expect("static memory declaration registry poisoned");
930    registry.declarations.clear();
931    registry.requests.clear();
932    registry.ranges.clear();
933    registry.registration_hooks.clear();
934    registry.eager_init_hooks.clear();
935    registry.lifecycle = StaticRegistryLifecycle::Open;
936}
937
938#[cfg(test)]
939mod tests;