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
29///
30/// RegisteredMemory
31///
32
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct RegisteredMemory {
35    pub id: u8,
36    pub owner: String,
37    pub range: MemoryRange,
38    pub label: String,
39}
40
41impl MemoryApi {
42    /// Bootstrap eager TLS, eager-init hooks, and the caller's initial reserved range.
43    pub fn bootstrap_registry(
44        crate_name: &'static str,
45        start: u8,
46        end: u8,
47    ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
48        MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)
49    }
50
51    /// Register one stable-memory ID and return its opened virtual memory handle.
52    ///
53    /// Call `bootstrap_registry(...)` first so the caller's owned range is reserved.
54    pub fn register_memory(
55        id: u8,
56        crate_name: &str,
57        label: &str,
58    ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
59        if let Some(entry) = MemoryRegistry::get(id)
60            && entry.crate_name == crate_name
61            && entry.label == label
62        {
63            return Ok(open_memory(id));
64        }
65
66        MemoryRegistry::register(id, crate_name, label)?;
67        Ok(open_memory(id))
68    }
69
70    /// Inspect who currently owns one memory id and whether it is registered.
71    #[must_use]
72    pub fn inspect_memory(id: u8) -> Option<MemoryInspection> {
73        let range = MemoryRegistry::export_range_entries()
74            .into_iter()
75            .find(|entry| entry.range.contains(id))?;
76        let label = MemoryRegistry::get(id).map(|entry| entry.label);
77
78        Some(MemoryInspection {
79            id,
80            owner: range.owner,
81            range: range.range,
82            label,
83        })
84    }
85
86    /// List every registered memory slot with owner/range/label context.
87    #[must_use]
88    pub fn registered_memories() -> Vec<RegisteredMemory> {
89        MemoryRegistry::export_ids_by_range()
90            .into_iter()
91            .flat_map(|snapshot| {
92                snapshot
93                    .entries
94                    .into_iter()
95                    .map(move |(id, entry)| RegisteredMemory {
96                        id,
97                        owner: snapshot.owner.clone(),
98                        range: snapshot.range,
99                        label: entry.label,
100                    })
101            })
102            .collect()
103    }
104
105    /// List all registered memory slots for one owner.
106    #[must_use]
107    pub fn registered_memories_for_owner(owner: &str) -> Vec<RegisteredMemory> {
108        Self::registered_memories()
109            .into_iter()
110            .filter(|entry| entry.owner == owner)
111            .collect()
112    }
113
114    /// Find one registered memory slot by owner and label.
115    #[must_use]
116    pub fn find_registered_memory(owner: &str, label: &str) -> Option<RegisteredMemory> {
117        Self::registered_memories()
118            .into_iter()
119            .find(|entry| entry.owner == owner && entry.label == label)
120    }
121}
122
123// Open a registered virtual memory slot through the shared manager.
124fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
125    MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
126}
127
128///
129/// TESTS
130///
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::registry::{MemoryRegistryError, reset_for_tests};
136
137    #[test]
138    fn register_memory_returns_opened_memory_for_reserved_slot() {
139        reset_for_tests();
140        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
141
142        let _memory = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
143    }
144
145    #[test]
146    fn register_memory_is_idempotent_for_same_entry() {
147        reset_for_tests();
148        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
149        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
150
151        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("second register succeeds");
152    }
153
154    #[test]
155    fn register_memory_rejects_unreserved_id() {
156        reset_for_tests();
157
158        let Err(err) = MemoryApi::register_memory(9, "crate_a", "slot") else {
159            panic!("unreserved slot must fail")
160        };
161        assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
162    }
163
164    #[test]
165    fn register_memory_preserves_duplicate_id_error_for_conflicts() {
166        reset_for_tests();
167        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
168        MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
169
170        let Err(err) = MemoryApi::register_memory(2, "crate_a", "other") else {
171            panic!("conflicting duplicate register must fail")
172        };
173        assert!(matches!(err, MemoryRegistryError::DuplicateId(2)));
174    }
175
176    #[test]
177    fn inspect_memory_returns_reserved_owner_without_label() {
178        reset_for_tests();
179        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
180
181        let inspection = MemoryApi::inspect_memory(2).expect("reserved slot should inspect");
182        assert_eq!(inspection.owner, "crate_a");
183        assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
184        assert_eq!(inspection.label, None);
185    }
186
187    #[test]
188    fn inspect_memory_returns_registered_label() {
189        reset_for_tests();
190        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
191        let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
192
193        let inspection = MemoryApi::inspect_memory(2).expect("registered slot should inspect");
194        assert_eq!(inspection.owner, "crate_a");
195        assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
196        assert_eq!(inspection.label.as_deref(), Some("slot"));
197    }
198
199    #[test]
200    fn inspect_memory_returns_none_for_unowned_id() {
201        reset_for_tests();
202        assert_eq!(MemoryApi::inspect_memory(9), None);
203    }
204
205    #[test]
206    fn registered_memories_lists_registered_slots_with_owner_context() {
207        reset_for_tests();
208        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
209        let _ = MemoryApi::bootstrap_registry("crate_b", 10, 12).expect("bootstrap registry");
210        let _ = MemoryApi::register_memory(2, "crate_a", "slot_a").expect("register memory");
211        let _ = MemoryApi::register_memory(11, "crate_b", "slot_b").expect("register memory");
212
213        let registrations = MemoryApi::registered_memories();
214        assert_eq!(registrations.len(), 2);
215        assert!(registrations.contains(&RegisteredMemory {
216            id: 2,
217            owner: "crate_a".to_string(),
218            range: MemoryRange { start: 1, end: 3 },
219            label: "slot_a".to_string(),
220        }));
221        assert!(registrations.contains(&RegisteredMemory {
222            id: 11,
223            owner: "crate_b".to_string(),
224            range: MemoryRange { start: 10, end: 12 },
225            label: "slot_b".to_string(),
226        }));
227    }
228
229    #[test]
230    fn registered_memories_for_owner_filters_to_owner() {
231        reset_for_tests();
232        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
233        let _ = MemoryApi::bootstrap_registry("crate_b", 10, 12).expect("bootstrap registry");
234        let _ = MemoryApi::register_memory(2, "crate_a", "slot_a").expect("register memory");
235        let _ = MemoryApi::register_memory(11, "crate_b", "slot_b").expect("register memory");
236
237        let registrations = MemoryApi::registered_memories_for_owner("crate_a");
238        assert_eq!(
239            registrations,
240            vec![RegisteredMemory {
241                id: 2,
242                owner: "crate_a".to_string(),
243                range: MemoryRange { start: 1, end: 3 },
244                label: "slot_a".to_string(),
245            }]
246        );
247    }
248
249    #[test]
250    fn find_registered_memory_returns_match_for_owner_and_label() {
251        reset_for_tests();
252        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
253        let _ = MemoryApi::register_memory(2, "crate_a", "slot_a").expect("register memory");
254
255        let registration =
256            MemoryApi::find_registered_memory("crate_a", "slot_a").expect("slot should exist");
257        assert_eq!(
258            registration,
259            RegisteredMemory {
260                id: 2,
261                owner: "crate_a".to_string(),
262                range: MemoryRange { start: 1, end: 3 },
263                label: "slot_a".to_string(),
264            }
265        );
266    }
267
268    #[test]
269    fn find_registered_memory_returns_none_when_missing() {
270        reset_for_tests();
271        let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
272        assert_eq!(MemoryApi::find_registered_memory("crate_a", "slot_a"), None);
273    }
274}