Skip to main content

ic_memory/
registry.rs

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