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 key requests in the host-wide pool under current namespace grants,
24//!    then validate retained key-to-ID bindings and application 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//! The host supplies one [`MemoryAllocationPool`] with explicit namespace grants
34//! and physical exclusions. Linked components request permanent keys and name
35//! their owner; they do not select numeric IDs or reserve component subranges.
36//! Namespace grants are current host policy, not caller authentication or
37//! persisted ownership labels. Application admission remains host-owned.
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, admit recovered identity, resolve requests, validate against
42//! history and policy, persist a new generation, and only then publish authority
43//! before opening slots through the storage owner.
44//!
45//! Bounded physical attribution is available through
46//! [`MemoryRuntime::memory_allocations`] and
47//! [`default_memory_manager_memory_allocations`]. It reports actual persisted
48//! buckets and explicit residuals without decoding retained ownership. Virtual
49//! extent is not payload occupancy. Opens return [`RuntimeMemory`]; explicit
50//! [`MemoryManagerConfig`] selects fresh-state buckets or checks a persisted
51//! setting without migration. The default remains 128 pages.
52//!
53//! [`MemoryRuntime`] is the canonical owner for one backing memory instance. It
54//! owns that memory's manager, ledger persistence, bootstrap lifecycle, committed
55//! capability, opens, and diagnostics. Each bootstrap attempt fallibly decodes
56//! its ledger record and uses a temporary cell for capacity-checked writes.
57//! Linked code contributes declarations to
58//! one immutable [`SealedDeclarationSnapshot`], which is supplied to each
59//! runtime independently.
60//!
61//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
62//! ledger store. Canic may own bootstrap for a framework canister and compose
63//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
64//! directly for generated database stores; or a standalone application canister
65//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
66//! store. Multiple layers in the same canister must either compose declarations
67//! into that owner or use distinct ledger stores and allocation domains.
68//!
69//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
70//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
71//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
72//! The crate still keeps narrow internal abstractions for storage adapters and
73//! diagnostics, but the native IC path is
74//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
75//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
76//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
77//! -> [`CommittedAllocations`].
78//!
79//! [`ic_stable_structures`] re-exports the exact substrate version used by this
80//! crate. Use its collections and traits with [`RuntimeMemory`] handles;
81//! `ic-memory` owns allocation governance without wrapping typed collections.
82
83mod allocation_pool;
84mod bootstrap;
85mod capability;
86mod cbor;
87mod constants;
88mod declaration;
89mod diagnostics;
90mod hash;
91mod key;
92mod ledger;
93mod physical;
94mod policy;
95mod registry;
96mod runtime;
97mod schema;
98mod slot;
99mod stable_cell;
100mod text;
101mod validation;
102
103#[cfg(test)]
104mod test_cbor {
105    use serde::{Serialize, de::DeserializeOwned};
106
107    pub use ciborium::Value;
108
109    pub fn to_vec<T: Serialize>(
110        value: &T,
111    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
112        let mut bytes = Vec::new();
113        ciborium::into_writer(value, &mut bytes)?;
114        Ok(bytes)
115    }
116
117    pub fn from_slice<T: DeserializeOwned>(
118        bytes: &[u8],
119    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
120        crate::cbor::from_slice_exact(bytes)
121    }
122
123    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
124        Value::serialized(&value)
125    }
126
127    pub fn hex_fixture(contents: &str) -> Vec<u8> {
128        let hex = contents
129            .chars()
130            .filter(|char| !char.is_whitespace())
131            .collect::<String>();
132        assert_eq!(hex.len() % 2, 0, "fixture hex must have byte pairs");
133        hex.as_bytes()
134            .as_chunks::<2>()
135            .0
136            .iter()
137            .map(|pair| {
138                let pair = std::str::from_utf8(pair).expect("fixture hex is utf8");
139                u8::from_str_radix(pair, 16).expect("fixture hex byte")
140            })
141            .collect()
142    }
143}
144
145/// Stable collections and traits from this crate's exact substrate dependency.
146///
147/// Use the upstream collections with [`RuntimeMemory`] handles obtained through
148/// the owned runtime. This re-export preserves upstream type identity.
149pub use ic_stable_structures;
150
151pub use allocation_pool::{MemoryAllocationPool, MemoryAllocationPoolError, MemoryAuthority};
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_NESTING, MAX_LEDGER_RECORD_BYTES, WASM_PAGE_SIZE_BYTES,
159};
160pub use declaration::{AllocationDeclaration, DeclarationSnapshot, DeclarationSnapshotError};
161pub use diagnostics::{
162    DiagnosticCheck, DiagnosticCode, DiagnosticExport, DiagnosticFailure, DiagnosticMemorySize,
163    DiagnosticRecord, DiagnosticRuntimeBinding, DiagnosticStableCell, DiagnosticStableCellStatus,
164    MemoryRuntimeDoctorReport,
165};
166pub use key::{StableKey, StableKeyError};
167pub use ledger::{
168    AllocationLedger, AllocationRecord, AllocationReservationError, AllocationRetirement,
169    AllocationRetirementError, AllocationStageError, AllocationState,
170    LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore, LedgerIntegrityError,
171    LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
172};
173pub use physical::{
174    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
175    DualCommitStore,
176};
177pub use policy::{AllocationPolicy, PolicyIdentity, PolicyIdentityError, RuntimeBootstrapPolicy};
178pub use registry::{
179    MemoryRequest, SealedDeclarationFingerprint, SealedDeclarationSnapshot,
180    StaticMemoryDeclarationError, register_memory_request, sealed_declaration_snapshot,
181};
182pub use runtime::{
183    AllocationBinding, BootstrapAdmission, BootstrapAdmissionError, GenericAllocationPolicy,
184    MemoryAllocation, MemoryAllocationSummary, MemoryAllocations, MemoryBindingSummary,
185    MemoryManagerConfig, MemoryManagerLayoutError, MemoryResolutionError, MemoryRuntime,
186    RecoveredAllocationMetadata, RuntimeAdoptionError, RuntimeBootstrapError,
187    RuntimeConstructionError, RuntimeDiagnosticError, RuntimeGrowError, RuntimeMemory,
188    RuntimeOpenError, RuntimeStateError, bootstrap_default_memory_manager,
189    bootstrap_default_memory_manager_with_config, bootstrap_default_memory_manager_with_policy,
190    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
191    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
192    default_memory_manager_doctor_report_with_policy,
193    default_memory_manager_memory_allocation_summary, default_memory_manager_memory_allocations,
194    default_memory_manager_memory_id, is_default_memory_manager_bootstrapped,
195    open_default_memory_manager_memory, verify_default_memory_manager_authority,
196};
197pub use schema::{SchemaMetadata, SchemaMetadataError};
198pub use slot::{
199    IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
200    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
201    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID, MemoryManagerIdRange,
202    MemoryManagerRangeError, MemoryManagerSlot, MemoryManagerSlotError, is_ic_memory_stable_key,
203    memory_manager_governance_range, validate_memory_manager_id,
204};
205pub use stable_cell::{
206    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
207    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
208    StableCellPayloadError, decode_stable_cell_ledger_record,
209    decode_stable_cell_ledger_record_from_memory, decode_stable_cell_payload,
210};
211pub use validation::{AllocationValidationError, validate_allocations};
212
213#[doc(hidden)]
214pub use registry::{defer_eager_init, defer_static_memory_registration};
215
216#[doc(hidden)]
217pub mod __reexports {
218    pub use ctor;
219}
220
221/// Register a `MemoryManager` allocation declaration during static initialization.
222///
223/// The authority names the owner admitted by the host namespace grant.
224/// A string literal or shared compile-time string constant
225/// may be used. Internal `ic-memory` authority is unavailable to callers.
226///
227/// This macro only registers declaration metadata. It does not open stable
228/// memory. The bootstrap owner still has to collect/seal declarations, validate
229/// them against the ledger, commit the generation, and then open memory handles.
230#[macro_export]
231macro_rules! ic_memory_declaration {
232    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
233        const _: () = {
234            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
235                $crate::register_memory_request($crate::MemoryRequest::new(
236                    $authority, $stable_key, $crate::SchemaMetadata::default(),
237                )?)
238            }
239            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
240            fn __ic_memory_defer_request() {
241                $crate::defer_static_memory_registration(__ic_memory_register_request);
242            }
243        };
244    };
245}
246
247/// Declare a key-only request and open it after the host has committed bootstrap.
248#[macro_export]
249macro_rules! ic_memory_key {
250    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
251        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
252        $crate::open_default_memory_manager_memory($stable_key)
253    }};
254}
255
256/// Register one pre-bootstrap hook.
257#[macro_export]
258macro_rules! eager_init {
259    ($body:block) => {
260        const _: () = {
261            fn __ic_memory_registered_eager_init_body() {
262                $body
263            }
264
265            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
266            fn __ic_memory_register_eager_init() {
267                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
268            }
269        };
270    };
271}