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