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