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