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 retained ownership 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 retained ownership. 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//! owns that memory's manager, ledger persistence, bootstrap lifecycle, committed
57//! capability, opens, and diagnostics. Each bootstrap attempt fallibly decodes
58//! its ledger record and uses a temporary cell for capacity-checked writes.
59//! Linked code contributes declarations to
60//! one immutable [`SealedDeclarationSnapshot`], which is supplied to each
61//! runtime independently.
62//!
63//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
64//! ledger store. Canic may own bootstrap for a framework canister and compose
65//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
66//! directly for generated database stores; or a standalone application canister
67//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
68//! store. Multiple layers in the same canister must either compose declarations
69//! into that owner or use distinct ledger stores and allocation domains.
70//!
71//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
72//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
73//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
74//! The crate still keeps narrow internal abstractions for storage adapters and
75//! diagnostics, but the native IC path is
76//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
77//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
78//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
79//! -> [`CommittedAllocations`].
80//!
81//! [`ic_stable_structures`] re-exports the exact substrate version used by this
82//! crate. Use its collections and traits with [`RuntimeMemory`] handles;
83//! `ic-memory` owns allocation governance without wrapping typed collections.
84
85mod bootstrap;
86mod capability;
87mod cbor;
88mod constants;
89mod declaration;
90mod diagnostics;
91mod hash;
92mod key;
93mod ledger;
94mod physical;
95mod policy;
96mod registry;
97mod runtime;
98mod schema;
99mod slot;
100mod stable_cell;
101mod text;
102mod validation;
103
104#[cfg(test)]
105mod test_cbor {
106    use serde::{Serialize, de::DeserializeOwned};
107
108    pub use ciborium::Value;
109
110    pub fn to_vec<T: Serialize>(
111        value: &T,
112    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
113        let mut bytes = Vec::new();
114        ciborium::into_writer(value, &mut bytes)?;
115        Ok(bytes)
116    }
117
118    pub fn from_slice<T: DeserializeOwned>(
119        bytes: &[u8],
120    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
121        crate::cbor::from_slice_exact(bytes)
122    }
123
124    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
125        Value::serialized(&value)
126    }
127
128    pub fn hex_fixture(contents: &str) -> Vec<u8> {
129        let hex = contents
130            .chars()
131            .filter(|char| !char.is_whitespace())
132            .collect::<String>();
133        assert_eq!(hex.len() % 2, 0, "fixture hex must have byte pairs");
134        hex.as_bytes()
135            .as_chunks::<2>()
136            .0
137            .iter()
138            .map(|pair| {
139                let pair = std::str::from_utf8(pair).expect("fixture hex is utf8");
140                u8::from_str_radix(pair, 16).expect("fixture hex byte")
141            })
142            .collect()
143    }
144}
145
146/// Stable collections and traits from this crate's exact substrate dependency.
147///
148/// Use the upstream collections with [`RuntimeMemory`] handles obtained through
149/// the owned runtime. This re-export preserves upstream type identity.
150pub use ic_stable_structures;
151
152pub use bootstrap::{
153    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
154    PendingBootstrapCommit,
155};
156pub use capability::{CommittedAllocations, ValidatedAllocations};
157pub use constants::{
158    MAX_LEDGER_BYTES, MAX_LEDGER_NESTING, MAX_LEDGER_RECORD_BYTES, WASM_PAGE_SIZE_BYTES,
159};
160pub use declaration::{AllocationDeclaration, DeclarationSnapshot, DeclarationSnapshotError};
161pub use diagnostics::{
162    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
163    DiagnosticMemorySize, DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticRuntimeBinding,
164    DiagnosticStableCell, DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
165};
166pub use key::{StableKey, StableKeyError};
167pub use ledger::{
168    AllocationLedger, AllocationRecord, AllocationReservationError, AllocationRetirement,
169    AllocationRetirementError, AllocationStageError, AllocationState,
170    LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore, LedgerIntegrityError,
171    LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
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    IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
205    IC_MEMORY_LEDGER_STABLE_KEY, IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID,
206    MEMORY_MANAGER_INVALID_ID, MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID,
207    MEMORY_MANAGER_MIN_ID, MemoryManagerAuthorityRecord, MemoryManagerIdRange,
208    MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError, MemoryManagerRangeError,
209    MemoryManagerRangeMode, MemoryManagerSlot, MemoryManagerSlotError, is_ic_memory_stable_key,
210    memory_manager_governance_range, validate_memory_manager_id,
211};
212pub use stable_cell::{
213    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
214    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
215    StableCellPayloadError, decode_stable_cell_ledger_record,
216    decode_stable_cell_ledger_record_from_memory, decode_stable_cell_payload,
217};
218pub use validation::{AllocationValidationError, validate_allocations};
219
220#[doc(hidden)]
221pub use registry::{defer_eager_init, defer_static_memory_registration};
222
223#[doc(hidden)]
224pub mod __reexports {
225    pub use ctor;
226}
227
228/// Register a `MemoryManager` allocation declaration during static initialization.
229///
230/// The explicit authority is stable policy identity shared with the matching
231/// range declaration. A string literal or shared compile-time string constant
232/// may be used. Internal `ic-memory` authority is unavailable to callers.
233///
234/// This macro only registers declaration metadata. It does not open stable
235/// memory. The bootstrap owner still has to collect/seal declarations, validate
236/// them against the ledger, commit the generation, and then open memory handles.
237#[macro_export]
238macro_rules! ic_memory_declaration {
239    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
240        const _: () = {
241            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
242                $crate::register_memory_request($crate::MemoryRequest::new(
243                    $authority, $stable_key, $crate::SchemaMetadata::default(),
244                )?)
245            }
246            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
247            fn __ic_memory_defer_request() {
248                $crate::defer_static_memory_registration(__ic_memory_register_request);
249            }
250        };
251    };
252    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
253        const _: () = {
254            const __IC_MEMORY_AUTHORITY: &str = $authority;
255
256            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
257                let _ = core::marker::PhantomData::<$label>;
258                $crate::register_static_memory_manager_declaration(
259                    $id,
260                    __IC_MEMORY_AUTHORITY,
261                    stringify!($label),
262                    $stable_key,
263                )
264            }
265
266            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
267            fn __ic_memory_defer_static_declaration() {
268                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
269            }
270        };
271    };
272    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
273        const _: () = {
274            const __IC_MEMORY_AUTHORITY: &str = $authority;
275
276            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
277                $crate::register_static_memory_manager_declaration(
278                    $id,
279                    __IC_MEMORY_AUTHORITY,
280                    $label,
281                    $stable_key,
282                )
283            }
284
285            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
286            fn __ic_memory_defer_static_declaration() {
287                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
288            }
289        };
290    };
291}
292
293/// Declare a `MemoryManager` allocation range during static initialization.
294///
295/// The explicit authority must match every declaration that uses this range.
296/// A shared compile-time string constant can keep those declarations aligned.
297///
298/// Omitting `mode` selects [`MemoryManagerRangeMode::Reserved`]. A reserved
299/// range permits matching fixed or historical claims but supplies no fresh
300/// logical placements. Use `mode = Allowed` to grant a pool for new key-only
301/// requests; neither mode allocates any memory by itself.
302///
303/// A new logical request with no free ID in a matching `Allowed` range returns
304/// [`MemoryResolutionError::Exhausted`], even when a `Reserved` range has free IDs.
305///
306/// # Examples
307///
308/// ```no_run
309/// // Fixed claims use the default Reserved mode.
310/// ic_memory::ic_memory_range!(authority = "framework", start = 10, end = 19);
311///
312/// // New key-only requests require an explicit Allowed pool.
313/// ic_memory::ic_memory_range!(authority = "app", start = 20, end = 29, mode = Allowed);
314/// ic_memory::ic_memory_declaration!(authority = "app", key = "app.users.v1");
315/// ```
316#[macro_export]
317macro_rules! ic_memory_range {
318    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
319        $crate::ic_memory_range!(
320            authority = $authority,
321            start = $start,
322            end = $end,
323            mode = Reserved,
324        );
325    };
326    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
327        const _: () = {
328            const __IC_MEMORY_AUTHORITY: &str = $authority;
329
330            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
331                $crate::register_static_memory_manager_range(
332                    $start,
333                    $end,
334                    __IC_MEMORY_AUTHORITY,
335                    $crate::MemoryManagerRangeMode::$mode,
336                    None,
337                )
338            }
339
340            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
341            fn __ic_memory_defer_static_range() {
342                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
343            }
344        };
345    };
346}
347
348/// Declare and open a committed `MemoryManager` slot by stable key.
349///
350/// The macro registers declaration metadata during static initialization and
351/// returns the typed default-runtime open result at expression use time.
352#[macro_export]
353macro_rules! ic_memory_key {
354    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
355        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
356        $crate::open_default_memory_manager_memory_by_key($stable_key)
357    }};
358    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
359        $crate::ic_memory_declaration!(
360            authority = $authority,
361            key = $stable_key,
362            ty = $label,
363            id = $id,
364        );
365        $crate::open_default_memory_manager_memory($stable_key, $id)
366    }};
367    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
368        $crate::ic_memory_declaration!(
369            authority = $authority,
370            key = $stable_key,
371            label = $label,
372            id = $id,
373        );
374        $crate::open_default_memory_manager_memory($stable_key, $id)
375    }};
376}
377
378/// Register one pre-bootstrap hook.
379#[macro_export]
380macro_rules! eager_init {
381    ($body:block) => {
382        const _: () = {
383            fn __ic_memory_registered_eager_init_body() {
384                $body
385            }
386
387            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
388            fn __ic_memory_register_eager_init() {
389                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
390            }
391        };
392    };
393}