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;
18
19#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct MemoryInspection {
26 pub id: u8,
28 pub owner: String,
30 pub range: MemoryRange,
32 pub label: Option<String>,
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct RegisteredMemory {
43 pub id: u8,
45 pub owner: String,
47 pub range: MemoryRange,
49 pub label: String,
51}
52
53impl MemoryApi {
54 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 pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
67 let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
68 Ok(())
69 }
70
71 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 #[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 #[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 #[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 #[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
143fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
145 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
146}
147
148#[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}