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, DiagnosticCheckStatus,
123    DiagnosticDeclaration, DiagnosticExport, 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, LedgerCommitError, LedgerCommitStore, LedgerIntegrityError,
131    LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger, SchemaMetadataRecord,
132};
133pub use physical::{
134    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
135    DualCommitStore,
136};
137pub use policy::AllocationPolicy;
138pub use registry::{
139    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
140    collect_static_memory_declarations, register_static_memory_declaration,
141    register_static_memory_manager_declaration,
142    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
143    register_static_memory_range_declaration, static_memory_declaration_snapshot,
144    static_memory_declarations, static_memory_range_authority, static_memory_range_declarations,
145};
146pub use runtime::{
147    RuntimeBootstrapError, RuntimeDiagnosticError, RuntimeOpenError, RuntimePolicyError,
148    bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_policy,
149    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
150    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
151    is_default_memory_manager_bootstrapped, open_default_memory_manager_memory,
152};
153pub use schema::{SchemaMetadata, SchemaMetadataError};
154pub use slot::{
155    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
156    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
157    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
158    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
159    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
160    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
161    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
162    validate_memory_manager_id,
163};
164pub use stable_cell::{
165    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
166    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
167    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
168    validate_stable_cell_ledger_memory,
169};
170pub use validation::{AllocationValidationError, Validate, validate_allocations};
171
172#[doc(hidden)]
173pub use runtime::defer_eager_init;
174
175#[doc(hidden)]
176pub mod __reexports {
177    pub use ctor;
178}
179
180/// Register a `MemoryManager` allocation declaration during static initialization.
181///
182/// The explicit authority is stable policy identity shared with the matching
183/// range declaration. Internal `ic-memory` authority is unavailable to callers.
184///
185/// This macro only registers declaration metadata. It does not open stable
186/// memory. The bootstrap owner still has to collect/seal declarations, validate
187/// them against the ledger, commit the generation, and then open memory handles.
188#[macro_export]
189macro_rules! ic_memory_declaration {
190    (authority = $authority:literal, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
191        const _: () = {
192            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
193            fn __ic_memory_register_static_declaration() {
194                let _ = core::marker::PhantomData::<$label>;
195                $crate::register_static_memory_manager_declaration(
196                    $id,
197                    $authority,
198                    stringify!($label),
199                    $stable_key,
200                )
201                .expect("ic-memory static memory declaration failed");
202            }
203        };
204    };
205    (authority = $authority:literal, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
206        const _: () = {
207            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
208            fn __ic_memory_register_static_declaration() {
209                $crate::register_static_memory_manager_declaration(
210                    $id,
211                    $authority,
212                    $label,
213                    $stable_key,
214                )
215                .expect("ic-memory static memory declaration failed");
216            }
217        };
218    };
219}
220
221/// Declare a `MemoryManager` allocation range during static initialization.
222///
223/// The explicit authority must match every declaration that uses this range.
224#[macro_export]
225macro_rules! ic_memory_range {
226    (authority = $authority:literal, start = $start:expr, end = $end:expr $(,)?) => {
227        $crate::ic_memory_range!(
228            authority = $authority,
229            start = $start,
230            end = $end,
231            mode = Reserved,
232        );
233    };
234    (authority = $authority:literal, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
235        const _: () = {
236            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
237            fn __ic_memory_register_static_range() {
238                $crate::register_static_memory_manager_range(
239                    $start,
240                    $end,
241                    $authority,
242                    $crate::MemoryManagerRangeMode::$mode,
243                    None,
244                )
245                .expect("ic-memory static memory range declaration failed");
246            }
247        };
248    };
249}
250
251/// Declare and open a committed `MemoryManager` slot by stable key.
252///
253/// The macro registers declaration metadata during static initialization and
254/// returns the committed default-runtime memory handle at expression use time.
255#[macro_export]
256macro_rules! ic_memory_key {
257    (authority = $authority:literal, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
258        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key, ty = $label, id = $id,);
259        $crate::open_default_memory_manager_memory($stable_key, $id)
260            .expect("ic-memory failed to open committed stable memory; bootstrap must run first and the stable key/id must match the committed declaration")
261    }};
262    (authority = $authority:literal, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
263        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key, label = $label, id = $id,);
264        $crate::open_default_memory_manager_memory($stable_key, $id)
265            .expect("ic-memory failed to open committed stable memory; bootstrap must run first and the stable key/id must match the committed declaration")
266    }};
267}
268
269/// Register one pre-bootstrap hook.
270#[macro_export]
271macro_rules! eager_init {
272    ($body:block) => {
273        const _: () = {
274            fn __ic_memory_registered_eager_init_body() {
275                $body
276            }
277
278            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
279            fn __ic_memory_register_eager_init() {
280                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
281            }
282        };
283    };
284}