Skip to main content

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    #[error(
95        "memory id {id} is reserved for stable-structures internals and cannot be used by application code"
96    )]
97    ReservedInternalId { id: u8 },
98}
99
100//
101// Internal global state (substrate-level, single-threaded)
102//
103
104thread_local! {
105    static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
106    static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
107
108    // Deferred registrations (used before init)
109    static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
110    static PENDING_REGISTRATIONS: RefCell<Vec<(u8, String, String)>> = const { RefCell::new(Vec::new()) };
111}
112
113///
114/// MemoryRegistry
115///
116/// Canonical substrate registry for stable memory IDs.
117///
118pub struct MemoryRegistry;
119
120impl MemoryRegistry {
121    /// Reserve a memory range for a crate.
122    pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
123        if start > end {
124            return Err(MemoryRegistryError::InvalidRange { start, end });
125        }
126        validate_range_excludes_reserved_internal_id(start, end)?;
127
128        let range = MemoryRange { start, end };
129
130        RESERVED_RANGES.with_borrow(|ranges| {
131            for (existing_crate, existing_range) in ranges {
132                if ranges_overlap(*existing_range, range) {
133                    if existing_crate == crate_name
134                        && existing_range.start == start
135                        && existing_range.end == end
136                    {
137                        // Allow exact duplicate reservations for idempotent init.
138                        return Ok(());
139                    }
140                    return Err(MemoryRegistryError::Overlap {
141                        existing_crate: existing_crate.clone(),
142                        existing_start: existing_range.start,
143                        existing_end: existing_range.end,
144                        new_crate: crate_name.to_string(),
145                        new_start: start,
146                        new_end: end,
147                    });
148                }
149            }
150
151            Ok(())
152        })?;
153
154        RESERVED_RANGES.with_borrow_mut(|ranges| {
155            ranges.push((crate_name.to_string(), range));
156        });
157
158        Ok(())
159    }
160
161    /// Register a memory ID.
162    pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
163        validate_non_internal_id(id)?;
164        validate_registration_range(crate_name, id)?;
165
166        REGISTRY.with_borrow(|reg| {
167            if reg.contains_key(&id) {
168                return Err(MemoryRegistryError::DuplicateId(id));
169            }
170            Ok(())
171        })?;
172
173        REGISTRY.with_borrow_mut(|reg| {
174            reg.insert(
175                id,
176                MemoryRegistryEntry {
177                    crate_name: crate_name.to_string(),
178                    label: label.to_string(),
179                },
180            );
181        });
182
183        Ok(())
184    }
185
186    /// Export all registered entries (canonical snapshot).
187    #[must_use]
188    pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
189        REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
190    }
191
192    /// Export all reserved ranges.
193    #[must_use]
194    pub fn export_ranges() -> Vec<(String, MemoryRange)> {
195        RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
196    }
197
198    /// Export all reserved ranges with explicit owners.
199    #[must_use]
200    pub fn export_range_entries() -> Vec<MemoryRangeEntry> {
201        RESERVED_RANGES.with_borrow(|ranges| {
202            ranges
203                .iter()
204                .map(|(owner, range)| MemoryRangeEntry {
205                    owner: owner.clone(),
206                    range: *range,
207                })
208                .collect()
209        })
210    }
211
212    /// Export registry entries grouped by reserved range.
213    #[must_use]
214    pub fn export_ids_by_range() -> Vec<MemoryRangeSnapshot> {
215        let mut ranges = RESERVED_RANGES.with_borrow(std::clone::Clone::clone);
216        let entries = REGISTRY.with_borrow(std::clone::Clone::clone);
217
218        ranges.sort_by_key(|(_, range)| range.start);
219
220        ranges
221            .into_iter()
222            .map(|(owner, range)| {
223                let entries = entries
224                    .iter()
225                    .filter(|(id, _)| range.contains(**id))
226                    .map(|(id, entry)| (*id, entry.clone()))
227                    .collect();
228
229                MemoryRangeSnapshot {
230                    owner,
231                    range,
232                    entries,
233                }
234            })
235            .collect()
236    }
237
238    /// Retrieve a single registry entry.
239    #[must_use]
240    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
241        REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
242    }
243}
244
245//
246// Deferred registration helpers (used before runtime init)
247//
248
249pub fn defer_reserve_range(
250    crate_name: &str,
251    start: u8,
252    end: u8,
253) -> Result<(), MemoryRegistryError> {
254    if start > end {
255        return Err(MemoryRegistryError::InvalidRange { start, end });
256    }
257    validate_range_excludes_reserved_internal_id(start, end)?;
258
259    // Queue range reservations for runtime init to apply deterministically.
260    PENDING_RANGES.with_borrow_mut(|ranges| {
261        ranges.push((crate_name.to_string(), start, end));
262    });
263
264    Ok(())
265}
266
267pub fn defer_register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
268    validate_non_internal_id(id)?;
269
270    // Queue ID registrations for runtime init to apply after ranges are reserved.
271    PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
272        regs.push((id, crate_name.to_string(), label.to_string()));
273    });
274
275    Ok(())
276}
277
278#[must_use]
279pub fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
280    PENDING_RANGES.with_borrow_mut(std::mem::take)
281}
282
283#[must_use]
284pub fn drain_pending_registrations() -> Vec<(u8, String, String)> {
285    PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
286}
287
288//
289// Test-only helpers
290//
291
292#[cfg(test)]
293pub fn reset_for_tests() {
294    RESERVED_RANGES.with_borrow_mut(Vec::clear);
295    REGISTRY.with_borrow_mut(BTreeMap::clear);
296    PENDING_RANGES.with_borrow_mut(Vec::clear);
297    PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
298}
299
300//
301// Internal helpers
302//
303
304const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
305    a.start <= b.end && b.start <= a.end
306}
307
308const INTERNAL_RESERVED_MEMORY_ID: u8 = u8::MAX;
309
310const fn validate_non_internal_id(id: u8) -> Result<(), MemoryRegistryError> {
311    if id == INTERNAL_RESERVED_MEMORY_ID {
312        return Err(MemoryRegistryError::ReservedInternalId { id });
313    }
314    Ok(())
315}
316
317const fn validate_range_excludes_reserved_internal_id(
318    _start: u8,
319    end: u8,
320) -> Result<(), MemoryRegistryError> {
321    if end == INTERNAL_RESERVED_MEMORY_ID {
322        return Err(MemoryRegistryError::ReservedInternalId {
323            id: INTERNAL_RESERVED_MEMORY_ID,
324        });
325    }
326    Ok(())
327}
328
329fn validate_registration_range(crate_name: &str, id: u8) -> Result<(), MemoryRegistryError> {
330    let mut has_range = false;
331    let mut owner_match = false;
332    let mut owner_for_id: Option<(String, MemoryRange)> = None;
333
334    RESERVED_RANGES.with_borrow(|ranges| {
335        for (owner, range) in ranges {
336            if owner == crate_name {
337                has_range = true;
338                if range.contains(id) {
339                    owner_match = true;
340                    break;
341                }
342            }
343
344            if owner_for_id.is_none() && range.contains(id) {
345                owner_for_id = Some((owner.clone(), *range));
346            }
347        }
348    });
349
350    if owner_match {
351        return Ok(());
352    }
353
354    if !has_range {
355        return Err(MemoryRegistryError::NoReservedRange {
356            crate_name: crate_name.to_string(),
357            id,
358        });
359    }
360
361    if let Some((owner, range)) = owner_for_id {
362        return Err(MemoryRegistryError::IdOwnedByOther {
363            crate_name: crate_name.to_string(),
364            id,
365            owner,
366            owner_start: range.start,
367            owner_end: range.end,
368        });
369    }
370
371    Err(MemoryRegistryError::IdOutOfRange {
372        crate_name: crate_name.to_string(),
373        id,
374    })
375}
376
377///
378/// TESTS
379///
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    #[test]
386    fn allows_in_range() {
387        reset_for_tests();
388
389        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range");
390        MemoryRegistry::register(2, "crate_a", "slot").expect("register in range");
391    }
392
393    #[test]
394    fn rejects_unreserved() {
395        reset_for_tests();
396
397        let err = MemoryRegistry::register(2, "crate_a", "slot").expect_err("missing range");
398        assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
399    }
400
401    #[test]
402    fn rejects_other_owner() {
403        reset_for_tests();
404
405        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
406        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
407
408        let err = MemoryRegistry::register(2, "crate_b", "slot").expect_err("owned by other");
409        assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
410    }
411
412    #[test]
413    fn export_ids_by_range_groups_entries() {
414        reset_for_tests();
415
416        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
417        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
418        MemoryRegistry::register(1, "crate_a", "a1").expect("register a1");
419        MemoryRegistry::register(5, "crate_b", "b5").expect("register b5");
420
421        let snapshots = MemoryRegistry::export_ids_by_range();
422        assert_eq!(snapshots.len(), 2);
423        assert_eq!(snapshots[0].entries.len(), 1);
424        assert_eq!(snapshots[1].entries.len(), 1);
425    }
426
427    #[test]
428    fn rejects_internal_reserved_id_on_register() {
429        reset_for_tests();
430
431        MemoryRegistry::reserve_range("crate_a", 1, 254).expect("reserve range");
432        let err = MemoryRegistry::register(u8::MAX, "crate_a", "slot")
433            .expect_err("reserved id should be rejected");
434        assert!(matches!(
435            err,
436            MemoryRegistryError::ReservedInternalId { .. }
437        ));
438    }
439
440    #[test]
441    fn rejects_internal_reserved_id_on_range_reservation() {
442        reset_for_tests();
443
444        let err = MemoryRegistry::reserve_range("crate_a", 250, u8::MAX)
445            .expect_err("reserved internal id must not be reservable");
446        assert!(matches!(
447            err,
448            MemoryRegistryError::ReservedInternalId { .. }
449        ));
450    }
451
452    #[test]
453    fn rejects_internal_reserved_id_on_deferred_register() {
454        reset_for_tests();
455
456        let err = defer_register(u8::MAX, "crate_a", "slot")
457            .expect_err("reserved id should fail before init");
458        assert!(matches!(
459            err,
460            MemoryRegistryError::ReservedInternalId { .. }
461        ));
462    }
463
464    #[test]
465    fn rejects_internal_reserved_id_on_deferred_range_reservation() {
466        reset_for_tests();
467
468        let err = defer_reserve_range("crate_a", 240, u8::MAX)
469            .expect_err("reserved id should fail before init");
470        assert!(matches!(
471            err,
472            MemoryRegistryError::ReservedInternalId { .. }
473        ));
474    }
475}