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