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::{MemoryRange, MemoryRegistry, MemoryRegistryError},
8    runtime::{MemoryRuntimeApi, registry::MemoryRegistryInitSummary},
9};
10
11///
12/// MemoryApi
13///
14
15pub struct MemoryApi;
16
17///
18/// MemoryInspection
19///
20
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct MemoryInspection {
23    pub id: u8,
24    pub owner: String,
25    pub range: MemoryRange,
26    pub label: Option<String>,
27}
28
29impl MemoryApi {
30    /// Bootstrap eager TLS, eager-init hooks, and the caller's initial reserved range.
31    pub fn bootstrap_registry(
32        crate_name: &'static str,
33        start: u8,
34        end: u8,
35    ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
36        MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)
37    }
38
39    /// Register one stable-memory ID and return its opened virtual memory handle.
40    ///
41    /// Call `bootstrap_registry(...)` first so the caller's owned range is reserved.
42    pub fn register_memory(
43        id: u8,
44        crate_name: &str,
45        label: &str,
46    ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
47        if let Some(entry) = MemoryRegistry::get(id)
48            && entry.crate_name == crate_name
49            && entry.label == label
50        {
51            return Ok(open_memory(id));
52        }
53
54        MemoryRegistry::register(id, crate_name, label)?;
55        Ok(open_memory(id))
56    }
57
58    /// Inspect who currently owns one memory id and whether it is registered.
59    #[must_use]
60    pub fn inspect_memory(id: u8) -> Option<MemoryInspection> {
61        let range = MemoryRegistry::export_range_entries()
62            .into_iter()
63            .find(|entry| entry.range.contains(id))?;
64        let label = MemoryRegistry::get(id).map(|entry| entry.label);
65
66        Some(MemoryInspection {
67            id,
68            owner: range.owner,
69            range: range.range,
70            label,
71        })
72    }
73}
74
75// Open a registered virtual memory slot through the shared manager.
76fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
77    MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
78}
79
80///
81/// TESTS
82///
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::registry::{MemoryRegistryError, reset_for_tests};
88
89    #[test]
90    fn register_memory_returns_opened_memory_for_reserved_slot() {
91        reset_for_tests();
92        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
93
94        let _memory = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
95    }
96
97    #[test]
98    fn register_memory_is_idempotent_for_same_entry() {
99        reset_for_tests();
100        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
101        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
102
103        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("second register succeeds");
104    }
105
106    #[test]
107    fn register_memory_rejects_unreserved_id() {
108        reset_for_tests();
109
110        let Err(err) = MemoryApi::register_memory(9, "crate_a", "slot") else {
111            panic!("unreserved slot must fail")
112        };
113        assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
114    }
115
116    #[test]
117    fn register_memory_preserves_duplicate_id_error_for_conflicts() {
118        reset_for_tests();
119        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
120        MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
121
122        let Err(err) = MemoryApi::register_memory(2, "crate_a", "other") else {
123            panic!("conflicting duplicate register must fail")
124        };
125        assert!(matches!(err, MemoryRegistryError::DuplicateId(2)));
126    }
127
128    #[test]
129    fn inspect_memory_returns_reserved_owner_without_label() {
130        reset_for_tests();
131        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
132
133        let inspection = MemoryApi::inspect_memory(2).expect("reserved slot should inspect");
134        assert_eq!(inspection.owner, "crate_a");
135        assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
136        assert_eq!(inspection.label, None);
137    }
138
139    #[test]
140    fn inspect_memory_returns_registered_label() {
141        reset_for_tests();
142        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
143        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
144
145        let inspection = MemoryApi::inspect_memory(2).expect("registered slot should inspect");
146        assert_eq!(inspection.owner, "crate_a");
147        assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
148        assert_eq!(inspection.label.as_deref(), Some("slot"));
149    }
150
151    #[test]
152    fn inspect_memory_returns_none_for_unowned_id() {
153        reset_for_tests();
154        assert_eq!(MemoryApi::inspect_memory(9), None);
155    }
156}