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