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
11pub struct MemoryApi;
16
17#[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#[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 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 pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
55 let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
56 Ok(())
57 }
58
59 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 #[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 #[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 #[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 #[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
131fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
133 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
134}
135
136#[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}