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