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