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        declaration.validate()?;
50        if is_ic_memory_stable_key(declaration.stable_key().as_str()) {
51            return Err(StaticMemoryDeclarationError::ReservedStableKey {
52                stable_key: declaration.stable_key().as_str().to_string(),
53            });
54        }
55        Ok(Self {
56            authority,
57            declaration,
58        })
59    }
60
61    /// Return the authority that registered this declaration.
62    #[must_use]
63    pub fn authority(&self) -> &str {
64        &self.authority
65    }
66
67    /// Borrow the allocation declaration.
68    #[must_use]
69    pub const fn declaration(&self) -> &AllocationDeclaration {
70        &self.declaration
71    }
72
73    /// Consume this registration and return the allocation declaration.
74    #[must_use]
75    pub fn into_declaration(self) -> AllocationDeclaration {
76        self.declaration
77    }
78}
79
80///
81/// MemoryRequest
82///
83/// Key-only request resolved after ledger recovery. New keys require an explicit
84/// Allowed range owned by this authority; known keys retain their durable slot.
85///
86
87#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
88pub struct MemoryRequest {
89    authority: String,
90    stable_key: crate::StableKey,
91    schema: SchemaMetadata,
92}
93
94impl MemoryRequest {
95    /// Build a checked logical request before sealing.
96    pub fn new(
97        authority: impl Into<String>,
98        stable_key: &str,
99        schema: SchemaMetadata,
100    ) -> Result<Self, StaticMemoryDeclarationError> {
101        let authority = authority.into();
102        validate_external_authority(&authority)?;
103        let stable_key =
104            crate::StableKey::parse(stable_key).map_err(crate::DeclarationSnapshotError::Key)?;
105        schema
106            .validate()
107            .map_err(crate::DeclarationSnapshotError::SchemaMetadata)?;
108        if is_ic_memory_stable_key(stable_key.as_str()) {
109            return Err(StaticMemoryDeclarationError::ReservedStableKey {
110                stable_key: stable_key.as_str().to_string(),
111            });
112        }
113        Ok(Self {
114            authority,
115            stable_key,
116            schema,
117        })
118    }
119
120    /// Attach schema metadata from the immutable, integrity-checked recovered ledger.
121    pub(crate) const fn with_schema(mut self, schema: SchemaMetadata) -> Self {
122        self.schema = schema;
123        self
124    }
125
126    /// Borrow the requested durable key.
127    #[must_use]
128    pub const fn stable_key(&self) -> &crate::StableKey {
129        &self.stable_key
130    }
131
132    /// Borrow the requested diagnostic schema metadata.
133    #[must_use]
134    pub const fn schema(&self) -> &SchemaMetadata {
135        &self.schema
136    }
137
138    /// Borrow the declaring authority.
139    #[must_use]
140    pub fn authority(&self) -> &str {
141        &self.authority
142    }
143}
144
145/// Register a key-only request before the linked snapshot seals.
146pub fn register_memory_request(request: MemoryRequest) -> Result<(), StaticMemoryDeclarationError> {
147    with_unsealed_registry(|registry| registry.requests.push(request))
148}
149
150///
151/// StaticMemoryRangeDeclaration
152///
153/// One `MemoryManager` authority range registered by crate-level generated or
154/// macro code before the linked registry seals the declaration snapshot. In a
155/// `MemoryRuntime`, registered user ranges are authoritative generic range policy:
156/// declarations must stay inside the authority's claimed range before
157/// caller-supplied policy runs.
158#[derive(Clone, Debug, Eq, PartialEq)]
159pub struct StaticMemoryRangeDeclaration {
160    record: MemoryManagerAuthorityRecord,
161}
162
163impl StaticMemoryRangeDeclaration {
164    /// Build one static range declaration from a validated authority record.
165    pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
166        validate_external_authority(record.authority())?;
167        record.validate()?;
168        Ok(Self { record })
169    }
170
171    /// Return the authority that registered this range.
172    #[must_use]
173    pub fn authority(&self) -> &str {
174        self.record.authority()
175    }
176
177    /// Borrow the authority record.
178    #[must_use]
179    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
180        &self.record
181    }
182
183    /// Consume this registration and return the authority record.
184    #[must_use]
185    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
186        self.record
187    }
188}
189
190///
191/// StaticMemoryDeclarationError
192///
193/// Failure to register or collect static allocation declarations.
194#[non_exhaustive]
195#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
196pub enum StaticMemoryDeclarationError {
197    #[error("at most 254 external declarations and ranges are supported")]
198    TooManyDeclarations,
199    #[error("duplicate requested stable key {stable_key}")]
200    DuplicateRequest { stable_key: crate::StableKey },
201    /// Static declaration registry lock was poisoned.
202    #[error("static memory declaration registry lock poisoned")]
203    RegistryPoisoned,
204    /// Bootstrap already sealed the declaration snapshot.
205    #[error("static memory declaration registry is already sealed")]
206    RegistrySealed,
207    /// Snapshot sealing was called recursively from an eager hook.
208    #[error("static memory declaration snapshot sealing is already active on this thread")]
209    ReentrantSealing,
210    /// A deferred eager initialization hook panicked while declarations were sealing.
211    #[error("static memory declaration eager-init hook panicked")]
212    EagerInitPanicked,
213    /// Declaration validation failed.
214    #[error(transparent)]
215    Declaration(#[from] crate::DeclarationSnapshotError),
216    /// Range authority validation failed.
217    #[error(transparent)]
218    Range(#[from] MemoryManagerRangeAuthorityError),
219    /// External registration attempted to use an invalid authority identifier.
220    #[error("authority {reason}")]
221    InvalidAuthority {
222        /// Validation failure.
223        reason: &'static str,
224    },
225    /// External registration attempted to impersonate the internal authority.
226    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
227    ReservedAuthority {
228        /// Reserved authority identifier.
229        authority: String,
230    },
231    /// External registration attempted to claim the internal stable-key namespace.
232    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
233    ReservedStableKey {
234        /// Reserved stable key.
235        stable_key: String,
236    },
237}
238
239///
240/// SealedDeclarationSnapshot
241///
242/// Immutable, canonical linked-program allocation declarations and range
243/// authority supplied to each concrete [`crate::MemoryRuntime`].
244///
245/// Sealing runs generated registration hooks and eager declaration hooks
246/// exactly once. Clones share the same immutable snapshot. This value contains
247/// declaration authority only; it contains no memory handles, recovery state,
248/// bootstrap lifecycle, or committed allocation capability.
249///
250
251#[derive(Clone, Debug, Eq, PartialEq)]
252pub struct SealedDeclarationSnapshot {
253    inner: Arc<SealedDeclarationSnapshotInner>,
254}
255
256///
257/// SealedDeclarationFingerprint
258///
259/// Deterministic non-cryptographic fingerprint of one canonical sealed
260/// declaration snapshot.
261///
262/// The fingerprint covers canonical allocation declarations, their linked-code
263/// authorities, and the effective range-authority table. It is diagnostic
264/// metadata for comparing in-memory bootstrap bindings, not persisted
265/// allocation authority or an adversarial integrity proof.
266///
267
268#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
269#[serde(deny_unknown_fields)]
270pub struct SealedDeclarationFingerprint {
271    algorithm_version: u8,
272    value: u64,
273}
274
275impl SealedDeclarationFingerprint {
276    /// Return the diagnostic fingerprint algorithm version.
277    #[must_use]
278    pub const fn algorithm_version(&self) -> u8 {
279        self.algorithm_version
280    }
281
282    /// Return the non-cryptographic fingerprint value.
283    #[must_use]
284    pub const fn value(&self) -> u64 {
285        self.value
286    }
287}
288
289#[derive(Debug, Eq, PartialEq)]
290struct SealedDeclarationSnapshotInner {
291    allocation_snapshot: DeclarationSnapshot,
292    requests: Vec<MemoryRequest>,
293    registered_declarations: Vec<StaticMemoryDeclaration>,
294    registered_ranges: Vec<StaticMemoryRangeDeclaration>,
295    range_authority: MemoryManagerRangeAuthority,
296    fingerprint: SealedDeclarationFingerprint,
297}
298
299impl SealedDeclarationSnapshot {
300    /// Seal explicitly owned inputs with the same rules as the linked registry.
301    pub fn new(
302        declarations: &[StaticMemoryDeclaration],
303        ranges: &[StaticMemoryRangeDeclaration],
304        requests: &[MemoryRequest],
305    ) -> Result<Self, StaticMemoryDeclarationError> {
306        build_snapshot(
307            Cow::Borrowed(declarations),
308            Cow::Borrowed(ranges),
309            Cow::Borrowed(requests),
310        )
311    }
312
313    /// Borrow canonical unresolved key-only requests.
314    #[must_use]
315    pub fn requests(&self) -> &[MemoryRequest] {
316        &self.inner.requests
317    }
318
319    pub(crate) fn resolve(
320        &self,
321        ledger: &crate::AllocationLedger,
322        historical: Vec<MemoryRequest>,
323    ) -> Result<Self, crate::MemoryResolutionError> {
324        if self.requests().is_empty() && historical.is_empty() {
325            return Ok(self.clone());
326        }
327        if self.registered_declarations().len() + self.requests().len() + historical.len() > 254 {
328            return Err(StaticMemoryDeclarationError::TooManyDeclarations.into());
329        }
330        let mut declarations = self.registered_declarations().to_vec();
331        let mut occupied = [false; 255];
332        for record in ledger.allocation_history().records() {
333            occupied[usize::from(record.slot().id())] = true;
334        }
335        for fixed in &declarations {
336            occupied[usize::from(fixed.declaration().slot().id())] = true;
337        }
338        // Only the original requests can allocate new slots and they are already
339        // canonical. Admission selections are known-only: all their slots are
340        // occupied above regardless of selection order. Final declarations are
341        // canonicalized and checked together below.
342        for request in self
343            .requests()
344            .iter()
345            .map(Cow::Borrowed)
346            .chain(historical.into_iter().map(Cow::Owned))
347        {
348            let historical = ledger
349                .allocation_history()
350                .records()
351                .iter()
352                .find(|record| record.stable_key() == &request.stable_key);
353            let id = if let Some(record) = historical {
354                let id = record.slot().id();
355                // Historical assignment is not current authorization. Fresh
356                // placement below obtains its authorization from the grant
357                // that supplies the ID.
358                self.range_authority()
359                    .validate_id_authority(id, &request.authority)
360                    .map_err(crate::MemoryResolutionError::Range)?;
361                id
362            } else {
363                // Validated ranges are disjoint and ascending, so walking only
364                // this authority's Allowed grants preserves lowest-ID placement.
365                self.range_authority()
366                    .authorities()
367                    .iter()
368                    .filter(|range| {
369                        range.authority() == request.authority
370                            && range.mode() == MemoryManagerRangeMode::Allowed
371                    })
372                    .flat_map(|range| range.range().start()..=range.range().end())
373                    .find(|id| !occupied[usize::from(*id)])
374                    .ok_or_else(|| crate::MemoryResolutionError::Exhausted {
375                        stable_key: request.stable_key.clone(),
376                        authority: request.authority.clone(),
377                    })?
378            };
379            let slot = crate::MemoryManagerSlot::new(id).expect("usable id");
380            occupied[usize::from(id)] = true;
381            // Request construction checked authority/key/schema, and recovery
382            // checked historical schemas. Copy borrowed source requests only;
383            // owned selections move their fields into the final declarations.
384            // The final snapshot still validates all declarations together.
385            let request = request.into_owned();
386            declarations.push(StaticMemoryDeclaration {
387                authority: request.authority,
388                declaration: AllocationDeclaration {
389                    stable_key: request.stable_key,
390                    slot,
391                    label: None,
392                    schema: request.schema,
393                },
394            });
395        }
396        Ok(build_snapshot(
397            Cow::Owned(declarations),
398            Cow::Borrowed(self.registered_ranges()),
399            Cow::Owned(Vec::new()),
400        )?)
401    }
402
403    /// Borrow fixed declarations, including runtime governance. Key-only requests
404    /// are resolved by the runtime after recovery; inspect committed allocations
405    /// for the complete resolved set.
406    #[must_use]
407    pub fn allocation_snapshot(&self) -> &DeclarationSnapshot {
408        &self.inner.allocation_snapshot
409    }
410
411    /// Borrow canonical external declarations registered by linked code.
412    #[must_use]
413    pub fn registered_declarations(&self) -> &[StaticMemoryDeclaration] {
414        &self.inner.registered_declarations
415    }
416
417    /// Borrow canonical external range declarations registered by linked code.
418    #[must_use]
419    pub fn registered_ranges(&self) -> &[StaticMemoryRangeDeclaration] {
420        &self.inner.registered_ranges
421    }
422
423    /// Borrow the effective range authority, including runtime governance.
424    #[must_use]
425    pub fn range_authority(&self) -> &MemoryManagerRangeAuthority {
426        &self.inner.range_authority
427    }
428
429    /// Return the deterministic fingerprint of this sealed declaration meaning.
430    #[must_use]
431    pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
432        self.inner.fingerprint
433    }
434
435    pub(crate) fn registered_declaration(
436        &self,
437        key: &crate::StableKey,
438    ) -> Option<&StaticMemoryDeclaration> {
439        let declarations = self.registered_declarations();
440        // Sealing establishes unique keys in ascending canonical order.
441        declarations
442            .binary_search_by(|registration| registration.declaration().stable_key().cmp(key))
443            .ok()
444            .map(|index| &declarations[index])
445    }
446
447    pub(crate) fn user_ranges_registered(&self) -> bool {
448        !self.inner.registered_ranges.is_empty()
449    }
450
451    #[cfg(test)]
452    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
453        Arc::ptr_eq(&self.inner, &other.inner)
454    }
455}
456
457type StaticRegistrationHook = fn() -> Result<(), StaticMemoryDeclarationError>;
458
459#[derive(Debug)]
460struct StaticMemoryDeclarationRegistry {
461    declarations: Vec<StaticMemoryDeclaration>,
462    requests: Vec<MemoryRequest>,
463    ranges: Vec<StaticMemoryRangeDeclaration>,
464    registration_hooks: Vec<StaticRegistrationHook>,
465    eager_init_hooks: Vec<fn()>,
466    lifecycle: StaticRegistryLifecycle,
467}
468
469impl StaticMemoryDeclarationRegistry {
470    fn finish_sealing(
471        &mut self,
472        result: Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError>,
473    ) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
474        // Only the immutable snapshot or terminal error remains useful.
475        self.declarations = Vec::new();
476        self.requests = Vec::new();
477        self.ranges = Vec::new();
478        self.registration_hooks = Vec::new();
479        self.eager_init_hooks = Vec::new();
480        self.lifecycle = match &result {
481            Ok(snapshot) => StaticRegistryLifecycle::Sealed(snapshot.clone()),
482            Err(error) => StaticRegistryLifecycle::Failed(error.clone()),
483        };
484        result
485    }
486}
487
488#[derive(Debug)]
489enum StaticRegistryLifecycle {
490    Open,
491    Sealing {
492        owner: ThreadId,
493        deferred_error: Option<StaticMemoryDeclarationError>,
494    },
495    Sealed(SealedDeclarationSnapshot),
496    Failed(StaticMemoryDeclarationError),
497}
498
499static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
500    Mutex::new(StaticMemoryDeclarationRegistry {
501        declarations: Vec::new(),
502        requests: Vec::new(),
503        ranges: Vec::new(),
504        registration_hooks: Vec::new(),
505        eager_init_hooks: Vec::new(),
506        lifecycle: StaticRegistryLifecycle::Open,
507    });
508
509static STATIC_MEMORY_SEAL: Mutex<()> = Mutex::new(());
510
511fn lock_registry()
512-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
513    STATIC_MEMORY_DECLARATIONS
514        .lock()
515        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
516}
517
518fn ensure_registration_open(
519    registry: &StaticMemoryDeclarationRegistry,
520) -> Result<(), StaticMemoryDeclarationError> {
521    match &registry.lifecycle {
522        StaticRegistryLifecycle::Open => Ok(()),
523        StaticRegistryLifecycle::Sealing { owner, .. } if *owner == std::thread::current().id() => {
524            Ok(())
525        }
526        StaticRegistryLifecycle::Sealing { .. }
527        | StaticRegistryLifecycle::Sealed(_)
528        | StaticRegistryLifecycle::Failed(_) => Err(StaticMemoryDeclarationError::RegistrySealed),
529    }
530}
531
532fn with_unsealed_registry(
533    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
534) -> Result<(), StaticMemoryDeclarationError> {
535    let mut registry = lock_registry()?;
536    ensure_registration_open(&registry)?;
537    op(&mut registry);
538    Ok(())
539}
540
541/// Queue a generated registration hook for the fallible sealing phase.
542///
543/// Static constructors cannot return an error. A late deferral is therefore
544/// retained in registry state and returned by snapshot sealing.
545#[doc(hidden)]
546pub fn defer_static_memory_registration(hook: StaticRegistrationHook) {
547    defer_constructor_registration(|registry| {
548        registry.registration_hooks.push(hook);
549    });
550}
551
552/// Queue a declaration-only hook to run immediately before snapshot sealing.
553///
554/// Static constructors cannot return an error. A late deferral is therefore
555/// retained in registry state and returned by snapshot sealing.
556#[doc(hidden)]
557pub fn defer_eager_init(hook: fn()) {
558    defer_constructor_registration(|registry| {
559        registry.eager_init_hooks.push(hook);
560    });
561}
562
563fn defer_constructor_registration(op: impl FnOnce(&mut StaticMemoryDeclarationRegistry)) {
564    let Ok(mut registry) = STATIC_MEMORY_DECLARATIONS.lock() else {
565        // Mutex poisoning is itself durable evidence of the registration
566        // failure and is reported by the next snapshot request.
567        return;
568    };
569    if matches!(registry.lifecycle, StaticRegistryLifecycle::Open) {
570        op(&mut registry);
571        return;
572    }
573    match &mut registry.lifecycle {
574        StaticRegistryLifecycle::Sealing { deferred_error, .. } => {
575            if deferred_error.is_none() {
576                *deferred_error = Some(StaticMemoryDeclarationError::RegistrySealed);
577            }
578        }
579        StaticRegistryLifecycle::Sealed(_) => {
580            registry.lifecycle =
581                StaticRegistryLifecycle::Failed(StaticMemoryDeclarationError::RegistrySealed);
582        }
583        StaticRegistryLifecycle::Failed(_) | StaticRegistryLifecycle::Open => {}
584    }
585}
586
587/// Register one allocation declaration before bootstrap seals the snapshot.
588pub fn register_static_memory_declaration(
589    authority: impl Into<String>,
590    declaration: AllocationDeclaration,
591) -> Result<(), StaticMemoryDeclarationError> {
592    let registration = StaticMemoryDeclaration::new(authority, declaration)?;
593    with_unsealed_registry(|registry| {
594        registry.declarations.push(registration);
595    })
596}
597
598/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
599pub fn register_static_memory_manager_range(
600    start: u8,
601    end: u8,
602    authority: impl Into<String>,
603    mode: MemoryManagerRangeMode,
604    purpose: Option<String>,
605) -> Result<(), StaticMemoryDeclarationError> {
606    let authority = authority.into();
607    let record = MemoryManagerAuthorityRecord::new(
608        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
609        authority,
610        mode,
611        purpose,
612    )?;
613    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
614}
615
616/// Register one authority range declaration before bootstrap seals the snapshot.
617pub fn register_static_memory_range_declaration(
618    declaration: StaticMemoryRangeDeclaration,
619) -> Result<(), StaticMemoryDeclarationError> {
620    with_unsealed_registry(|registry| {
621        registry.ranges.push(declaration);
622    })
623}
624
625fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
626    if value == IC_MEMORY_AUTHORITY_OWNER {
627        return Err(StaticMemoryDeclarationError::ReservedAuthority {
628            authority: value.to_string(),
629        });
630    }
631    validate_diagnostic_text(value).map_err(|error| {
632        StaticMemoryDeclarationError::InvalidAuthority {
633            reason: error.reason(),
634        }
635    })
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    registered_declarations.sort_by(|left, right| {
785        left.declaration()
786            .stable_key()
787            .cmp(right.declaration().stable_key())
788            .then_with(|| left.declaration().slot().cmp(right.declaration().slot()))
789            .then_with(|| left.authority().cmp(right.authority()))
790    });
791
792    // Canonical vectors already supply both membership and adjacency. Check
793    // each request in key order so fixed/request and request/request conflicts
794    // preserve their shared duplicate-error precedence.
795    for (index, request) in requests.iter().enumerate() {
796        if (index > 0 && requests[index - 1].stable_key == request.stable_key)
797            || registered_declarations
798                .binary_search_by(|d| d.declaration().stable_key().cmp(&request.stable_key))
799                .is_ok()
800        {
801            return Err(StaticMemoryDeclarationError::DuplicateRequest {
802                stable_key: request.stable_key.clone(),
803            });
804        }
805    }
806
807    let mut registered_ranges = ranges.into_owned();
808    // Equal bounds reject as overlaps, so metadata cannot distinguish accepted
809    // ranges. Keep bound ordering for deterministic overlap diagnostics.
810    registered_ranges.sort_by(|left, right| {
811        let left = left.record();
812        let right = right.record();
813        left.range()
814            .start()
815            .cmp(&right.range().start())
816            .then_with(|| left.range().end().cmp(&right.range().end()))
817    });
818
819    let mut allocation_declarations = Vec::with_capacity(registered_declarations.len() + 1);
820    allocation_declarations.push(internal_ledger_declaration());
821    allocation_declarations.extend(
822        registered_declarations
823            .iter()
824            .map(|registration| registration.declaration().clone()),
825    );
826    let allocation_snapshot = DeclarationSnapshot::new(allocation_declarations)?;
827
828    let mut authority_records = Vec::with_capacity(registered_ranges.len() + 1);
829    authority_records.push(internal_ledger_range());
830    authority_records.extend(
831        registered_ranges
832            .iter()
833            .map(|registration| registration.record().clone()),
834    );
835    let range_authority = MemoryManagerRangeAuthority::from_records(authority_records)?;
836    let fingerprint = sealed_declaration_fingerprint(
837        &allocation_snapshot,
838        &registered_declarations,
839        range_authority.authorities(),
840        &requests,
841    );
842
843    Ok(SealedDeclarationSnapshot {
844        inner: Arc::new(SealedDeclarationSnapshotInner {
845            allocation_snapshot,
846            requests,
847            registered_declarations,
848            registered_ranges,
849            range_authority,
850            fingerprint,
851        }),
852    })
853}
854
855#[derive(Serialize)]
856struct SealedDeclarationFingerprintMaterial<'a> {
857    format: &'static str,
858    allocation_snapshot: &'a DeclarationSnapshot,
859    registered_declarations: &'a [StaticMemoryDeclaration],
860    effective_ranges: &'a [MemoryManagerAuthorityRecord],
861    requests: &'a [MemoryRequest],
862}
863
864// Fingerprints need the canonical encoded bytes only as input to the hash;
865// keep no payload buffer after serialization.
866struct FingerprintWriter(u64);
867
868impl std::io::Write for FingerprintWriter {
869    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
870        self.0 = crate::hash::fnv64(self.0, bytes);
871        Ok(bytes.len())
872    }
873
874    fn flush(&mut self) -> std::io::Result<()> {
875        Ok(())
876    }
877}
878
879fn sealed_declaration_fingerprint(
880    allocation_snapshot: &DeclarationSnapshot,
881    registered_declarations: &[StaticMemoryDeclaration],
882    effective_ranges: &[MemoryManagerAuthorityRecord],
883    requests: &[MemoryRequest],
884) -> SealedDeclarationFingerprint {
885    let material = SealedDeclarationFingerprintMaterial {
886        format: "ic-memory.sealed-declaration-fingerprint.v1",
887        allocation_snapshot,
888        registered_declarations,
889        effective_ranges,
890        requests,
891    };
892    let mut writer = FingerprintWriter(crate::hash::FNV_OFFSET);
893    // Concrete derived serializers and this hash writer have no recoverable failures.
894    ciborium::into_writer(&material, &mut writer)
895        .expect("sealed declaration fingerprint encodes into hash");
896
897    SealedDeclarationFingerprint {
898        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
899        value: writer.0,
900    }
901}
902
903const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
904
905fn internal_ledger_declaration() -> AllocationDeclaration {
906    AllocationDeclaration::memory_manager(
907        IC_MEMORY_LEDGER_STABLE_KEY,
908        MEMORY_MANAGER_LEDGER_ID,
909        IC_MEMORY_LEDGER_LABEL,
910    )
911    .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
912}
913
914fn internal_ledger_range() -> MemoryManagerAuthorityRecord {
915    MemoryManagerAuthorityRecord::new(
916        memory_manager_governance_range(),
917        IC_MEMORY_AUTHORITY_OWNER,
918        MemoryManagerRangeMode::Reserved,
919        Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
920    )
921    .unwrap_or_else(|_| unreachable!("built-in governance range metadata constants are valid"))
922}
923
924#[cfg(test)]
925pub fn reset_static_memory_declarations_for_tests() {
926    let mut registry = STATIC_MEMORY_DECLARATIONS
927        .lock()
928        .expect("static memory declaration registry poisoned");
929    registry.declarations.clear();
930    registry.requests.clear();
931    registry.ranges.clear();
932    registry.registration_hooks.clear();
933    registry.eager_init_hooks.clear();
934    registry.lifecycle = StaticRegistryLifecycle::Open;
935}
936
937#[cfg(test)]
938mod tests;