Skip to main content

canic_memory/
registry.rs

1use crate::ThisError;
2use std::{cell::RefCell, collections::BTreeMap};
3
4///
5/// MemoryRange
6///
7/// Inclusive stable-memory ID range reserved by one owner crate.
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct MemoryRange {
11    /// First stable-memory ID in the range.
12    pub start: u8,
13    /// Last stable-memory ID in the range.
14    pub end: u8,
15}
16
17impl MemoryRange {
18    /// Return whether `id` is inside this inclusive range.
19    #[must_use]
20    pub const fn contains(&self, id: u8) -> bool {
21        id >= self.start && id <= self.end
22    }
23}
24
25///
26/// MemoryRegistryEntry
27///
28/// Registered stable-memory slot metadata.
29
30#[derive(Clone, Debug)]
31pub struct MemoryRegistryEntry {
32    /// Crate name that registered the stable-memory slot.
33    pub crate_name: String,
34    /// Human-readable label for the registered stable-memory slot.
35    pub label: String,
36}
37
38///
39/// MemoryRangeEntry
40///
41/// Reserved stable-memory range with explicit owner context.
42
43#[derive(Clone, Debug)]
44pub struct MemoryRangeEntry {
45    /// Crate name that reserved the range.
46    pub owner: String,
47    /// Inclusive stable-memory ID range reserved by `owner`.
48    pub range: MemoryRange,
49}
50
51///
52/// MemoryRangeSnapshot
53///
54/// Registered stable-memory slots grouped under one reserved range.
55
56#[derive(Clone, Debug)]
57pub struct MemoryRangeSnapshot {
58    /// Crate name that reserved the range.
59    pub owner: String,
60    /// Inclusive stable-memory ID range reserved by `owner`.
61    pub range: MemoryRange,
62    /// Registered entries whose IDs fall inside `range`.
63    pub entries: Vec<(u8, MemoryRegistryEntry)>,
64}
65
66///
67/// MemoryRegistryError
68///
69/// Errors returned when a memory range or ID registration is invalid.
70
71#[derive(Debug, ThisError)]
72pub enum MemoryRegistryError {
73    /// A requested owner range overlaps an already reserved range.
74    #[error(
75        "memory range overlap: crate '{existing_crate}' [{existing_start}-{existing_end}]
76conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
77    )]
78    Overlap {
79        /// Crate that already owns the conflicting range.
80        existing_crate: String,
81        /// First ID in the existing range.
82        existing_start: u8,
83        /// Last ID in the existing range.
84        existing_end: u8,
85        /// Crate requesting the new range.
86        new_crate: String,
87        /// First ID in the requested range.
88        new_start: u8,
89        /// Last ID in the requested range.
90        new_end: u8,
91    },
92
93    /// The requested range has `start > end`.
94    #[error("memory range is invalid: start={start} end={end}")]
95    InvalidRange {
96        /// First ID in the requested range.
97        start: u8,
98        /// Last ID in the requested range.
99        end: u8,
100    },
101
102    /// The memory ID is already registered.
103    #[error("memory id {0} is already registered; each memory id must be globally unique")]
104    DuplicateId(u8),
105
106    /// The crate attempted to register an ID before reserving any range.
107    #[error("memory id {id} has no reserved range for crate '{crate_name}'")]
108    NoReservedRange {
109        /// Crate attempting to register the ID.
110        crate_name: String,
111        /// Stable-memory ID being registered.
112        id: u8,
113    },
114
115    /// The ID falls inside a range reserved by another crate.
116    #[error(
117        "memory id {id} reserved to crate '{owner}' [{owner_start}-{owner_end}], not '{crate_name}'"
118    )]
119    IdOwnedByOther {
120        /// Crate attempting to register the ID.
121        crate_name: String,
122        /// Stable-memory ID being registered.
123        id: u8,
124        /// Crate that owns the range containing `id`.
125        owner: String,
126        /// First ID in the owning range.
127        owner_start: u8,
128        /// Last ID in the owning range.
129        owner_end: u8,
130    },
131
132    /// The crate has reserved ranges, but none contain the requested ID.
133    #[error("memory id {id} is outside reserved ranges for crate '{crate_name}'")]
134    IdOutOfRange {
135        /// Crate attempting to register the ID.
136        crate_name: String,
137        /// Stable-memory ID being registered.
138        id: u8,
139    },
140
141    /// The ID is reserved for stable-structures internals.
142    #[error(
143        "memory id {id} is reserved for stable-structures internals and cannot be used by application code"
144    )]
145    ReservedInternalId {
146        /// Reserved internal stable-memory ID.
147        id: u8,
148    },
149}
150
151//
152// Internal global state (substrate-level, single-threaded)
153//
154
155thread_local! {
156    static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
157    static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
158
159    // Deferred registrations (used before init)
160    static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
161    static PENDING_REGISTRATIONS: RefCell<Vec<(u8, String, String)>> = const { RefCell::new(Vec::new()) };
162}
163
164///
165/// MemoryRegistry
166///
167/// Canonical substrate registry for stable memory IDs.
168///
169pub struct MemoryRegistry;
170
171impl MemoryRegistry {
172    /// Reserve an inclusive memory ID range for one crate.
173    ///
174    /// Exact duplicate reservations by the same crate are accepted so init and
175    /// post-upgrade can share the same bootstrap path.
176    pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
177        if start > end {
178            return Err(MemoryRegistryError::InvalidRange { start, end });
179        }
180        validate_range_excludes_reserved_internal_id(start, end)?;
181
182        let range = MemoryRange { start, end };
183
184        RESERVED_RANGES.with_borrow(|ranges| {
185            for (existing_crate, existing_range) in ranges {
186                if ranges_overlap(*existing_range, range) {
187                    if existing_crate == crate_name
188                        && existing_range.start == start
189                        && existing_range.end == end
190                    {
191                        // Allow exact duplicate reservations for idempotent init.
192                        return Ok(());
193                    }
194                    return Err(MemoryRegistryError::Overlap {
195                        existing_crate: existing_crate.clone(),
196                        existing_start: existing_range.start,
197                        existing_end: existing_range.end,
198                        new_crate: crate_name.to_string(),
199                        new_start: start,
200                        new_end: end,
201                    });
202                }
203            }
204
205            Ok(())
206        })?;
207
208        RESERVED_RANGES.with_borrow_mut(|ranges| {
209            ranges.push((crate_name.to_string(), range));
210        });
211
212        Ok(())
213    }
214
215    /// Register one memory ID under an existing owner range.
216    pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
217        validate_non_internal_id(id)?;
218        validate_registration_range(crate_name, id)?;
219
220        REGISTRY.with_borrow(|reg| {
221            if reg.contains_key(&id) {
222                return Err(MemoryRegistryError::DuplicateId(id));
223            }
224            Ok(())
225        })?;
226
227        REGISTRY.with_borrow_mut(|reg| {
228            reg.insert(
229                id,
230                MemoryRegistryEntry {
231                    crate_name: crate_name.to_string(),
232                    label: label.to_string(),
233                },
234            );
235        });
236
237        Ok(())
238    }
239
240    /// Export all registered entries (canonical snapshot).
241    #[must_use]
242    pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
243        REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
244    }
245
246    /// Export all reserved ranges.
247    #[must_use]
248    pub fn export_ranges() -> Vec<(String, MemoryRange)> {
249        RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
250    }
251
252    /// Export all reserved ranges with explicit owners.
253    #[must_use]
254    pub fn export_range_entries() -> Vec<MemoryRangeEntry> {
255        RESERVED_RANGES.with_borrow(|ranges| {
256            ranges
257                .iter()
258                .map(|(owner, range)| MemoryRangeEntry {
259                    owner: owner.clone(),
260                    range: *range,
261                })
262                .collect()
263        })
264    }
265
266    /// Export registry entries grouped by reserved range.
267    #[must_use]
268    pub fn export_ids_by_range() -> Vec<MemoryRangeSnapshot> {
269        let mut ranges = RESERVED_RANGES.with_borrow(std::clone::Clone::clone);
270        let entries = REGISTRY.with_borrow(std::clone::Clone::clone);
271
272        ranges.sort_by_key(|(_, range)| range.start);
273
274        ranges
275            .into_iter()
276            .map(|(owner, range)| {
277                let entries = entries
278                    .iter()
279                    .filter(|(id, _)| range.contains(**id))
280                    .map(|(id, entry)| (*id, entry.clone()))
281                    .collect();
282
283                MemoryRangeSnapshot {
284                    owner,
285                    range,
286                    entries,
287                }
288            })
289            .collect()
290    }
291
292    /// Retrieve a single registry entry.
293    #[must_use]
294    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
295        REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
296    }
297}
298
299//
300// Deferred registration helpers (used before runtime init)
301//
302
303/// Queue a range reservation for deterministic application during bootstrap.
304#[doc(hidden)]
305pub fn defer_reserve_range(
306    crate_name: &str,
307    start: u8,
308    end: u8,
309) -> Result<(), MemoryRegistryError> {
310    if start > end {
311        return Err(MemoryRegistryError::InvalidRange { start, end });
312    }
313    validate_range_excludes_reserved_internal_id(start, end)?;
314
315    // Queue range reservations for runtime init to apply deterministically.
316    PENDING_RANGES.with_borrow_mut(|ranges| {
317        ranges.push((crate_name.to_string(), start, end));
318    });
319
320    Ok(())
321}
322
323/// Queue an ID registration for deterministic application during bootstrap.
324#[doc(hidden)]
325pub fn defer_register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
326    validate_non_internal_id(id)?;
327
328    // Queue ID registrations for runtime init to apply after ranges are reserved.
329    PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
330        regs.push((id, crate_name.to_string(), label.to_string()));
331    });
332
333    Ok(())
334}
335
336/// Drain all queued range reservations in insertion order.
337#[must_use]
338pub(crate) fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
339    PENDING_RANGES.with_borrow_mut(std::mem::take)
340}
341
342/// Drain all queued ID registrations in insertion order.
343#[must_use]
344pub(crate) fn drain_pending_registrations() -> Vec<(u8, String, String)> {
345    PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
346}
347
348//
349// Test-only helpers
350//
351
352#[cfg(test)]
353/// Clear registry and pending queues for isolated unit tests.
354pub fn reset_for_tests() {
355    RESERVED_RANGES.with_borrow_mut(Vec::clear);
356    REGISTRY.with_borrow_mut(BTreeMap::clear);
357    PENDING_RANGES.with_borrow_mut(Vec::clear);
358    PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
359}
360
361//
362// Internal helpers
363//
364
365const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
366    a.start <= b.end && b.start <= a.end
367}
368
369const INTERNAL_RESERVED_MEMORY_ID: u8 = u8::MAX;
370
371const fn validate_non_internal_id(id: u8) -> Result<(), MemoryRegistryError> {
372    if id == INTERNAL_RESERVED_MEMORY_ID {
373        return Err(MemoryRegistryError::ReservedInternalId { id });
374    }
375    Ok(())
376}
377
378const fn validate_range_excludes_reserved_internal_id(
379    _start: u8,
380    end: u8,
381) -> Result<(), MemoryRegistryError> {
382    if end == INTERNAL_RESERVED_MEMORY_ID {
383        return Err(MemoryRegistryError::ReservedInternalId {
384            id: INTERNAL_RESERVED_MEMORY_ID,
385        });
386    }
387    Ok(())
388}
389
390fn validate_registration_range(crate_name: &str, id: u8) -> Result<(), MemoryRegistryError> {
391    let mut has_range = false;
392    let mut owner_match = false;
393    let mut owner_for_id: Option<(String, MemoryRange)> = None;
394
395    RESERVED_RANGES.with_borrow(|ranges| {
396        for (owner, range) in ranges {
397            if owner == crate_name {
398                has_range = true;
399                if range.contains(id) {
400                    owner_match = true;
401                    break;
402                }
403            }
404
405            if owner_for_id.is_none() && range.contains(id) {
406                owner_for_id = Some((owner.clone(), *range));
407            }
408        }
409    });
410
411    if owner_match {
412        return Ok(());
413    }
414
415    if !has_range {
416        return Err(MemoryRegistryError::NoReservedRange {
417            crate_name: crate_name.to_string(),
418            id,
419        });
420    }
421
422    if let Some((owner, range)) = owner_for_id {
423        return Err(MemoryRegistryError::IdOwnedByOther {
424            crate_name: crate_name.to_string(),
425            id,
426            owner,
427            owner_start: range.start,
428            owner_end: range.end,
429        });
430    }
431
432    Err(MemoryRegistryError::IdOutOfRange {
433        crate_name: crate_name.to_string(),
434        id,
435    })
436}
437
438///
439/// TESTS
440///
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445
446    #[test]
447    fn allows_in_range() {
448        reset_for_tests();
449
450        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range");
451        MemoryRegistry::register(2, "crate_a", "slot").expect("register in range");
452    }
453
454    #[test]
455    fn rejects_unreserved() {
456        reset_for_tests();
457
458        let err = MemoryRegistry::register(2, "crate_a", "slot").expect_err("missing range");
459        assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
460    }
461
462    #[test]
463    fn rejects_other_owner() {
464        reset_for_tests();
465
466        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
467        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
468
469        let err = MemoryRegistry::register(2, "crate_b", "slot").expect_err("owned by other");
470        assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
471    }
472
473    #[test]
474    fn export_ids_by_range_groups_entries() {
475        reset_for_tests();
476
477        MemoryRegistry::reserve_range("crate_a", 1, 3).expect("reserve range A");
478        MemoryRegistry::reserve_range("crate_b", 4, 6).expect("reserve range B");
479        MemoryRegistry::register(1, "crate_a", "a1").expect("register a1");
480        MemoryRegistry::register(5, "crate_b", "b5").expect("register b5");
481
482        let snapshots = MemoryRegistry::export_ids_by_range();
483        assert_eq!(snapshots.len(), 2);
484        assert_eq!(snapshots[0].entries.len(), 1);
485        assert_eq!(snapshots[1].entries.len(), 1);
486    }
487
488    #[test]
489    fn rejects_internal_reserved_id_on_register() {
490        reset_for_tests();
491
492        MemoryRegistry::reserve_range("crate_a", 1, 254).expect("reserve range");
493        let err = MemoryRegistry::register(u8::MAX, "crate_a", "slot")
494            .expect_err("reserved id should be rejected");
495        assert!(matches!(
496            err,
497            MemoryRegistryError::ReservedInternalId { .. }
498        ));
499    }
500
501    #[test]
502    fn rejects_internal_reserved_id_on_range_reservation() {
503        reset_for_tests();
504
505        let err = MemoryRegistry::reserve_range("crate_a", 250, u8::MAX)
506            .expect_err("reserved internal id must not be reservable");
507        assert!(matches!(
508            err,
509            MemoryRegistryError::ReservedInternalId { .. }
510        ));
511    }
512
513    #[test]
514    fn rejects_internal_reserved_id_on_deferred_register() {
515        reset_for_tests();
516
517        let err = defer_register(u8::MAX, "crate_a", "slot")
518            .expect_err("reserved id should fail before init");
519        assert!(matches!(
520            err,
521            MemoryRegistryError::ReservedInternalId { .. }
522        ));
523    }
524
525    #[test]
526    fn rejects_internal_reserved_id_on_deferred_range_reservation() {
527        reset_for_tests();
528
529        let err = defer_reserve_range("crate_a", 240, u8::MAX)
530            .expect_err("reserved id should fail before init");
531        assert!(matches!(
532            err,
533            MemoryRegistryError::ReservedInternalId { .. }
534        ));
535    }
536}