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