Skip to main content

canic_core/memory/
registry.rs

1use super::{ledger, policy};
2use ic_memory::{AllocationDeclaration, DeclarationSnapshotError, SchemaMetadata};
3#[cfg(test)]
4use std::cell::RefCell;
5use std::sync::Mutex;
6use thiserror::Error as ThisError;
7
8///
9/// PendingRegistration
10///
11/// One stable-memory declaration collected before bootstrap validation.
12///
13
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub(crate) struct PendingRegistration {
16    crate_name: String,
17    declaration: AllocationDeclaration,
18}
19
20impl PendingRegistration {
21    fn from_parts(
22        id: u8,
23        crate_name: &str,
24        label: &str,
25        stable_key: &str,
26        schema_version: Option<u32>,
27        schema_fingerprint: Option<&str>,
28    ) -> Result<Self, MemoryRegistryError> {
29        let schema = SchemaMetadata::new(schema_version, schema_fingerprint.map(str::to_string))
30            .map_err(|_| MemoryRegistryError::InvalidDeclaration {
31                stable_key: stable_key.to_string(),
32                reason: "schema metadata rejected by ic-memory",
33            })?;
34        let declaration =
35            AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)
36                .map_err(|err| memory_registry_error_from_declaration_error(err, stable_key))?;
37        policy::validate_stable_key_authority(id, crate_name, stable_key)?;
38
39        Ok(Self {
40            crate_name: crate_name.to_string(),
41            declaration,
42        })
43    }
44
45    pub(crate) fn internal_layout_ledger() -> Result<Self, MemoryRegistryError> {
46        Self::from_parts(
47            ledger::MEMORY_LAYOUT_LEDGER_ID,
48            ledger::MEMORY_LAYOUT_LEDGER_OWNER,
49            ledger::MEMORY_LAYOUT_LEDGER_LABEL,
50            ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY,
51            None,
52            None,
53        )
54    }
55
56    pub(crate) fn crate_name(&self) -> &str {
57        &self.crate_name
58    }
59
60    pub(crate) const fn declaration(&self) -> &AllocationDeclaration {
61        &self.declaration
62    }
63}
64
65///
66/// MemoryRegistryError
67///
68/// Errors returned when a memory ID declaration is invalid.
69
70#[derive(Debug, ThisError)]
71pub enum MemoryRegistryError {
72    /// A declaration was rejected before or during `ic-memory` validation.
73    #[error("memory declaration rejected for stable key '{stable_key}': {reason}")]
74    InvalidDeclaration {
75        stable_key: String,
76        reason: &'static str,
77    },
78
79    /// The stable key namespace and memory ID range do not match.
80    #[error(
81        "memory stable key '{stable_key}' with id {id} violates namespace/range authority: {reason}"
82    )]
83    RangeAuthorityViolation {
84        /// Stable key being registered.
85        stable_key: String,
86        /// Stable-memory ID being registered.
87        id: u8,
88        /// Human-readable reason for the rejection.
89        reason: &'static str,
90    },
91
92    /// Registration was attempted after the bootstrap declaration snapshot was sealed.
93    #[error(
94        "memory registration after bootstrap is sealed is not allowed: {registrations} registration(s)"
95    )]
96    RegistrationAfterBootstrap {
97        /// Number of late memory ID declarations.
98        registrations: usize,
99    },
100
101    /// A memory handle was requested before bootstrap validated the declaration snapshot.
102    #[error("memory registry has not completed bootstrap validation")]
103    RegistryNotBootstrapped,
104
105    /// The persisted ABI ledger cannot be validated.
106    #[error("memory layout ledger is corrupt: {reason}")]
107    LedgerCorrupt {
108        /// Human-readable corruption reason.
109        reason: &'static str,
110    },
111}
112
113#[cfg(test)]
114thread_local! {
115    static PENDING_REGISTRATIONS: RefCell<Vec<PendingRegistration>> = const { RefCell::new(Vec::new()) };
116}
117
118static STATIC_DECLARATIONS: Mutex<Vec<PendingRegistration>> = Mutex::new(Vec::new());
119
120/// Declare a macro-owned stable-memory slot with an explicit ABI-stable key.
121#[doc(hidden)]
122pub fn declare_memory_slot_with_key(
123    id: u8,
124    crate_name: &str,
125    label: &str,
126    stable_key: &str,
127) -> Result<(), MemoryRegistryError> {
128    declare_memory_slot_with_key_metadata(id, crate_name, label, stable_key, None, None)
129}
130
131/// Declare a macro-owned stable-memory slot with optional schema metadata.
132#[doc(hidden)]
133pub fn declare_memory_slot_with_key_metadata(
134    id: u8,
135    crate_name: &str,
136    label: &str,
137    stable_key: &str,
138    schema_version: Option<u32>,
139    schema_fingerprint: Option<&str>,
140) -> Result<(), MemoryRegistryError> {
141    let registration = PendingRegistration::from_parts(
142        id,
143        crate_name,
144        label,
145        stable_key,
146        schema_version,
147        schema_fingerprint,
148    )?;
149
150    STATIC_DECLARATIONS
151        .lock()
152        .expect("static memory declaration queue poisoned")
153        .push(registration);
154
155    Ok(())
156}
157
158#[cfg(test)]
159pub fn defer_register_with_key(
160    id: u8,
161    crate_name: &str,
162    label: &str,
163    stable_key: &str,
164) -> Result<(), MemoryRegistryError> {
165    defer_register_with_key_metadata(id, crate_name, label, stable_key, None, None)
166}
167
168#[cfg(test)]
169pub fn defer_register_with_key_metadata(
170    id: u8,
171    crate_name: &str,
172    label: &str,
173    stable_key: &str,
174    schema_version: Option<u32>,
175    schema_fingerprint: Option<&str>,
176) -> Result<(), MemoryRegistryError> {
177    let registration = PendingRegistration::from_parts(
178        id,
179        crate_name,
180        label,
181        stable_key,
182        schema_version,
183        schema_fingerprint,
184    )?;
185
186    PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
187        regs.push(registration);
188    });
189
190    Ok(())
191}
192
193/// Snapshot macro-owned stable-memory declarations.
194#[must_use]
195pub(crate) fn static_declarations() -> Vec<PendingRegistration> {
196    STATIC_DECLARATIONS
197        .lock()
198        .expect("static memory declaration queue poisoned")
199        .clone()
200}
201
202#[cfg(test)]
203pub(crate) fn drain_pending_registrations() -> Vec<PendingRegistration> {
204    PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
205}
206
207#[cfg(test)]
208/// Clear registry and pending queues for isolated unit tests.
209pub fn reset_for_tests() {
210    reset_runtime_for_tests();
211    ledger::reset_for_tests();
212    super::runtime::registry::reset_initialized_for_tests();
213}
214
215#[cfg(test)]
216fn reset_runtime_for_tests() {
217    PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
218}
219
220pub(super) fn memory_registry_error_from_declaration_error(
221    err: DeclarationSnapshotError,
222    stable_key: &str,
223) -> MemoryRegistryError {
224    let (stable_key, reason) = if let DeclarationSnapshotError::Key(err) = err {
225        (err.stable_key, err.reason)
226    } else {
227        (stable_key.to_string(), "declaration rejected by ic-memory")
228    };
229
230    MemoryRegistryError::InvalidDeclaration { stable_key, reason }
231}
232
233///
234/// TESTS
235///
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn defers_valid_application_declaration() {
243        reset_for_tests();
244
245        defer_register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
246            .expect("register valid app slot");
247
248        let registrations = drain_pending_registrations();
249        assert_eq!(registrations.len(), 1);
250        assert_eq!(
251            registrations[0].declaration().stable_key().as_str(),
252            "app.crate_a.slot.v1"
253        );
254    }
255
256    #[test]
257    fn rejects_ic_memory_key_from_non_ic_memory_owner() {
258        reset_for_tests();
259
260        let err = defer_register_with_key(10, "canic-core", "slot", "ic_memory.future.v1")
261            .expect_err("only ic-memory may declare ic_memory keys");
262        assert!(matches!(
263            err,
264            MemoryRegistryError::RangeAuthorityViolation { .. }
265        ));
266    }
267}