Skip to main content

canic_memory/api/
mod.rs

1use crate::{
2    cdk::structures::{
3        DefaultMemoryImpl,
4        memory::{MemoryId, VirtualMemory},
5    },
6    manager::MEMORY_MANAGER,
7    registry::{
8        MemoryRange, MemoryRegistry, MemoryRegistryError, defer_register, defer_register_with_key,
9    },
10    runtime::{MemoryRuntimeApi, registry::MemoryRegistryRuntime},
11};
12
13///
14/// MemoryApi
15///
16/// Supported facade for memory bootstrap, dynamic slot registration, and
17/// registry inspection.
18
19pub struct MemoryApi;
20
21///
22/// MemoryInspection
23///
24/// Read-only description of the owner range for one memory ID.
25
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct MemoryInspection {
28    /// Stable-memory ID being inspected.
29    pub id: u8,
30    /// Crate name that reserved the range containing `id`.
31    pub owner: String,
32    /// Reserved range containing `id`.
33    pub range: MemoryRange,
34    /// Registered slot label for `id`, when the ID has already been registered.
35    pub label: Option<String>,
36    /// ABI-stable key for `id`, when the ID has already been registered.
37    pub stable_key: Option<String>,
38}
39
40///
41/// RegisteredMemory
42///
43/// Read-only description of one registered stable-memory slot.
44
45#[derive(Clone, Debug, Eq, PartialEq)]
46pub struct RegisteredMemory {
47    /// Registered stable-memory ID.
48    pub id: u8,
49    /// Crate name that owns the slot's reserved range.
50    pub owner: String,
51    /// Reserved range containing `id`.
52    pub range: MemoryRange,
53    /// Human-readable slot label supplied by the registering crate.
54    pub label: String,
55    /// ABI-stable key that owns this memory ID permanently.
56    pub stable_key: String,
57}
58
59///
60/// LedgerSnapshot
61///
62/// Read-only snapshot of the persisted ABI ledger.
63
64#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct LedgerSnapshot {
66    /// Historical owner ranges recorded by the persisted ABI ledger.
67    pub ranges: Vec<(String, MemoryRange)>,
68    /// Historical memory ID records recorded by the persisted ABI ledger.
69    pub entries: Vec<(u8, crate::registry::MemoryRegistryEntry)>,
70}
71
72impl MemoryApi {
73    /// Bootstrap eager TLS, eager-init hooks, and the caller's initial reserved range.
74    pub fn bootstrap_owner_range(
75        crate_name: &'static str,
76        start: u8,
77        end: u8,
78    ) -> Result<(), MemoryRegistryError> {
79        let _ = MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)?;
80        Ok(())
81    }
82
83    /// Bootstrap eager TLS, eager-init hooks, and flush deferred registry state
84    /// without reserving a new owner range.
85    pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
86        let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
87        Ok(())
88    }
89
90    /// Declare one legacy-key stable-memory ID for bootstrap validation.
91    ///
92    /// This queues metadata only. It does not open the underlying virtual memory.
93    pub fn declare(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
94        if MemoryRegistryRuntime::is_initialized() {
95            return Err(MemoryRegistryError::RegistrationAfterBootstrap {
96                ranges: 0,
97                registrations: 1,
98            });
99        }
100
101        defer_register(id, crate_name, label)
102    }
103
104    /// Declare one explicit-key stable-memory ID for bootstrap validation.
105    ///
106    /// This queues metadata only. It does not open the underlying virtual memory.
107    pub fn declare_with_key(
108        id: u8,
109        crate_name: &str,
110        label: &str,
111        stable_key: &str,
112    ) -> Result<(), MemoryRegistryError> {
113        if MemoryRegistryRuntime::is_initialized() {
114            return Err(MemoryRegistryError::RegistrationAfterBootstrap {
115                ranges: 0,
116                registrations: 1,
117            });
118        }
119
120        defer_register_with_key(id, crate_name, label, stable_key)
121    }
122
123    /// Open one already-validated stable-memory ID and return its virtual memory handle.
124    ///
125    /// The ID must have been declared before bootstrap and accepted by the
126    /// sealed runtime declaration snapshot. This is not a dynamic allocation API.
127    pub fn register(
128        id: u8,
129        crate_name: &str,
130        label: &str,
131    ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
132        if !MemoryRegistryRuntime::is_initialized() {
133            return Err(MemoryRegistryError::RegistryNotBootstrapped);
134        }
135
136        if let Some(entry) = MemoryRegistry::get(id)
137            && entry.crate_name == crate_name
138            && entry.label == label
139        {
140            return Ok(open_memory(id));
141        }
142
143        Err(MemoryRegistryError::RegistrationAfterBootstrap {
144            ranges: 0,
145            registrations: 1,
146        })
147    }
148
149    /// Open one already-validated stable-memory ID using its explicit ABI-stable key.
150    pub fn register_with_key(
151        id: u8,
152        _crate_name: &str,
153        _label: &str,
154        stable_key: &str,
155    ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
156        if !MemoryRegistryRuntime::is_initialized() {
157            return Err(MemoryRegistryError::RegistryNotBootstrapped);
158        }
159
160        if let Some(entry) = MemoryRegistry::get(id)
161            && entry.stable_key == stable_key
162        {
163            return Ok(open_memory(id));
164        }
165
166        Err(MemoryRegistryError::RegistrationAfterBootstrap {
167            ranges: 0,
168            registrations: 1,
169        })
170    }
171
172    /// Inspect who currently owns one memory id and whether it is registered.
173    #[must_use]
174    pub fn inspect(id: u8) -> Option<MemoryInspection> {
175        let range = MemoryRegistry::export_range_entries()
176            .into_iter()
177            .find(|entry| entry.range.contains(id))?;
178        let entry = MemoryRegistry::get(id);
179        let label = entry.as_ref().map(|entry| entry.label.clone());
180        let stable_key = entry.map(|entry| entry.stable_key);
181
182        Some(MemoryInspection {
183            id,
184            owner: range.owner,
185            range: range.range,
186            label,
187            stable_key,
188        })
189    }
190
191    /// List every registered memory slot with owner/range/label context.
192    #[must_use]
193    pub fn registered() -> Vec<RegisteredMemory> {
194        MemoryRegistry::export_ids_by_range()
195            .into_iter()
196            .flat_map(|snapshot| {
197                snapshot
198                    .entries
199                    .into_iter()
200                    .map(move |(id, entry)| RegisteredMemory {
201                        id,
202                        owner: snapshot.owner.clone(),
203                        range: snapshot.range,
204                        label: entry.label,
205                        stable_key: entry.stable_key,
206                    })
207            })
208            .collect()
209    }
210
211    /// List all registered memory slots for one owner.
212    #[must_use]
213    pub fn registered_for_owner(owner: &str) -> Vec<RegisteredMemory> {
214        Self::registered()
215            .into_iter()
216            .filter(|entry| entry.owner == owner)
217            .collect()
218    }
219
220    /// Find one registered memory slot by owner and label.
221    #[must_use]
222    pub fn find(owner: &str, label: &str) -> Option<RegisteredMemory> {
223        Self::registered()
224            .into_iter()
225            .find(|entry| entry.owner == owner && entry.label == label)
226    }
227
228    /// Read the persisted ABI ledger without relying on current registry reconstruction.
229    pub fn ledger_snapshot() -> Result<LedgerSnapshot, MemoryRegistryError> {
230        Ok(LedgerSnapshot {
231            ranges: MemoryRegistry::try_export_historical_ranges()?,
232            entries: MemoryRegistry::try_export_historical()?,
233        })
234    }
235}
236
237// Open a registered virtual memory slot through the shared manager.
238fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
239    MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
240}
241
242///
243/// TESTS
244///
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::registry::{
250        MemoryRegistryError, defer_register, defer_reserve_range, reset_for_tests,
251    };
252
253    #[test]
254    fn register_memory_opens_validated_memory_for_reserved_slot() {
255        reset_for_tests();
256        defer_reserve_range("crate_a", 100, 102).expect("defer range");
257        defer_register(101, "crate_a", "slot").expect("defer register");
258        MemoryApi::bootstrap_pending().expect("bootstrap registry");
259
260        let _memory = MemoryApi::register(101, "crate_a", "slot").expect("open memory");
261    }
262
263    #[test]
264    fn register_memory_is_idempotent_for_same_entry() {
265        reset_for_tests();
266        defer_reserve_range("crate_a", 100, 102).expect("defer range");
267        defer_register(101, "crate_a", "slot").expect("defer register");
268        MemoryApi::bootstrap_pending().expect("bootstrap registry");
269        let _ = MemoryApi::register(101, "crate_a", "slot").expect("first open succeeds");
270
271        let _ = MemoryApi::register(101, "crate_a", "slot").expect("second open succeeds");
272    }
273
274    #[test]
275    fn register_with_key_opens_validated_explicit_key() {
276        reset_for_tests();
277        defer_reserve_range("crate_a", 100, 102).expect("defer range");
278        MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
279            .expect("defer register");
280        MemoryApi::bootstrap_pending().expect("bootstrap registry");
281
282        let _memory = MemoryApi::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
283            .expect("open memory");
284    }
285
286    #[test]
287    fn declare_memory_does_not_open_before_bootstrap() {
288        reset_for_tests();
289
290        MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
291            .expect("declare memory");
292
293        assert!(MemoryRegistry::get(101).is_none());
294    }
295
296    #[test]
297    fn declare_memory_rejects_after_bootstrap_seal() {
298        reset_for_tests();
299        MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
300
301        let err = MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
302            .expect_err("late declaration should fail");
303        assert!(matches!(
304            err,
305            MemoryRegistryError::RegistrationAfterBootstrap {
306                ranges: 0,
307                registrations: 1,
308            }
309        ));
310    }
311
312    #[test]
313    fn register_memory_rejects_before_bootstrap_validation() {
314        reset_for_tests();
315
316        let Err(err) = MemoryApi::register(100, "crate_a", "slot") else {
317            panic!("opening before bootstrap must fail")
318        };
319        assert!(matches!(err, MemoryRegistryError::RegistryNotBootstrapped));
320    }
321
322    #[test]
323    fn register_memory_rejects_new_claim_after_bootstrap_seal() {
324        reset_for_tests();
325        MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
326
327        let Err(err) = MemoryApi::register(101, "crate_a", "slot") else {
328            panic!("new registration after bootstrap must fail")
329        };
330        assert!(matches!(
331            err,
332            MemoryRegistryError::RegistrationAfterBootstrap {
333                ranges: 0,
334                registrations: 1,
335            }
336        ));
337    }
338
339    #[test]
340    fn bootstrap_pending_flushes_deferred_state() {
341        reset_for_tests();
342        defer_reserve_range("crate_a", 100, 102).expect("defer range");
343        defer_register(101, "crate_a", "slot").expect("defer register");
344
345        MemoryApi::bootstrap_pending().expect("bootstrap pending");
346
347        assert!(MemoryRegistry::export_ranges().contains(&(
348            "crate_a".to_string(),
349            MemoryRange {
350                start: 100,
351                end: 102
352            }
353        )));
354        let entries = MemoryRegistry::export();
355        assert!(entries.iter().any(|(id, entry)| {
356            *id == 101 && entry.crate_name == "crate_a" && entry.label == "slot"
357        }));
358    }
359
360    #[test]
361    fn inspect_memory_returns_reserved_owner_without_label() {
362        reset_for_tests();
363        MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
364
365        let inspection = MemoryApi::inspect(101).expect("reserved slot should inspect");
366        assert_eq!(inspection.owner, "crate_a");
367        assert_eq!(
368            inspection.range,
369            MemoryRange {
370                start: 100,
371                end: 102
372            }
373        );
374        assert_eq!(inspection.label, None);
375    }
376
377    #[test]
378    fn inspect_memory_returns_registered_label() {
379        reset_for_tests();
380        defer_reserve_range("crate_a", 100, 102).expect("defer range");
381        defer_register(101, "crate_a", "slot").expect("defer register");
382        MemoryApi::bootstrap_pending().expect("bootstrap registry");
383
384        let inspection = MemoryApi::inspect(101).expect("registered slot should inspect");
385        assert_eq!(inspection.owner, "crate_a");
386        assert_eq!(
387            inspection.range,
388            MemoryRange {
389                start: 100,
390                end: 102
391            }
392        );
393        assert_eq!(inspection.label.as_deref(), Some("slot"));
394        assert_eq!(
395            inspection.stable_key.as_deref(),
396            Some("legacy.crate_a.slot.v1")
397        );
398    }
399
400    #[test]
401    fn inspect_memory_returns_none_for_unowned_id() {
402        reset_for_tests();
403        assert_eq!(MemoryApi::inspect(99), None);
404    }
405
406    #[test]
407    fn registered_memories_lists_registered_slots_with_owner_context() {
408        reset_for_tests();
409        defer_reserve_range("crate_a", 100, 102).expect("defer range A");
410        defer_reserve_range("crate_b", 110, 112).expect("defer range B");
411        defer_register(101, "crate_a", "slot_a").expect("defer register A");
412        defer_register(111, "crate_b", "slot_b").expect("defer register B");
413        MemoryApi::bootstrap_pending().expect("bootstrap registry");
414
415        let registrations = MemoryApi::registered();
416        assert_eq!(registrations.len(), 3);
417        assert!(registrations.contains(&RegisteredMemory {
418            id: 101,
419            owner: "crate_a".to_string(),
420            range: MemoryRange {
421                start: 100,
422                end: 102
423            },
424            label: "slot_a".to_string(),
425            stable_key: "legacy.crate_a.slot_a.v1".to_string(),
426        }));
427        assert!(registrations.contains(&RegisteredMemory {
428            id: 111,
429            owner: "crate_b".to_string(),
430            range: MemoryRange {
431                start: 110,
432                end: 112
433            },
434            label: "slot_b".to_string(),
435            stable_key: "legacy.crate_b.slot_b.v1".to_string(),
436        }));
437    }
438
439    #[test]
440    fn registered_memories_for_owner_filters_to_owner() {
441        reset_for_tests();
442        defer_reserve_range("crate_a", 100, 102).expect("defer range A");
443        defer_reserve_range("crate_b", 110, 112).expect("defer range B");
444        defer_register(101, "crate_a", "slot_a").expect("defer register A");
445        defer_register(111, "crate_b", "slot_b").expect("defer register B");
446        MemoryApi::bootstrap_pending().expect("bootstrap registry");
447
448        let registrations = MemoryApi::registered_for_owner("crate_a");
449        assert_eq!(
450            registrations,
451            vec![RegisteredMemory {
452                id: 101,
453                owner: "crate_a".to_string(),
454                range: MemoryRange {
455                    start: 100,
456                    end: 102
457                },
458                label: "slot_a".to_string(),
459                stable_key: "legacy.crate_a.slot_a.v1".to_string(),
460            }]
461        );
462    }
463
464    #[test]
465    fn find_registered_memory_returns_match_for_owner_and_label() {
466        reset_for_tests();
467        defer_reserve_range("crate_a", 100, 102).expect("defer range");
468        defer_register(101, "crate_a", "slot_a").expect("defer register");
469        MemoryApi::bootstrap_pending().expect("bootstrap registry");
470
471        let registration = MemoryApi::find("crate_a", "slot_a").expect("slot should exist");
472        assert_eq!(
473            registration,
474            RegisteredMemory {
475                id: 101,
476                owner: "crate_a".to_string(),
477                range: MemoryRange {
478                    start: 100,
479                    end: 102
480                },
481                label: "slot_a".to_string(),
482                stable_key: "legacy.crate_a.slot_a.v1".to_string(),
483            }
484        );
485    }
486
487    #[test]
488    fn find_registered_memory_returns_none_when_missing() {
489        reset_for_tests();
490        MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
491        assert_eq!(MemoryApi::find("crate_a", "slot_a"), None);
492    }
493
494    #[test]
495    fn ledger_snapshot_reads_historical_records() {
496        reset_for_tests();
497        defer_reserve_range("crate_a", 100, 102).expect("defer range");
498        defer_register(101, "crate_a", "slot").expect("defer register");
499        MemoryApi::bootstrap_pending().expect("bootstrap registry");
500
501        let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
502        assert!(snapshot.ranges.iter().any(|(owner, range)| {
503            owner == "crate_a"
504                && *range
505                    == MemoryRange {
506                        start: 100,
507                        end: 102,
508                    }
509        }));
510        assert!(snapshot.entries.iter().any(|(id, entry)| {
511            *id == 101
512                && entry.crate_name == "crate_a"
513                && entry.label == "slot"
514                && entry.stable_key == "legacy.crate_a.slot.v1"
515        }));
516    }
517}