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 hex_fixture(contents: &str) -> Vec<u8> {
127        let hex = contents
128            .chars()
129            .filter(|char| !char.is_whitespace())
130            .collect::<String>();
131        assert_eq!(hex.len() % 2, 0, "fixture hex must have byte pairs");
132        hex.as_bytes()
133            .as_chunks::<2>()
134            .0
135            .iter()
136            .map(|pair| {
137                let pair = std::str::from_utf8(pair).expect("fixture hex is utf8");
138                u8::from_str_radix(pair, 16).expect("fixture hex byte")
139            })
140            .collect()
141    }
142}
143
144/// Stable collections and traits from this crate's exact substrate dependency.
145///
146/// Use the upstream collections with [`RuntimeMemory`] handles obtained through
147/// the owned runtime. This re-export preserves upstream type identity.
148pub use ic_stable_structures;
149
150pub use bootstrap::{
151    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
152    PendingBootstrapCommit,
153};
154pub use capability::{CommittedAllocations, ValidatedAllocations};
155pub use constants::{
156    MAX_LEDGER_BYTES, MAX_LEDGER_GENERATIONS, MAX_LEDGER_NESTING, MAX_LEDGER_RECORD_BYTES,
157    WASM_PAGE_SIZE_BYTES,
158};
159pub use declaration::{AllocationDeclaration, DeclarationSnapshot, DeclarationSnapshotError};
160pub use diagnostics::{
161    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
162    DiagnosticMemorySize, DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticRuntimeBinding,
163    DiagnosticStableCell, DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
164};
165pub use key::{StableKey, StableKeyError};
166pub use ledger::{
167    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
168    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
169    GenerationRecord, LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore,
170    LedgerIntegrityError, LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
171    SchemaMetadataRecord,
172};
173pub use physical::{
174    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
175    DualCommitStore,
176};
177pub use policy::{AllocationPolicy, PolicyIdentity, PolicyIdentityError, RuntimeBootstrapPolicy};
178pub use registry::{
179    MemoryRequest, SealedDeclarationFingerprint, SealedDeclarationSnapshot,
180    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
181    register_memory_request, register_static_memory_declaration,
182    register_static_memory_manager_declaration,
183    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
184    register_static_memory_range_declaration, sealed_declaration_snapshot,
185};
186pub use runtime::{
187    AllocationBinding, AllocationRangeClaim, BootstrapAdmission, BootstrapAdmissionError,
188    GenericRangePolicy, MemoryAllocation, MemoryAllocationSummary, MemoryAllocations,
189    MemoryBindingSummary, MemoryManagerConfig, MemoryManagerLayoutError, MemoryResolutionError,
190    MemoryRuntime, RecoveredAllocationMetadata, RuntimeAdoptionError, RuntimeBootstrapError,
191    RuntimeConstructionError, RuntimeDiagnosticError, RuntimeGrowError, RuntimeMemory,
192    RuntimeOpenError, RuntimePolicyError, RuntimeStateError, bootstrap_default_memory_manager,
193    bootstrap_default_memory_manager_with_config, bootstrap_default_memory_manager_with_policy,
194    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
195    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
196    default_memory_manager_doctor_report_with_policy,
197    default_memory_manager_memory_allocation_summary, default_memory_manager_memory_allocations,
198    default_memory_manager_memory_id, is_default_memory_manager_bootstrapped,
199    open_default_memory_manager_memory, open_default_memory_manager_memory_by_key,
200    verify_default_memory_manager_authority,
201};
202pub use schema::{SchemaMetadata, SchemaMetadataError};
203pub use slot::{
204    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
205    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
206    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
207    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
208    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
209    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
210    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
211    validate_memory_manager_id,
212};
213pub use stable_cell::{
214    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
215    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
216    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
217    validate_stable_cell_ledger_memory,
218};
219pub use validation::{AllocationValidationError, validate_allocations};
220
221#[doc(hidden)]
222pub use registry::{defer_eager_init, defer_static_memory_registration};
223
224#[doc(hidden)]
225pub mod __reexports {
226    pub use ctor;
227}
228
229/// Register a `MemoryManager` allocation declaration during static initialization.
230///
231/// The explicit authority is stable policy identity shared with the matching
232/// range declaration. A string literal or shared compile-time string constant
233/// may be used. Internal `ic-memory` authority is unavailable to callers.
234///
235/// This macro only registers declaration metadata. It does not open stable
236/// memory. The bootstrap owner still has to collect/seal declarations, validate
237/// them against the ledger, commit the generation, and then open memory handles.
238#[macro_export]
239macro_rules! ic_memory_declaration {
240    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
241        const _: () = {
242            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
243                $crate::register_memory_request($crate::MemoryRequest::new(
244                    $authority, $stable_key, $crate::SchemaMetadata::default(),
245                )?)
246            }
247            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
248            fn __ic_memory_defer_request() {
249                $crate::defer_static_memory_registration(__ic_memory_register_request);
250            }
251        };
252    };
253    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
254        const _: () = {
255            const __IC_MEMORY_AUTHORITY: &str = $authority;
256
257            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
258                let _ = core::marker::PhantomData::<$label>;
259                $crate::register_static_memory_manager_declaration(
260                    $id,
261                    __IC_MEMORY_AUTHORITY,
262                    stringify!($label),
263                    $stable_key,
264                )
265            }
266
267            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
268            fn __ic_memory_defer_static_declaration() {
269                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
270            }
271        };
272    };
273    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
274        const _: () = {
275            const __IC_MEMORY_AUTHORITY: &str = $authority;
276
277            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
278                $crate::register_static_memory_manager_declaration(
279                    $id,
280                    __IC_MEMORY_AUTHORITY,
281                    $label,
282                    $stable_key,
283                )
284            }
285
286            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
287            fn __ic_memory_defer_static_declaration() {
288                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
289            }
290        };
291    };
292}
293
294/// Declare a `MemoryManager` allocation range during static initialization.
295///
296/// The explicit authority must match every declaration that uses this range.
297/// A shared compile-time string constant can keep those declarations aligned.
298///
299/// Omitting `mode` selects [`MemoryManagerRangeMode::Reserved`]. A reserved
300/// range permits matching fixed or historical claims but supplies no fresh
301/// logical placements. Use `mode = Allowed` to grant a pool for new key-only
302/// requests; neither mode allocates any memory by itself.
303///
304/// A new logical request with no free ID in a matching `Allowed` range returns
305/// [`MemoryResolutionError::Exhausted`], even when a `Reserved` range has free IDs.
306///
307/// # Examples
308///
309/// ```no_run
310/// // Fixed claims use the default Reserved mode.
311/// ic_memory::ic_memory_range!(authority = "framework", start = 10, end = 19);
312///
313/// // New key-only requests require an explicit Allowed pool.
314/// ic_memory::ic_memory_range!(authority = "app", start = 20, end = 29, mode = Allowed);
315/// ic_memory::ic_memory_declaration!(authority = "app", key = "app.users.v1");
316/// ```
317#[macro_export]
318macro_rules! ic_memory_range {
319    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
320        $crate::ic_memory_range!(
321            authority = $authority,
322            start = $start,
323            end = $end,
324            mode = Reserved,
325        );
326    };
327    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
328        const _: () = {
329            const __IC_MEMORY_AUTHORITY: &str = $authority;
330
331            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
332                $crate::register_static_memory_manager_range(
333                    $start,
334                    $end,
335                    __IC_MEMORY_AUTHORITY,
336                    $crate::MemoryManagerRangeMode::$mode,
337                    None,
338                )
339            }
340
341            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
342            fn __ic_memory_defer_static_range() {
343                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
344            }
345        };
346    };
347}
348
349/// Declare and open a committed `MemoryManager` slot by stable key.
350///
351/// The macro registers declaration metadata during static initialization and
352/// returns the typed default-runtime open result at expression use time.
353#[macro_export]
354macro_rules! ic_memory_key {
355    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
356        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
357        $crate::open_default_memory_manager_memory_by_key($stable_key)
358    }};
359    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
360        $crate::ic_memory_declaration!(
361            authority = $authority,
362            key = $stable_key,
363            ty = $label,
364            id = $id,
365        );
366        $crate::open_default_memory_manager_memory($stable_key, $id)
367    }};
368    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
369        $crate::ic_memory_declaration!(
370            authority = $authority,
371            key = $stable_key,
372            label = $label,
373            id = $id,
374        );
375        $crate::open_default_memory_manager_memory($stable_key, $id)
376    }};
377}
378
379/// Register one pre-bootstrap hook.
380#[macro_export]
381macro_rules! eager_init {
382    ($body:block) => {
383        const _: () = {
384            fn __ic_memory_registered_eager_init_body() {
385                $body
386            }
387
388            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
389            fn __ic_memory_register_eager_init() {
390                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
391            }
392        };
393    };
394}