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