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