1use crate::manager::MEMORY_MANAGER;
4use candid::CandidType;
5use canic_cdk::structures::{
6 BTreeMap as StableBTreeMap, DefaultMemoryImpl,
7 memory::{MemoryId, VirtualMemory},
8};
9use canic_macros::impl_storable_bounded;
10use canic_types::BoundedString256;
11use canic_utils::time::now_secs;
12use serde::{Deserialize, Serialize};
13use std::cell::RefCell;
14use thiserror::Error as ThisError;
15
16pub const MEMORY_REGISTRY_ID: u8 = 0;
20pub const MEMORY_RANGES_ID: u8 = 1;
21
22thread_local! {
27 static MEMORY_REGISTRY: RefCell<StableBTreeMap<u8, MemoryRegistryEntry, VirtualMemory<DefaultMemoryImpl>>> =
28 RefCell::new(StableBTreeMap::init(
29 MEMORY_MANAGER.with_borrow(|this| {
30 this.get(MemoryId::new(MEMORY_REGISTRY_ID))
31 }),
32 ));
33}
34
35thread_local! {
40 static MEMORY_RANGES: RefCell<StableBTreeMap<String, MemoryRange, VirtualMemory<DefaultMemoryImpl>>> =
41 RefCell::new(StableBTreeMap::init(
42 MEMORY_MANAGER.with_borrow(|mgr| {
43 mgr.get(MemoryId::new(MEMORY_RANGES_ID))
44 }),
45 ));
46}
47
48thread_local! {
58 static PENDING_REGISTRATIONS: RefCell<Vec<(u8, &'static str, &'static str)>> = const {
59 RefCell::new(Vec::new())
60 };
61}
62
63pub fn defer_register(id: u8, crate_name: &'static str, label: &'static str) {
65 PENDING_REGISTRATIONS.with(|q| {
66 q.borrow_mut().push((id, crate_name, label));
67 });
68}
69
70#[must_use]
73pub fn drain_pending_registrations() -> Vec<(u8, &'static str, &'static str)> {
74 PENDING_REGISTRATIONS.with(|q| q.borrow_mut().drain(..).collect())
75}
76
77thread_local! {
82 pub static PENDING_RANGES: RefCell<Vec<(&'static str, u8, u8)>> = const {
83 RefCell::new(Vec::new())
84 };
85}
86
87pub fn defer_reserve_range(crate_name: &'static str, start: u8, end: u8) {
89 PENDING_RANGES.with(|q| q.borrow_mut().push((crate_name, start, end)));
90}
91
92#[must_use]
95pub fn drain_pending_ranges() -> Vec<(&'static str, u8, u8)> {
96 PENDING_RANGES.with(|q| q.borrow_mut().drain(..).collect())
97}
98
99#[derive(Debug, ThisError)]
104pub enum MemoryRegistryError {
105 #[error("ID {0} is already registered with type {1}, tried to register type {2}")]
106 AlreadyRegistered(u8, String, String),
107
108 #[error("crate `{0}` already has a reserved range")]
109 DuplicateRange(String),
110
111 #[error("crate `{0}` provided invalid range {1}-{2} (start > end)")]
112 InvalidRange(String, u8, u8),
113
114 #[error("crate `{0}` attempted to register ID {1}, but it is outside its allowed ranges")]
115 OutOfRange(String, u8),
116
117 #[error("crate `{0}` range {1}-{2} overlaps with crate `{3}` range {4}-{5}")]
118 Overlap(String, u8, u8, String, u8, u8),
119
120 #[error("crate `{0}` has not reserved any memory range")]
121 NoRange(String),
122}
123
124#[derive(Clone, Debug, Deserialize, Serialize)]
128pub struct MemoryRange {
129 pub crate_key: BoundedString256,
130 pub start: u8,
131 pub end: u8,
132 pub created_at: u64,
133}
134
135impl MemoryRange {
136 #[must_use]
137 pub(crate) fn new(crate_key: &str, start: u8, end: u8) -> Self {
138 Self {
139 crate_key: BoundedString256::new(crate_key),
140 start,
141 end,
142 created_at: now_secs(),
143 }
144 }
145
146 #[must_use]
147 pub fn contains(&self, id: u8) -> bool {
148 (self.start..=self.end).contains(&id)
149 }
150}
151
152impl_storable_bounded!(MemoryRange, 320, false);
153
154#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
159pub struct MemoryRegistryEntry {
160 pub label: BoundedString256,
161 pub created_at: u64,
162}
163
164impl MemoryRegistryEntry {
165 #[must_use]
166 pub(crate) fn new(label: &str) -> Self {
167 Self {
168 label: BoundedString256::new(label),
169 created_at: now_secs(),
170 }
171 }
172}
173
174impl_storable_bounded!(MemoryRegistryEntry, 320, false);
175
176pub type MemoryRegistryView = Vec<(u8, MemoryRegistryEntry)>;
181
182pub struct MemoryRegistry;
187
188impl MemoryRegistry {
189 pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
196 let crate_key = crate_name.to_string();
197
198 let range = MEMORY_RANGES.with_borrow(|ranges| ranges.get(&crate_key));
200 match range {
201 None => {
202 return Err(MemoryRegistryError::NoRange(crate_key));
203 }
204 Some(r) if !r.contains(id) => {
205 return Err(MemoryRegistryError::OutOfRange(crate_key, id));
206 }
207 Some(_) => {
208 }
210 }
211
212 let existing = MEMORY_REGISTRY.with_borrow(|map| map.get(&id));
214 if let Some(existing) = existing {
215 if existing.label.as_ref() != label {
216 return Err(MemoryRegistryError::AlreadyRegistered(
217 id,
218 existing.label.to_string(),
219 label.to_string(),
220 ));
221 }
222
223 return Ok(());
225 }
226
227 MEMORY_REGISTRY.with_borrow_mut(|map| {
229 map.insert(id, MemoryRegistryEntry::new(label));
230 });
231
232 Ok(())
233 }
234
235 pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
239 if start > end {
240 return Err(MemoryRegistryError::InvalidRange(
241 crate_name.to_string(),
242 start,
243 end,
244 ));
245 }
246
247 let crate_key = crate_name.to_string();
248
249 let conflict = MEMORY_RANGES.with_borrow(|ranges| {
251 if ranges.contains_key(&crate_key) {
252 return Some(MemoryRegistryError::DuplicateRange(crate_key.clone()));
253 }
254
255 for entry in ranges.iter() {
256 let other_crate = entry.key();
257 let other_range = entry.value();
258
259 if !(end < other_range.start || start > other_range.end) {
260 return Some(MemoryRegistryError::Overlap(
261 crate_key.clone(),
262 start,
263 end,
264 other_crate.clone(),
265 other_range.start,
266 other_range.end,
267 ));
268 }
269 }
270
271 None
272 });
273
274 if let Some(err) = conflict {
275 return Err(err);
276 }
277
278 MEMORY_RANGES.with_borrow_mut(|ranges| {
280 let range = MemoryRange::new(crate_name, start, end);
281 ranges.insert(crate_name.to_string(), range);
282 });
283
284 Ok(())
285 }
286
287 #[must_use]
288 pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
289 MEMORY_REGISTRY.with_borrow(|map| map.get(&id))
290 }
291
292 #[must_use]
293 pub fn export() -> MemoryRegistryView {
294 MEMORY_REGISTRY.with_borrow(|map| {
295 map.iter()
296 .map(|entry| (*entry.key(), entry.value()))
297 .collect()
298 })
299 }
300
301 #[must_use]
302 pub fn export_ranges() -> Vec<(String, MemoryRange)> {
303 MEMORY_RANGES.with_borrow(|ranges| {
304 ranges
305 .iter()
306 .map(|e| (e.key().clone(), e.value()))
307 .collect()
308 })
309 }
310}
311
312#[cfg(test)]
313pub(crate) fn reset_for_tests() {
314 MEMORY_REGISTRY.with_borrow_mut(StableBTreeMap::clear);
315 MEMORY_RANGES.with_borrow_mut(StableBTreeMap::clear);
316 PENDING_REGISTRATIONS.with(|q| q.borrow_mut().clear());
317 PENDING_RANGES.with(|q| q.borrow_mut().clear());
318}
319
320#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn reserve_range_happy_path_and_reject_overlap() {
330 reset_for_tests();
331 MemoryRegistry::reserve_range("crate_a", 10, 20).unwrap();
332
333 let err = MemoryRegistry::reserve_range("crate_b", 15, 25).unwrap_err();
335 matches!(err, MemoryRegistryError::Overlap(_, _, _, _, _, _));
336
337 MemoryRegistry::reserve_range("crate_b", 30, 40).unwrap();
339
340 let ranges = MemoryRegistry::export_ranges();
341 assert_eq!(ranges.len(), 2);
342 }
343
344 #[test]
345 fn reserve_range_rejects_invalid_order() {
346 reset_for_tests();
347 let err = MemoryRegistry::reserve_range("crate_a", 5, 4).unwrap_err();
348 matches!(err, MemoryRegistryError::InvalidRange(_, _, _));
349 assert!(MemoryRegistry::export_ranges().is_empty());
350 }
351
352 #[test]
353 fn register_id_requires_range_and_checks_bounds() {
354 reset_for_tests();
355 MemoryRegistry::reserve_range("crate_a", 1, 3).unwrap();
356
357 let err = MemoryRegistry::register(5, "crate_a", "Foo").unwrap_err();
359 matches!(err, MemoryRegistryError::OutOfRange(_, _));
360
361 MemoryRegistry::register(2, "crate_a", "Foo").unwrap();
363
364 MemoryRegistry::register(2, "crate_a", "Foo").unwrap();
366
367 let err = MemoryRegistry::register(2, "crate_a", "Bar").unwrap_err();
369 matches!(err, MemoryRegistryError::AlreadyRegistered(_, _, _));
370
371 let view = MemoryRegistry::export();
372 assert_eq!(view.len(), 1);
373 assert_eq!(view[0].0, 2);
374 }
375
376 #[test]
377 fn pending_queues_drain_in_order() {
378 reset_for_tests();
379 defer_reserve_range("crate_a", 1, 2);
380 defer_reserve_range("crate_b", 3, 4);
381 defer_register(1, "crate_a", "A1");
382 defer_register(3, "crate_b", "B3");
383
384 let ranges = drain_pending_ranges();
385 assert_eq!(ranges, vec![("crate_a", 1, 2), ("crate_b", 3, 4)]);
386 let regs = drain_pending_registrations();
387 assert_eq!(regs, vec![(1, "crate_a", "A1"), (3, "crate_b", "B3")]);
388
389 assert!(drain_pending_ranges().is_empty());
391 assert!(drain_pending_registrations().is_empty());
392 }
393}