Skip to main content

ic_memory/
lib.rs

1#![deny(unsafe_code, unsafe_op_in_unsafe_fn)]
2#![deny(rustdoc::broken_intra_doc_links)]
3#![doc = include_str!("../README.md")]
4
5//! Stable-memory allocation-governance primitives for Internet Computer
6//! canister upgrades.
7//!
8//! `ic-memory` prevents stable-memory slot drift.
9//!
10//! Once a stable key is committed to a physical allocation slot, future binaries
11//! must either reopen that same stable key on that same slot or declare a new
12//! stable key.
13//!
14//! The crate records and validates durable ownership in both directions: an
15//! active stable key cannot move to a different physical slot, and an active
16//! physical slot cannot be reused by a different stable key.
17//!
18//! The intended runtime integration flow is:
19//!
20//! 1. Recover the persisted allocation ledger.
21//! 2. Admit consumer identity and authorized historical selections from bounded
22//!    metadata under the host's `RuntimeBootstrapPolicy`.
23//! 3. Resolve logical requests under explicit host grants, combine them with
24//!    sealed fixed declarations, then validate against history and current policy.
25//! 4. Stage and durably persist the next generation.
26//! 5. Only then open stable-memory handles through committed allocation
27//!    authority.
28//!
29//! This crate owns allocation invariants, not framework policy. Namespace
30//! rules, controller authorization, endpoint lifecycle, schema migrations, and
31//! application validation belong to the framework or application.
32//!
33//! For the default `MemoryManager` runtime, registered `ic-memory` range claims
34//! are generic allocation policy and are enforced before caller-supplied
35//! policy. A framework such as Canic that wants higher-level range semantics
36//! should adapt to this contract deliberately. Register explicit grants for
37//! logical placement and historical selection. When no user ranges are
38//! registered, the framework's [`AllocationPolicy`] can enforce fixed
39//! application claims directly.
40//!
41//! Use these primitives before opening stable-memory handles. Integrations
42//! should recover the historical ledger, declare the stores expected by the
43//! current binary, admit recovered identity, resolve requests, validate against
44//! history and policy, persist a new generation, and only then publish authority
45//! before opening slots through the storage owner.
46//!
47//! Bounded physical attribution is available through
48//! [`MemoryRuntime::memory_allocations`] and
49//! [`default_memory_manager_memory_allocations`]. It reports actual persisted
50//! buckets and explicit residuals without decoding ledger history. Virtual
51//! extent is not payload occupancy. Opens return [`RuntimeMemory`]; explicit
52//! [`MemoryManagerConfig`] selects fresh-state buckets or checks a persisted
53//! setting without migration. The default remains 128 pages.
54//!
55//! [`MemoryRuntime`] is the canonical owner for one backing memory instance. It
56//! contains that memory's manager, ledger cell, bootstrap lifecycle, committed
57//! capability, opens, and diagnostics. Linked code contributes declarations to
58//! one immutable [`SealedDeclarationSnapshot`], which is supplied to each
59//! runtime independently.
60//!
61//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
62//! ledger store. Canic may own bootstrap for a framework canister and compose
63//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
64//! directly for generated database stores; or a standalone application canister
65//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
66//! store. Multiple layers in the same canister must either compose declarations
67//! into that owner or use distinct ledger stores and allocation domains.
68//!
69//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
70//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
71//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
72//! The crate still keeps narrow internal abstractions for storage adapters and
73//! diagnostics, but the native IC path is
74//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
75//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
76//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
77//! -> [`CommittedAllocations`].
78//!
79//! [`ic_stable_structures`] re-exports the exact substrate version used by this
80//! crate. Use its collections and traits with [`RuntimeMemory`] handles;
81//! `ic-memory` owns allocation governance without wrapping typed collections.
82
83mod bootstrap;
84mod capability;
85mod cbor;
86mod constants;
87mod declaration;
88mod diagnostics;
89mod hash;
90mod key;
91mod ledger;
92mod physical;
93mod policy;
94mod registry;
95mod runtime;
96mod schema;
97mod slot;
98mod stable_cell;
99mod text;
100mod validation;
101
102#[cfg(test)]
103mod test_cbor {
104    use serde::{Serialize, de::DeserializeOwned};
105
106    pub use ciborium::Value;
107
108    pub fn to_vec<T: Serialize>(
109        value: &T,
110    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
111        let mut bytes = Vec::new();
112        ciborium::into_writer(value, &mut bytes)?;
113        Ok(bytes)
114    }
115
116    pub fn from_slice<T: DeserializeOwned>(
117        bytes: &[u8],
118    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
119        crate::cbor::from_slice_exact(bytes)
120    }
121
122    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
123        Value::serialized(&value)
124    }
125
126    pub fn map_insert(map: &mut Vec<(Value, Value)>, key: Value, value: Value) {
127        map.push((key, value));
128    }
129
130    pub fn hex_fixture(contents: &str) -> Vec<u8> {
131        let hex = contents
132            .chars()
133            .filter(|char| !char.is_whitespace())
134            .collect::<String>();
135        assert_eq!(hex.len() % 2, 0, "fixture hex must have byte pairs");
136        hex.as_bytes()
137            .as_chunks::<2>()
138            .0
139            .iter()
140            .map(|pair| {
141                let pair = std::str::from_utf8(pair).expect("fixture hex is utf8");
142                u8::from_str_radix(pair, 16).expect("fixture hex byte")
143            })
144            .collect()
145    }
146}
147
148/// Stable collections and traits from this crate's exact substrate dependency.
149///
150/// Use the upstream collections with [`RuntimeMemory`] handles obtained through
151/// the owned runtime. This re-export preserves upstream type identity.
152pub use ic_stable_structures;
153
154pub use bootstrap::{
155    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
156    PendingBootstrapCommit,
157};
158pub use capability::{CommittedAllocations, ValidatedAllocations};
159pub use constants::{
160    MAX_LEDGER_BYTES, MAX_LEDGER_GENERATIONS, MAX_LEDGER_NESTING, MAX_LEDGER_RECORD_BYTES,
161    WASM_PAGE_SIZE_BYTES,
162};
163pub use declaration::{AllocationDeclaration, DeclarationSnapshot, DeclarationSnapshotError};
164pub use diagnostics::{
165    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
166    DiagnosticMemorySize, DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticRuntimeBinding,
167    DiagnosticStableCell, DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
168};
169pub use key::{StableKey, StableKeyError};
170pub use ledger::{
171    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
172    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
173    GenerationRecord, LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore,
174    LedgerIntegrityError, LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
175    SchemaMetadataRecord,
176};
177pub use physical::{
178    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
179    DualCommitStore,
180};
181pub use policy::{AllocationPolicy, PolicyIdentity, PolicyIdentityError, RuntimeBootstrapPolicy};
182pub use registry::{
183    MemoryRequest, SealedDeclarationFingerprint, SealedDeclarationSnapshot,
184    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
185    register_memory_request, register_static_memory_declaration,
186    register_static_memory_manager_declaration,
187    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
188    register_static_memory_range_declaration, sealed_declaration_snapshot,
189};
190pub use runtime::{
191    AllocationBinding, AllocationRangeClaim, BootstrapAdmission, BootstrapAdmissionError,
192    GenericRangePolicy, MemoryAllocation, MemoryAllocationSummary, MemoryAllocations,
193    MemoryBindingSummary, MemoryManagerConfig, MemoryManagerLayoutError, MemoryResolutionError,
194    MemoryRuntime, RecoveredAllocationMetadata, RuntimeAdoptionError, RuntimeBootstrapError,
195    RuntimeConstructionError, RuntimeDiagnosticError, RuntimeGrowError, RuntimeMemory,
196    RuntimeOpenError, RuntimePolicyError, RuntimeStateError, bootstrap_default_memory_manager,
197    bootstrap_default_memory_manager_with_config, bootstrap_default_memory_manager_with_policy,
198    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
199    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
200    default_memory_manager_doctor_report_with_policy,
201    default_memory_manager_memory_allocation_summary, default_memory_manager_memory_allocations,
202    default_memory_manager_memory_id, is_default_memory_manager_bootstrapped,
203    open_default_memory_manager_memory, open_default_memory_manager_memory_by_key,
204    verify_default_memory_manager_authority,
205};
206pub use schema::{SchemaMetadata, SchemaMetadataError};
207pub use slot::{
208    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
209    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
210    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
211    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
212    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
213    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
214    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
215    validate_memory_manager_id,
216};
217pub use stable_cell::{
218    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
219    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
220    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
221    validate_stable_cell_ledger_memory,
222};
223pub use validation::{AllocationValidationError, validate_allocations};
224
225#[doc(hidden)]
226pub use registry::{defer_eager_init, defer_static_memory_registration};
227
228#[doc(hidden)]
229pub mod __reexports {
230    pub use ctor;
231}
232
233/// Register a `MemoryManager` allocation declaration during static initialization.
234///
235/// The explicit authority is stable policy identity shared with the matching
236/// range declaration. A string literal or shared compile-time string constant
237/// may be used. Internal `ic-memory` authority is unavailable to callers.
238///
239/// This macro only registers declaration metadata. It does not open stable
240/// memory. The bootstrap owner still has to collect/seal declarations, validate
241/// them against the ledger, commit the generation, and then open memory handles.
242#[macro_export]
243macro_rules! ic_memory_declaration {
244    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
245        const _: () = {
246            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
247                $crate::register_memory_request($crate::MemoryRequest::new(
248                    $authority, $stable_key, $crate::SchemaMetadata::default(),
249                )?)
250            }
251            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
252            fn __ic_memory_defer_request() {
253                $crate::defer_static_memory_registration(__ic_memory_register_request);
254            }
255        };
256    };
257    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
258        const _: () = {
259            const __IC_MEMORY_AUTHORITY: &str = $authority;
260
261            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
262                let _ = core::marker::PhantomData::<$label>;
263                $crate::register_static_memory_manager_declaration(
264                    $id,
265                    __IC_MEMORY_AUTHORITY,
266                    stringify!($label),
267                    $stable_key,
268                )
269            }
270
271            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
272            fn __ic_memory_defer_static_declaration() {
273                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
274            }
275        };
276    };
277    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
278        const _: () = {
279            const __IC_MEMORY_AUTHORITY: &str = $authority;
280
281            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
282                $crate::register_static_memory_manager_declaration(
283                    $id,
284                    __IC_MEMORY_AUTHORITY,
285                    $label,
286                    $stable_key,
287                )
288            }
289
290            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
291            fn __ic_memory_defer_static_declaration() {
292                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
293            }
294        };
295    };
296}
297
298/// Declare a `MemoryManager` allocation range during static initialization.
299///
300/// The explicit authority must match every declaration that uses this range.
301/// A shared compile-time string constant can keep those declarations aligned.
302///
303/// Omitting `mode` selects [`MemoryManagerRangeMode::Reserved`]. A reserved
304/// range permits matching fixed or historical claims but supplies no fresh
305/// logical placements. Use `mode = Allowed` to grant a pool for new key-only
306/// requests; neither mode allocates any memory by itself.
307///
308/// A new logical request with no free ID in a matching `Allowed` range returns
309/// [`MemoryResolutionError::Exhausted`], even when a `Reserved` range has free IDs.
310///
311/// # Examples
312///
313/// ```no_run
314/// // Fixed claims use the default Reserved mode.
315/// ic_memory::ic_memory_range!(authority = "framework", start = 10, end = 19);
316///
317/// // New key-only requests require an explicit Allowed pool.
318/// ic_memory::ic_memory_range!(authority = "app", start = 20, end = 29, mode = Allowed);
319/// ic_memory::ic_memory_declaration!(authority = "app", key = "app.users.v1");
320/// ```
321#[macro_export]
322macro_rules! ic_memory_range {
323    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
324        $crate::ic_memory_range!(
325            authority = $authority,
326            start = $start,
327            end = $end,
328            mode = Reserved,
329        );
330    };
331    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
332        const _: () = {
333            const __IC_MEMORY_AUTHORITY: &str = $authority;
334
335            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
336                $crate::register_static_memory_manager_range(
337                    $start,
338                    $end,
339                    __IC_MEMORY_AUTHORITY,
340                    $crate::MemoryManagerRangeMode::$mode,
341                    None,
342                )
343            }
344
345            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
346            fn __ic_memory_defer_static_range() {
347                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
348            }
349        };
350    };
351}
352
353/// Declare and open a committed `MemoryManager` slot by stable key.
354///
355/// The macro registers declaration metadata during static initialization and
356/// returns the typed default-runtime open result at expression use time.
357#[macro_export]
358macro_rules! ic_memory_key {
359    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
360        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
361        $crate::open_default_memory_manager_memory_by_key($stable_key)
362    }};
363    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
364        $crate::ic_memory_declaration!(
365            authority = $authority,
366            key = $stable_key,
367            ty = $label,
368            id = $id,
369        );
370        $crate::open_default_memory_manager_memory($stable_key, $id)
371    }};
372    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
373        $crate::ic_memory_declaration!(
374            authority = $authority,
375            key = $stable_key,
376            label = $label,
377            id = $id,
378        );
379        $crate::open_default_memory_manager_memory($stable_key, $id)
380    }};
381}
382
383/// Register one pre-bootstrap hook.
384#[macro_export]
385macro_rules! eager_init {
386    ($body:block) => {
387        const _: () = {
388            fn __ic_memory_registered_eager_init_body() {
389                $body
390            }
391
392            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
393            fn __ic_memory_register_eager_init() {
394                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
395            }
396        };
397    };
398}