Skip to main content

ic_memory/
registry.rs

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