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    IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
207    IC_MEMORY_LEDGER_STABLE_KEY, IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID,
208    MEMORY_MANAGER_INVALID_ID, MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID,
209    MEMORY_MANAGER_MIN_ID, MemoryManagerAuthorityRecord, MemoryManagerIdRange,
210    MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError, MemoryManagerRangeError,
211    MemoryManagerRangeMode, MemoryManagerSlot, MemoryManagerSlotError, is_ic_memory_stable_key,
212    memory_manager_governance_range, validate_memory_manager_id,
213};
214pub use stable_cell::{
215    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
216    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
217    StableCellPayloadError, decode_stable_cell_ledger_record,
218    decode_stable_cell_ledger_record_from_memory, decode_stable_cell_payload,
219};
220pub use validation::{AllocationValidationError, validate_allocations};
221
222#[doc(hidden)]
223pub use registry::{defer_eager_init, defer_static_memory_registration};
224
225#[doc(hidden)]
226pub mod __reexports {
227    pub use ctor;
228}
229
230/// Register a `MemoryManager` allocation declaration during static initialization.
231///
232/// The explicit authority is stable policy identity shared with the matching
233/// range declaration. A string literal or shared compile-time string constant
234/// may be used. Internal `ic-memory` authority is unavailable to callers.
235///
236/// This macro only registers declaration metadata. It does not open stable
237/// memory. The bootstrap owner still has to collect/seal declarations, validate
238/// them against the ledger, commit the generation, and then open memory handles.
239#[macro_export]
240macro_rules! ic_memory_declaration {
241    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
242        const _: () = {
243            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
244                $crate::register_memory_request($crate::MemoryRequest::new(
245                    $authority, $stable_key, $crate::SchemaMetadata::default(),
246                )?)
247            }
248            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
249            fn __ic_memory_defer_request() {
250                $crate::defer_static_memory_registration(__ic_memory_register_request);
251            }
252        };
253    };
254    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
255        const _: () = {
256            const __IC_MEMORY_AUTHORITY: &str = $authority;
257
258            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
259                let _ = core::marker::PhantomData::<$label>;
260                $crate::register_static_memory_manager_declaration(
261                    $id,
262                    __IC_MEMORY_AUTHORITY,
263                    stringify!($label),
264                    $stable_key,
265                )
266            }
267
268            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
269            fn __ic_memory_defer_static_declaration() {
270                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
271            }
272        };
273    };
274    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
275        const _: () = {
276            const __IC_MEMORY_AUTHORITY: &str = $authority;
277
278            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
279                $crate::register_static_memory_manager_declaration(
280                    $id,
281                    __IC_MEMORY_AUTHORITY,
282                    $label,
283                    $stable_key,
284                )
285            }
286
287            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
288            fn __ic_memory_defer_static_declaration() {
289                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
290            }
291        };
292    };
293}
294
295/// Declare a `MemoryManager` allocation range during static initialization.
296///
297/// The explicit authority must match every declaration that uses this range.
298/// A shared compile-time string constant can keep those declarations aligned.
299///
300/// Omitting `mode` selects [`MemoryManagerRangeMode::Reserved`]. A reserved
301/// range permits matching fixed or historical claims but supplies no fresh
302/// logical placements. Use `mode = Allowed` to grant a pool for new key-only
303/// requests; neither mode allocates any memory by itself.
304///
305/// A new logical request with no free ID in a matching `Allowed` range returns
306/// [`MemoryResolutionError::Exhausted`], even when a `Reserved` range has free IDs.
307///
308/// # Examples
309///
310/// ```no_run
311/// // Fixed claims use the default Reserved mode.
312/// ic_memory::ic_memory_range!(authority = "framework", start = 10, end = 19);
313///
314/// // New key-only requests require an explicit Allowed pool.
315/// ic_memory::ic_memory_range!(authority = "app", start = 20, end = 29, mode = Allowed);
316/// ic_memory::ic_memory_declaration!(authority = "app", key = "app.users.v1");
317/// ```
318#[macro_export]
319macro_rules! ic_memory_range {
320    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
321        $crate::ic_memory_range!(
322            authority = $authority,
323            start = $start,
324            end = $end,
325            mode = Reserved,
326        );
327    };
328    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
329        const _: () = {
330            const __IC_MEMORY_AUTHORITY: &str = $authority;
331
332            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
333                $crate::register_static_memory_manager_range(
334                    $start,
335                    $end,
336                    __IC_MEMORY_AUTHORITY,
337                    $crate::MemoryManagerRangeMode::$mode,
338                    None,
339                )
340            }
341
342            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
343            fn __ic_memory_defer_static_range() {
344                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
345            }
346        };
347    };
348}
349
350/// Declare and open a committed `MemoryManager` slot by stable key.
351///
352/// The macro registers declaration metadata during static initialization and
353/// returns the typed default-runtime open result at expression use time.
354#[macro_export]
355macro_rules! ic_memory_key {
356    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
357        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
358        $crate::open_default_memory_manager_memory_by_key($stable_key)
359    }};
360    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
361        $crate::ic_memory_declaration!(
362            authority = $authority,
363            key = $stable_key,
364            ty = $label,
365            id = $id,
366        );
367        $crate::open_default_memory_manager_memory($stable_key, $id)
368    }};
369    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
370        $crate::ic_memory_declaration!(
371            authority = $authority,
372            key = $stable_key,
373            label = $label,
374            id = $id,
375        );
376        $crate::open_default_memory_manager_memory($stable_key, $id)
377    }};
378}
379
380/// Register one pre-bootstrap hook.
381#[macro_export]
382macro_rules! eager_init {
383    ($body:block) => {
384        const _: () = {
385            fn __ic_memory_registered_eager_init_body() {
386                $body
387            }
388
389            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
390            fn __ic_memory_register_eager_init() {
391                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
392            }
393        };
394    };
395}