Skip to main content

ic_memory/
registry.rs

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