canic_memory/
registry.rs

1use crate::ThisError;
2use std::{cell::RefCell, collections::BTreeMap};
3
4///
5/// MemoryRange
6///
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct MemoryRange {
10    pub start: u8,
11    pub end: u8,
12}
13
14impl MemoryRange {
15    #[must_use]
16    pub const fn contains(&self, id: u8) -> bool {
17        id >= self.start && id <= self.end
18    }
19}
20
21///
22/// MemoryRegistryEntry
23///
24
25#[derive(Clone, Debug)]
26pub struct MemoryRegistryEntry {
27    pub crate_name: String,
28    pub label: String,
29}
30
31///
32/// MemoryRangeEntry
33///
34
35#[derive(Clone, Debug)]
36pub struct MemoryRangeEntry {
37    pub owner: String,
38    pub range: MemoryRange,
39}
40
41///
42/// MemoryRangeSnapshot
43///
44
45#[derive(Clone, Debug)]
46pub struct MemoryRangeSnapshot {
47    pub owner: String,
48    pub range: MemoryRange,
49    pub entries: Vec<(u8, MemoryRegistryEntry)>,
50}
51
52///
53/// MemoryRegistryError
54///
55
56#[derive(Debug, ThisError)]
57pub enum MemoryRegistryError {
58    #[error(
59        "memory range overlap: crate '{existing_crate}' [{existing_start}-{existing_end}]
60conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
61    )]
62    Overlap {
63        existing_crate: String,
64        existing_start: u8,
65        existing_end: u8,
66        new_crate: String,
67        new_start: u8,
68        new_end: u8,
69    },
70
71    #[error("memory range is invalid: start={start} end={end}")]
72    InvalidRange { start: u8, end: u8 },
73
74    #[error("memory id {0} is already registered; each memory id must be globally unique")]
75    DuplicateId(u8),
76
77    #[error("memory id {id} has no reserved range for crate '{crate_name}'")]
78    NoReservedRange { crate_name: String, id: u8 },
79
80    #[error(
81        "memory id {id} reserved to crate '{owner}' [{owner_start}-{owner_end}], not '{crate_name}'"
82    )]
83    IdOwnedByOther {
84        crate_name: String,
85        id: u8,
86        owner: String,
87        owner_start: u8,
88        owner_end: u8,
89    },
90
91    #[error("memory id {id} is outside reserved ranges for crate '{crate_name}'")]
92    IdOutOfRange { crate_name: String, id: u8 },
93}
94
95//
96// Internal global state (substrate-level, single-threaded)
97//
98
99thread_local! {
100    static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
101    static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
102
103    // Deferred registrations (used before init)
104    static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
105    static PENDING_REGISTRATIONS: RefCell<Vec<(u8, String, String)>> = const { RefCell::new(Vec::new()) };
106}
107
108///
109/// MemoryRegistry
110///
111/// Canonical substrate registry for stable memory IDs.
112///
113pub struct MemoryRegistry;
114
115impl MemoryRegistry {
116    /// Reserve a memory range for a crate.
117    pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
118        if start > end {
119            return Err(MemoryRegistryError::InvalidRange { start, end });
120        }
121
122        let range = MemoryRange { start, end };
123
124        RESERVED_RANGES.with_borrow(|ranges| {
125            for (existing_crate, existing_range) in ranges {
126                if ranges_overlap(*existing_range, range) {
127                    if existing_crate == crate_name
128                        && existing_range.start == start
129                        && existing_range.end == end
130                    {
131                        // Allow exact duplicate reservations for idempotent init.
132                        return Ok(());
133                    }
134                    return Err(MemoryRegistryError::Overlap {
135                        existing_crate: existing_crate.clone(),
136                        existing_start: existing_range.start,
137                        existing_end: existing_range.end,
138                        new_crate: crate_name.to_string(),
139                        new_start: start,
140                        new_end: end,
141                    });
142                }
143            }
144
145            Ok(())
146        })?;
147
148        RESERVED_RANGES.with_borrow_mut(|ranges| {
149            ranges.push((crate_name.to_string(), range));
150        });
151
152        Ok(())
153    }
154
155    /// Register a memory ID.
156    pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
157        validate_registration_range(crate_name, id)?;
158
159        REGISTRY.with_borrow(|reg| {
160            if reg.contains_key(&id) {
161                return Err(MemoryRegistryError::DuplicateId(id));
162            }
163            Ok(())
164        })?;
165
166        REGISTRY.with_borrow_mut(|reg| {
167            reg.insert(
168                id,
169                MemoryRegistryEntry {
170                    crate_name: crate_name.to_string(),
171                    label: label.to_string(),
172                },
173            );
174        });
175
176        Ok(())
177    }
178
179    /// Export all registered entries (canonical snapshot).
180    #[must_use]
181    pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
182        REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
183    }
184
185    /// Export all reserved ranges.
186    #[must_use]
187    pub fn export_ranges() -> Vec<(String, MemoryRange)> {
188        RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
189    }
190
191    /// Export all reserved ranges with explicit owners.
192    #[must_use]
193    pub fn export_range_entries() -> Vec<MemoryRangeEntry> {
194        RESERVED_RANGES.with_borrow(|ranges| {
195            ranges
196                .iter()
197                .map(|(owner, range)| MemoryRangeEntry {
198                    owner: owner.clone(),
199                    range: *range,
200                })
201                .collect()
202        })
203    }
204
205    /// Export registry entries grouped by reserved range.
206    #[must_use]
207    pub fn export_ids_by_range() -> Vec<MemoryRangeSnapshot> {
208        let mut ranges = RESERVED_RANGES.with_borrow(std::clone::Clone::clone);
209        let entries = REGISTRY.with_borrow(std::clone::Clone::clone);
210
211        ranges.sort_by_key(|(_, range)| range.start);
212
213        ranges
214            .into_iter()
215            .map(|(owner, range)| {
216                let entries = entries
217                    .iter()
218                    .filter(|(id, _)| range.contains(**id))
219                    .map(|(id, entry)| (*id, entry.clone()))
220                    .collect();
221
222                MemoryRangeSnapshot {
223                    owner,
224                    range,
225                    entries,
226                }
227            })
228            .collect()
229    }
230
231    /// Retrieve a single registry entry.
232    #[must_use]
233    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
234        REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
235    }
236}
237
238//
239// Deferred registration helpers (used before runtime init)
240//
241
242pub fn defer_reserve_range(crate_name: &str, start: u8, end: u8) {
243    // Queue range reservations for runtime init to apply deterministically.
244    PENDING_RANGES.with_borrow_mut(|ranges| {
245        ranges.push((crate_name.to_string(), start, end));
246    });
247}
248
249pub fn defer_register(id: u8, crate_name: &str, label: &str) {
250    // Queue ID registrations for runtime init to apply after ranges are reserved.
251    PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
252        regs.push((id, crate_name.to_string(), label.to_string()));
253    });
254}
255
256#[must_use]
257pub fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
258    PENDING_RANGES.with_borrow_mut(std::mem::take)
259}
260
261#[must_use]
262pub fn drain_pending_registrations() -> Vec<(u8, String, String)> {
263    PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
264}
265
266//
267// Test-only helpers
268//
269
270#[cfg(test)]
271pub fn reset_for_tests() {
272    RESERVED_RANGES.with_borrow_mut(Vec::clear);
273    REGISTRY.with_borrow_mut(BTreeMap::clear);
274    PENDING_RANGES.with_borrow_mut(Vec::clear);
275    PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
276}
277
278//
279// Internal helpers
280//
281
282const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
283    a.start <= b.end && b.start <= a.end
284}
285
286fn validate_registration_range(crate_name: &str, id: u8) -> Result<(), MemoryRegistryError> {
287    let mut has_range = false;
288    let mut owner_match = false;
289    let mut owner_for_id: Option<(String, MemoryRange)> = None;
290
291    RESERVED_RANGES.with_borrow(|ranges| {
292        for (owner, range) in ranges {
293            if owner == crate_name {
294                has_range = true;
295                if range.contains(id) {
296                    owner_match = true;
297                    break;
298                }
299            }
300
301            if owner_for_id.is_none() && range.contains(id) {
302                owner_for_id = Some((owner.clone(), *range));
303            }
304        }
305    });
306
307    if owner_match {
308        return Ok(());
309    }
310
311    if !has_range {
312        return Err(MemoryRegistryError::NoReservedRange {
313            crate_name: crate_name.to_string(),
314            id,
315        });
316    }
317
318    if let Some((owner, range)) = owner_for_id {
319        return Err(MemoryRegistryError::IdOwnedByOther {
320            crate_name: crate_name.to_string(),
321            id,
322            owner,
323            owner_start: range.start,
324            owner_end: range.end,
325        });
326    }
327
328    Err(MemoryRegistryError::IdOutOfRange {
329        crate_name: crate_name.to_string(),
330        id,
331    })
332}
333
334///
335/// TESTS
336///
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn allows_in_range() {
344        reset_for_tests();
345
346        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range");
347        MemoryRegistry::register(2, "crate_a", "slot").expect("register in range");
348    }
349
350    #[test]
351    fn rejects_unreserved() {
352        reset_for_tests();
353
354        let err = MemoryRegistry::register(2, "crate_a", "slot").expect_err("missing range");
355        assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
356    }
357
358    #[test]
359    fn rejects_other_owner() {
360        reset_for_tests();
361
362        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
363        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
364
365        let err = MemoryRegistry::register(2, "crate_b", "slot").expect_err("owned by other");
366        assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
367    }
368
369    #[test]
370    fn export_ids_by_range_groups_entries() {
371        reset_for_tests();
372
373        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
374        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
375        MemoryRegistry::register(1, "crate_a", "a1").expect("register a1");
376        MemoryRegistry::register(5, "crate_b", "b5").expect("register b5");
377
378        let snapshots = MemoryRegistry::export_ids_by_range();
379        assert_eq!(snapshots.len(), 2);
380        assert_eq!(snapshots[0].entries.len(), 1);
381        assert_eq!(snapshots[1].entries.len(), 1);
382    }
383}