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