Skip to main content

ic_memory/
lib.rs

1#![forbid(unsafe_code)]
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 integration flow is:
19//!
20//! 1. Recover the persisted allocation ledger.
21//! 2. Declare the stable stores expected by the current binary.
22//! 3. Validate those declarations against ledger history and any framework
23//!    policy.
24//! 4. Commit the next generation.
25//! 5. Only then open stable-memory handles through committed allocation
26//!    authority.
27//!
28//! This crate owns allocation invariants, not framework policy. Namespace
29//! rules, controller authorization, endpoint lifecycle, schema migrations, and
30//! application validation belong to the framework or application.
31//!
32//! For the default `MemoryManager` runtime, registered `ic-memory` range claims
33//! are generic allocation policy and are enforced before caller-supplied
34//! policy. A framework such as Canic that wants higher-level range semantics
35//! should adapt to this contract deliberately: either register the ranges it
36//! wants `ic-memory` to enforce, or omit user ranges and enforce application
37//! space through its own [`AllocationPolicy`].
38//!
39//! Use these primitives before opening stable-memory handles. Integrations
40//! should recover the historical ledger, declare the stores expected by the
41//! current binary, validate declarations against history and policy, commit a
42//! new generation, and only then publish committed allocation authority before
43//! opening slots through the storage owner.
44//!
45//! [`MemoryRuntime`] is the canonical owner for one backing memory instance. It
46//! contains that memory's manager, ledger cell, bootstrap lifecycle, committed
47//! capability, opens, and diagnostics. Linked code contributes declarations to
48//! one immutable [`SealedDeclarationSnapshot`], which is supplied to each
49//! runtime independently.
50//!
51//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
52//! ledger store. Canic may own bootstrap for a framework canister and compose
53//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
54//! directly for generated database stores; or a standalone application canister
55//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
56//! store. Multiple layers in the same canister must either compose declarations
57//! into that owner or use distinct ledger stores and allocation domains.
58//!
59//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
60//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
61//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
62//! The crate still keeps narrow internal abstractions for storage adapters and
63//! diagnostics, but the native IC path is
64//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
65//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
66//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
67//! -> [`CommittedAllocations`].
68//!
69//! `ic-memory` is not a replacement for `ic-stable-structures` collections and
70//! does not wrap typed stores such as `StableBTreeMap`.
71
72mod bootstrap;
73mod capability;
74mod cbor;
75mod constants;
76mod declaration;
77mod diagnostics;
78mod key;
79mod ledger;
80mod physical;
81mod policy;
82mod registry;
83mod runtime;
84mod schema;
85mod slot;
86mod stable_cell;
87mod validation;
88
89#[cfg(test)]
90mod test_cbor {
91    use serde::{Serialize, de::DeserializeOwned};
92
93    pub use ciborium::Value;
94
95    pub fn to_vec<T: Serialize>(
96        value: &T,
97    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
98        let mut bytes = Vec::new();
99        ciborium::into_writer(value, &mut bytes)?;
100        Ok(bytes)
101    }
102
103    pub fn from_slice<T: DeserializeOwned>(
104        bytes: &[u8],
105    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
106        crate::cbor::from_slice_exact(bytes)
107    }
108
109    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
110        Value::serialized(&value)
111    }
112
113    pub fn map_insert(map: &mut Vec<(Value, Value)>, key: Value, value: Value) {
114        map.push((key, value));
115    }
116}
117
118pub use bootstrap::{
119    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
120    PendingBootstrapCommit,
121};
122pub use capability::{CommittedAllocations, ValidatedAllocations};
123pub use constants::WASM_PAGE_SIZE_BYTES;
124pub use declaration::{
125    AllocationDeclaration, DeclarationCollector, DeclarationSnapshot, DeclarationSnapshotError,
126};
127pub use diagnostics::{
128    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
129    DiagnosticGeneration, DiagnosticMemorySize, DiagnosticRangeAuthority, DiagnosticRecord,
130    DiagnosticStableCell, DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
131};
132pub use key::{StableKey, StableKeyError};
133pub use ledger::{
134    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
135    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
136    GenerationRecord, LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore,
137    LedgerIntegrityError, LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
138    SchemaMetadataRecord,
139};
140pub use physical::{
141    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
142    DualCommitStore,
143};
144pub use policy::{AllocationPolicy, RuntimeBootstrapPolicy};
145pub use registry::{
146    SealedDeclarationSnapshot, StaticMemoryDeclaration, StaticMemoryDeclarationError,
147    StaticMemoryRangeDeclaration, register_static_memory_declaration,
148    register_static_memory_manager_declaration,
149    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
150    register_static_memory_range_declaration, sealed_declaration_snapshot,
151};
152pub use runtime::{
153    MemoryRuntime, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
154    RuntimeOpenError, RuntimePolicyError, RuntimeStateError, bootstrap_default_memory_manager,
155    bootstrap_default_memory_manager_with_policy, committed_allocations,
156    default_memory_manager_commit_recovery_diagnostic, default_memory_manager_diagnostic_export,
157    default_memory_manager_doctor_report, is_default_memory_manager_bootstrapped,
158    open_default_memory_manager_memory,
159};
160pub use schema::{SchemaMetadata, SchemaMetadataError};
161pub use slot::{
162    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
163    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
164    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
165    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
166    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
167    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
168    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
169    validate_memory_manager_id,
170};
171pub use stable_cell::{
172    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
173    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
174    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
175    validate_stable_cell_ledger_memory,
176};
177pub use validation::{AllocationValidationError, Validate, validate_allocations};
178
179#[doc(hidden)]
180pub use registry::{defer_eager_init, defer_static_memory_registration};
181
182#[doc(hidden)]
183pub mod __reexports {
184    pub use ctor;
185}
186
187/// Register a `MemoryManager` allocation declaration during static initialization.
188///
189/// The explicit authority is stable policy identity shared with the matching
190/// range declaration. A string literal or shared compile-time string constant
191/// may be used. Internal `ic-memory` authority is unavailable to callers.
192///
193/// This macro only registers declaration metadata. It does not open stable
194/// memory. The bootstrap owner still has to collect/seal declarations, validate
195/// them against the ledger, commit the generation, and then open memory handles.
196#[macro_export]
197macro_rules! ic_memory_declaration {
198    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
199        const _: () = {
200            const __IC_MEMORY_AUTHORITY: &str = $authority;
201
202            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
203                let _ = core::marker::PhantomData::<$label>;
204                $crate::register_static_memory_manager_declaration(
205                    $id,
206                    __IC_MEMORY_AUTHORITY,
207                    stringify!($label),
208                    $stable_key,
209                )
210            }
211
212            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
213            fn __ic_memory_defer_static_declaration() {
214                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
215            }
216        };
217    };
218    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
219        const _: () = {
220            const __IC_MEMORY_AUTHORITY: &str = $authority;
221
222            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
223                $crate::register_static_memory_manager_declaration(
224                    $id,
225                    __IC_MEMORY_AUTHORITY,
226                    $label,
227                    $stable_key,
228                )
229            }
230
231            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
232            fn __ic_memory_defer_static_declaration() {
233                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
234            }
235        };
236    };
237}
238
239/// Declare a `MemoryManager` allocation range during static initialization.
240///
241/// The explicit authority must match every declaration that uses this range.
242/// A shared compile-time string constant can keep those declarations aligned.
243#[macro_export]
244macro_rules! ic_memory_range {
245    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
246        $crate::ic_memory_range!(
247            authority = $authority,
248            start = $start,
249            end = $end,
250            mode = Reserved,
251        );
252    };
253    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
254        const _: () = {
255            const __IC_MEMORY_AUTHORITY: &str = $authority;
256
257            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
258                $crate::register_static_memory_manager_range(
259                    $start,
260                    $end,
261                    __IC_MEMORY_AUTHORITY,
262                    $crate::MemoryManagerRangeMode::$mode,
263                    None,
264                )
265            }
266
267            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
268            fn __ic_memory_defer_static_range() {
269                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
270            }
271        };
272    };
273}
274
275/// Declare and open a committed `MemoryManager` slot by stable key.
276///
277/// The macro registers declaration metadata during static initialization and
278/// returns the typed default-runtime open result at expression use time.
279#[macro_export]
280macro_rules! ic_memory_key {
281    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
282        $crate::ic_memory_declaration!(
283            authority = $authority,
284            key = $stable_key,
285            ty = $label,
286            id = $id,
287        );
288        $crate::open_default_memory_manager_memory($stable_key, $id)
289    }};
290    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
291        $crate::ic_memory_declaration!(
292            authority = $authority,
293            key = $stable_key,
294            label = $label,
295            id = $id,
296        );
297        $crate::open_default_memory_manager_memory($stable_key, $id)
298    }};
299}
300
301/// Register one pre-bootstrap hook.
302#[macro_export]
303macro_rules! eager_init {
304    ($body:block) => {
305        const _: () = {
306            fn __ic_memory_registered_eager_init_body() {
307                $body
308            }
309
310            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
311            fn __ic_memory_register_eager_init() {
312                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
313            }
314        };
315    };
316}