1use crate::{ThisError, ledger};
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::BTreeMap};
4
5#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct MemoryRange {
12 pub start: u8,
14 pub end: u8,
16}
17
18impl MemoryRange {
19 #[must_use]
21 pub const fn contains(&self, id: u8) -> bool {
22 id >= self.start && id <= self.end
23 }
24}
25
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
32pub struct MemoryRegistryEntry {
33 pub crate_name: String,
35 pub label: String,
37 pub stable_key: String,
39}
40
41#[derive(Clone, Debug)]
47pub struct MemoryRangeEntry {
48 pub owner: String,
50 pub range: MemoryRange,
52}
53
54#[derive(Clone, Debug)]
60pub struct MemoryRangeSnapshot {
61 pub owner: String,
63 pub range: MemoryRange,
65 pub entries: Vec<(u8, MemoryRegistryEntry)>,
67}
68
69#[derive(Debug, ThisError)]
75pub enum MemoryRegistryError {
76 #[error(
78 "memory range overlap: crate '{existing_crate}' [{existing_start}-{existing_end}]
79conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
80 )]
81 Overlap {
82 existing_crate: String,
84 existing_start: u8,
86 existing_end: u8,
88 new_crate: String,
90 new_start: u8,
92 new_end: u8,
94 },
95
96 #[error("memory range is invalid: start={start} end={end}")]
98 InvalidRange {
99 start: u8,
101 end: u8,
103 },
104
105 #[error("memory id {0} is already registered; each memory id must be globally unique")]
107 DuplicateId(u8),
108
109 #[error(
111 "memory stable key '{0}' is declared more than once; each stable key must be globally unique"
112 )]
113 DuplicateStableKey(String),
114
115 #[error("memory id {id} has no reserved range for crate '{crate_name}'")]
117 NoReservedRange {
118 crate_name: String,
120 id: u8,
122 },
123
124 #[error(
126 "memory id {id} reserved to crate '{owner}' [{owner_start}-{owner_end}], not '{crate_name}'"
127 )]
128 IdOwnedByOther {
129 crate_name: String,
131 id: u8,
133 owner: String,
135 owner_start: u8,
137 owner_end: u8,
139 },
140
141 #[error("memory id {id} is outside reserved ranges for crate '{crate_name}'")]
143 IdOutOfRange {
144 crate_name: String,
146 id: u8,
148 },
149
150 #[error(
152 "memory id {id} is the unallocated-bucket sentinel and is not a usable virtual memory id"
153 )]
154 ReservedInternalId {
155 id: u8,
157 },
158
159 #[error(
161 "memory range historical conflict: crate '{existing_crate}' [{existing_start}-{existing_end}]
162conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
163 )]
164 HistoricalRangeConflict {
165 existing_crate: String,
167 existing_start: u8,
169 existing_end: u8,
171 new_crate: String,
173 new_start: u8,
175 new_end: u8,
177 },
178
179 #[error(
181 "memory id {id} was historically registered to crate '{existing_crate}' label '{existing_label}', not crate '{new_crate}' label '{new_label}'"
182 )]
183 HistoricalIdConflict {
184 id: u8,
186 existing_crate: String,
188 existing_label: String,
190 new_crate: String,
192 new_label: String,
194 new_stable_key: String,
196 },
197
198 #[error(
200 "memory stable key '{stable_key}' was historically registered to id {existing_id}, not id {new_id}"
201 )]
202 HistoricalStableKeyConflict {
203 stable_key: String,
205 existing_id: u8,
207 new_id: u8,
209 },
210
211 #[error("memory stable key '{stable_key}' is invalid: {reason}")]
213 InvalidStableKey {
214 stable_key: String,
216 reason: &'static str,
218 },
219
220 #[error(
222 "memory stable key '{stable_key}' with id {id} violates namespace/range authority: {reason}"
223 )]
224 RangeAuthorityViolation {
225 stable_key: String,
227 id: u8,
229 reason: &'static str,
231 },
232
233 #[error(
235 "memory registration after bootstrap is sealed is not allowed: {ranges} range(s), {registrations} registration(s)"
236 )]
237 RegistrationAfterBootstrap {
238 ranges: usize,
240 registrations: usize,
242 },
243
244 #[error("memory registry has not completed bootstrap validation")]
246 RegistryNotBootstrapped,
247
248 #[error("memory layout ledger is corrupt: {reason}")]
250 LedgerCorrupt {
251 reason: &'static str,
253 },
254}
255
256thread_local! {
261 static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
262 static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
263
264 static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
266 static PENDING_REGISTRATIONS: RefCell<Vec<(u8, String, String, String)>> = const { RefCell::new(Vec::new()) };
267}
268
269pub struct MemoryRegistry;
275
276impl MemoryRegistry {
277 pub(crate) fn reserve_internal_layout_ledger() -> Result<(), MemoryRegistryError> {
279 Self::reserve_range(
280 ledger::MEMORY_LAYOUT_LEDGER_OWNER,
281 ledger::MEMORY_LAYOUT_RESERVED_MIN,
282 ledger::MEMORY_LAYOUT_RESERVED_MAX,
283 )?;
284
285 if let Some(entry) = Self::get(ledger::MEMORY_LAYOUT_LEDGER_ID)
286 && entry.crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
287 && entry.label == ledger::MEMORY_LAYOUT_LEDGER_LABEL
288 && entry.stable_key == ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY
289 {
290 return Ok(());
291 }
292
293 Self::register_with_key(
294 ledger::MEMORY_LAYOUT_LEDGER_ID,
295 ledger::MEMORY_LAYOUT_LEDGER_OWNER,
296 ledger::MEMORY_LAYOUT_LEDGER_LABEL,
297 ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY,
298 )
299 }
300
301 pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
306 if start > end {
307 return Err(MemoryRegistryError::InvalidRange { start, end });
308 }
309 validate_range_excludes_reserved_internal_id(start, end)?;
310 validate_range_excludes_layout_metadata(crate_name, start, end)?;
311
312 let range = MemoryRange { start, end };
313 let mut already_reserved = false;
314
315 RESERVED_RANGES.with_borrow(|ranges| {
316 for (existing_crate, existing_range) in ranges {
317 if ranges_overlap(*existing_range, range) {
318 if existing_crate == crate_name
319 && existing_range.start == start
320 && existing_range.end == end
321 {
322 already_reserved = true;
324 return Ok(());
325 }
326 return Err(MemoryRegistryError::Overlap {
327 existing_crate: existing_crate.clone(),
328 existing_start: existing_range.start,
329 existing_end: existing_range.end,
330 new_crate: crate_name.to_string(),
331 new_start: start,
332 new_end: end,
333 });
334 }
335 }
336
337 Ok(())
338 })?;
339
340 ledger::record_range(crate_name, range)?;
341
342 if already_reserved {
343 return Ok(());
344 }
345
346 RESERVED_RANGES.with_borrow_mut(|ranges| {
347 ranges.push((crate_name.to_string(), range));
348 });
349
350 Ok(())
351 }
352
353 pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
355 Self::register_with_key(
356 id,
357 crate_name,
358 label,
359 &fallback_stable_key(crate_name, label),
360 )
361 }
362
363 pub fn register_with_key(
365 id: u8,
366 crate_name: &str,
367 label: &str,
368 stable_key: &str,
369 ) -> Result<(), MemoryRegistryError> {
370 validate_non_internal_id(id)?;
371 validate_id_excludes_layout_metadata(crate_name, id)?;
372 validate_registration_range(crate_name, id)?;
373 validate_stable_key(stable_key)?;
374 validate_id_authority(id, crate_name, stable_key)?;
375
376 REGISTRY.with_borrow(|reg| {
377 if reg.contains_key(&id) {
378 return Err(MemoryRegistryError::DuplicateId(id));
379 }
380 Ok(())
381 })?;
382
383 ledger::record_entry(id, crate_name, label, stable_key)?;
384
385 REGISTRY.with_borrow_mut(|reg| {
386 reg.insert(
387 id,
388 MemoryRegistryEntry {
389 crate_name: crate_name.to_string(),
390 label: label.to_string(),
391 stable_key: stable_key.to_string(),
392 },
393 );
394 });
395
396 Ok(())
397 }
398
399 #[must_use]
401 pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
402 REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
403 }
404
405 #[must_use]
407 pub fn export_ranges() -> Vec<(String, MemoryRange)> {
408 RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
409 }
410
411 #[must_use]
413 pub fn export_range_entries() -> Vec<MemoryRangeEntry> {
414 RESERVED_RANGES.with_borrow(|ranges| {
415 ranges
416 .iter()
417 .map(|(owner, range)| MemoryRangeEntry {
418 owner: owner.clone(),
419 range: *range,
420 })
421 .collect()
422 })
423 }
424
425 #[must_use]
427 pub fn export_ids_by_range() -> Vec<MemoryRangeSnapshot> {
428 let mut ranges = RESERVED_RANGES.with_borrow(std::clone::Clone::clone);
429 let entries = REGISTRY.with_borrow(std::clone::Clone::clone);
430
431 ranges.sort_by_key(|(_, range)| range.start);
432
433 ranges
434 .into_iter()
435 .map(|(owner, range)| {
436 let entries = entries
437 .iter()
438 .filter(|(id, _)| range.contains(**id))
439 .map(|(id, entry)| (*id, entry.clone()))
440 .collect();
441
442 MemoryRangeSnapshot {
443 owner,
444 range,
445 entries,
446 }
447 })
448 .collect()
449 }
450
451 #[must_use]
453 pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
454 REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
455 }
456
457 #[must_use]
459 pub fn export_historical_ranges() -> Vec<(String, MemoryRange)> {
460 ledger::export_ranges()
461 }
462
463 pub fn try_export_historical_ranges() -> Result<Vec<(String, MemoryRange)>, MemoryRegistryError>
465 {
466 ledger::try_export_ranges()
467 }
468
469 #[must_use]
471 pub fn export_historical() -> Vec<(u8, MemoryRegistryEntry)> {
472 ledger::export_entries()
473 }
474
475 pub fn try_export_historical() -> Result<Vec<(u8, MemoryRegistryEntry)>, MemoryRegistryError> {
477 ledger::try_export_entries()
478 }
479}
480
481#[doc(hidden)]
487pub fn defer_reserve_range(
488 crate_name: &str,
489 start: u8,
490 end: u8,
491) -> Result<(), MemoryRegistryError> {
492 if start > end {
493 return Err(MemoryRegistryError::InvalidRange { start, end });
494 }
495 validate_range_excludes_reserved_internal_id(start, end)?;
496 validate_range_excludes_layout_metadata(crate_name, start, end)?;
497
498 PENDING_RANGES.with_borrow_mut(|ranges| {
500 ranges.push((crate_name.to_string(), start, end));
501 });
502
503 Ok(())
504}
505
506#[doc(hidden)]
508pub fn defer_register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
509 defer_register_with_key(
510 id,
511 crate_name,
512 label,
513 &fallback_stable_key(crate_name, label),
514 )
515}
516
517#[doc(hidden)]
519pub fn defer_register_with_key(
520 id: u8,
521 crate_name: &str,
522 label: &str,
523 stable_key: &str,
524) -> Result<(), MemoryRegistryError> {
525 validate_non_internal_id(id)?;
526 validate_id_excludes_layout_metadata(crate_name, id)?;
527 validate_stable_key(stable_key)?;
528 validate_id_authority(id, crate_name, stable_key)?;
529
530 PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
532 regs.push((
533 id,
534 crate_name.to_string(),
535 label.to_string(),
536 stable_key.to_string(),
537 ));
538 });
539
540 Ok(())
541}
542
543#[must_use]
545pub(crate) fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
546 PENDING_RANGES.with_borrow_mut(std::mem::take)
547}
548
549#[must_use]
551pub(crate) fn drain_pending_registrations() -> Vec<(u8, String, String, String)> {
552 PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
553}
554
555#[cfg(test)]
560pub fn reset_for_tests() {
562 reset_runtime_for_tests();
563 ledger::reset_for_tests();
564 crate::runtime::registry::reset_initialized_for_tests();
565}
566
567#[cfg(test)]
568fn reset_runtime_for_tests() {
569 RESERVED_RANGES.with_borrow_mut(Vec::clear);
570 REGISTRY.with_borrow_mut(BTreeMap::clear);
571 PENDING_RANGES.with_borrow_mut(Vec::clear);
572 PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
573}
574
575const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
580 a.start <= b.end && b.start <= a.end
581}
582
583const INTERNAL_RESERVED_MEMORY_ID: u8 = u8::MAX;
584const CANIC_FRAMEWORK_MAX_ID: u8 = 99;
585const APPLICATION_MIN_ID: u8 = 100;
586
587const MEMORY_LAYOUT_RESERVED_RANGE: MemoryRange = MemoryRange {
588 start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
589 end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
590};
591
592const fn validate_non_internal_id(id: u8) -> Result<(), MemoryRegistryError> {
593 if id == INTERNAL_RESERVED_MEMORY_ID {
594 return Err(MemoryRegistryError::ReservedInternalId { id });
595 }
596 Ok(())
597}
598
599fn fallback_stable_key(crate_name: &str, label: &str) -> String {
600 format!(
601 "legacy.{}.{}.v1",
602 canonical_segment(crate_name),
603 canonical_segment(label)
604 )
605}
606
607fn validate_stable_key(stable_key: &str) -> Result<(), MemoryRegistryError> {
608 if stable_key.is_empty() {
609 return invalid_stable_key(stable_key, "must not be empty");
610 }
611 if stable_key.len() > 128 {
612 return invalid_stable_key(stable_key, "must be at most 128 bytes");
613 }
614 if !stable_key.is_ascii() {
615 return invalid_stable_key(stable_key, "must be ASCII");
616 }
617 if stable_key.bytes().any(|b| b.is_ascii_uppercase()) {
618 return invalid_stable_key(stable_key, "must be lowercase");
619 }
620 if stable_key.contains(char::is_whitespace) {
621 return invalid_stable_key(stable_key, "must not contain whitespace");
622 }
623 if stable_key.contains('/') || stable_key.contains('-') {
624 return invalid_stable_key(stable_key, "must not contain slashes or hyphens");
625 }
626 if stable_key.starts_with('.') || stable_key.ends_with('.') {
627 return invalid_stable_key(stable_key, "must not start or end with a dot");
628 }
629
630 let Some(version_index) = stable_key.rfind(".v") else {
631 return invalid_stable_key(stable_key, "must end with .vN");
632 };
633 let version = &stable_key[version_index + 2..];
634 if version.is_empty()
635 || version.starts_with('0')
636 || !version.bytes().all(|b| b.is_ascii_digit())
637 {
638 return invalid_stable_key(stable_key, "version suffix must be nonzero .vN");
639 }
640
641 let prefix = &stable_key[..version_index];
642 if prefix.is_empty() {
643 return invalid_stable_key(
644 stable_key,
645 "must contain at least one segment before version",
646 );
647 }
648
649 for segment in prefix.split('.') {
650 validate_stable_key_segment(stable_key, segment)?;
651 }
652
653 Ok(())
654}
655
656fn validate_stable_key_segment(stable_key: &str, segment: &str) -> Result<(), MemoryRegistryError> {
657 if segment.is_empty() {
658 return invalid_stable_key(stable_key, "must not contain empty segments");
659 }
660 let mut bytes = segment.bytes();
661 let Some(first) = bytes.next() else {
662 return invalid_stable_key(stable_key, "must not contain empty segments");
663 };
664 if !first.is_ascii_lowercase() {
665 return invalid_stable_key(stable_key, "segments must start with a lowercase letter");
666 }
667 if !bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') {
668 return invalid_stable_key(
669 stable_key,
670 "segments may contain only lowercase letters, digits, and underscores",
671 );
672 }
673 Ok(())
674}
675
676fn invalid_stable_key<T>(stable_key: &str, reason: &'static str) -> Result<T, MemoryRegistryError> {
677 Err(MemoryRegistryError::InvalidStableKey {
678 stable_key: stable_key.to_string(),
679 reason,
680 })
681}
682
683fn validate_id_authority(
684 id: u8,
685 crate_name: &str,
686 stable_key: &str,
687) -> Result<(), MemoryRegistryError> {
688 if stable_key.starts_with("canic.") {
689 if !crate_name.starts_with("canic") {
690 return Err(MemoryRegistryError::RangeAuthorityViolation {
691 stable_key: stable_key.to_string(),
692 id,
693 reason: "canic.* keys may only be declared by Canic framework crates",
694 });
695 }
696
697 if id <= CANIC_FRAMEWORK_MAX_ID {
698 return Ok(());
699 }
700
701 return Err(MemoryRegistryError::RangeAuthorityViolation {
702 stable_key: stable_key.to_string(),
703 id,
704 reason: "canic.* keys must use ids 0-99",
705 });
706 }
707
708 if (APPLICATION_MIN_ID..INTERNAL_RESERVED_MEMORY_ID).contains(&id) {
709 return Ok(());
710 }
711
712 Err(MemoryRegistryError::RangeAuthorityViolation {
713 stable_key: stable_key.to_string(),
714 id,
715 reason: "application keys must use ids 100-254",
716 })
717}
718
719fn canonical_segment(value: &str) -> String {
720 let mut out = String::new();
721 let mut last_was_underscore = false;
722
723 for ch in value.chars().flat_map(char::to_lowercase) {
724 if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
725 out.push(ch);
726 last_was_underscore = false;
727 } else if !last_was_underscore {
728 out.push('_');
729 last_was_underscore = true;
730 }
731 }
732
733 let trimmed = out.trim_matches('_');
734 if trimmed.is_empty() {
735 return "unnamed".to_string();
736 }
737 if trimmed.as_bytes()[0].is_ascii_digit() {
738 return format!("n_{trimmed}");
739 }
740 trimmed.to_string()
741}
742
743const fn validate_range_excludes_reserved_internal_id(
744 _start: u8,
745 end: u8,
746) -> Result<(), MemoryRegistryError> {
747 if end == INTERNAL_RESERVED_MEMORY_ID {
748 return Err(MemoryRegistryError::ReservedInternalId {
749 id: INTERNAL_RESERVED_MEMORY_ID,
750 });
751 }
752 Ok(())
753}
754
755fn validate_range_excludes_layout_metadata(
756 crate_name: &str,
757 start: u8,
758 end: u8,
759) -> Result<(), MemoryRegistryError> {
760 let requested = MemoryRange { start, end };
761 if !ranges_overlap(requested, MEMORY_LAYOUT_RESERVED_RANGE) {
762 return Ok(());
763 }
764
765 if crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
766 && start == ledger::MEMORY_LAYOUT_RESERVED_MIN
767 && end == ledger::MEMORY_LAYOUT_RESERVED_MAX
768 {
769 return Ok(());
770 }
771
772 Err(MemoryRegistryError::HistoricalRangeConflict {
773 existing_crate: ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
774 existing_start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
775 existing_end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
776 new_crate: crate_name.to_string(),
777 new_start: start,
778 new_end: end,
779 })
780}
781
782fn validate_id_excludes_layout_metadata(
783 crate_name: &str,
784 id: u8,
785) -> Result<(), MemoryRegistryError> {
786 if !MEMORY_LAYOUT_RESERVED_RANGE.contains(id)
787 || crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
788 {
789 return Ok(());
790 }
791
792 Err(MemoryRegistryError::IdOwnedByOther {
793 crate_name: crate_name.to_string(),
794 id,
795 owner: ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
796 owner_start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
797 owner_end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
798 })
799}
800
801fn validate_registration_range(crate_name: &str, id: u8) -> Result<(), MemoryRegistryError> {
802 let mut has_range = false;
803 let mut owner_match = false;
804 let mut owner_for_id: Option<(String, MemoryRange)> = None;
805
806 RESERVED_RANGES.with_borrow(|ranges| {
807 for (owner, range) in ranges {
808 if owner == crate_name {
809 has_range = true;
810 if range.contains(id) {
811 owner_match = true;
812 break;
813 }
814 }
815
816 if owner_for_id.is_none() && range.contains(id) {
817 owner_for_id = Some((owner.clone(), *range));
818 }
819 }
820 });
821
822 if owner_match {
823 return Ok(());
824 }
825
826 if !has_range {
827 return Err(MemoryRegistryError::NoReservedRange {
828 crate_name: crate_name.to_string(),
829 id,
830 });
831 }
832
833 if let Some((owner, range)) = owner_for_id {
834 return Err(MemoryRegistryError::IdOwnedByOther {
835 crate_name: crate_name.to_string(),
836 id,
837 owner,
838 owner_start: range.start,
839 owner_end: range.end,
840 });
841 }
842
843 Err(MemoryRegistryError::IdOutOfRange {
844 crate_name: crate_name.to_string(),
845 id,
846 })
847}
848
849#[cfg(test)]
854mod tests {
855 use super::*;
856
857 #[test]
858 fn allows_in_range() {
859 reset_for_tests();
860
861 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range");
862 MemoryRegistry::register(101, "crate_a", "slot").expect("register in range");
863 }
864
865 #[test]
866 fn rejects_unreserved() {
867 reset_for_tests();
868
869 let err = MemoryRegistry::register(100, "crate_a", "slot").expect_err("missing range");
870 assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
871 }
872
873 #[test]
874 fn rejects_other_owner() {
875 reset_for_tests();
876
877 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
878 MemoryRegistry::reserve_range("crate_b", 110, 112).expect("reserve range B");
879
880 let err = MemoryRegistry::register(101, "crate_b", "slot").expect_err("owned by other");
881 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
882 }
883
884 #[test]
885 fn export_ids_by_range_groups_entries() {
886 reset_for_tests();
887
888 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
889 MemoryRegistry::reserve_range("crate_b", 110, 112).expect("reserve range B");
890 MemoryRegistry::register(100, "crate_a", "a100").expect("register a100");
891 MemoryRegistry::register(111, "crate_b", "b111").expect("register b111");
892
893 let snapshots = MemoryRegistry::export_ids_by_range();
894 assert_eq!(snapshots.len(), 2);
895 assert_eq!(snapshots[0].entries.len(), 1);
896 assert_eq!(snapshots[1].entries.len(), 1);
897 }
898
899 #[test]
900 fn historical_range_conflict_survives_runtime_reset() {
901 reset_for_tests();
902
903 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
904 reset_runtime_for_tests();
905
906 let err = MemoryRegistry::reserve_range("crate_b", 101, 103)
907 .expect_err("historical range overlap should fail");
908 assert!(matches!(
909 err,
910 MemoryRegistryError::HistoricalRangeConflict { .. }
911 ));
912 }
913
914 #[test]
915 fn historical_id_conflict_survives_runtime_reset() {
916 reset_for_tests();
917
918 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
919 MemoryRegistry::register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
920 .expect("register slot");
921 reset_runtime_for_tests();
922
923 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A again");
924 let err =
925 MemoryRegistry::register_with_key(100, "crate_a", "other", "app.crate_a.other.v1")
926 .expect_err("historical id label drift should fail");
927 assert!(matches!(
928 err,
929 MemoryRegistryError::HistoricalIdConflict { .. }
930 ));
931 }
932
933 #[test]
934 fn stable_key_allows_owner_and_label_metadata_drift() {
935 reset_for_tests();
936
937 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
938 MemoryRegistry::register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
939 .expect("register slot");
940 reset_runtime_for_tests();
941
942 MemoryRegistry::reserve_range("crate_renamed", 100, 102).expect("reserve range A again");
943 MemoryRegistry::register_with_key(
944 100,
945 "crate_renamed",
946 "SlotRenamed",
947 "app.crate_a.slot.v1",
948 )
949 .expect("stable key should survive owner/label drift");
950
951 let entry = MemoryRegistry::get(100).expect("entry should exist");
952 assert_eq!(entry.crate_name, "crate_renamed");
953 assert_eq!(entry.label, "SlotRenamed");
954 assert_eq!(entry.stable_key, "app.crate_a.slot.v1");
955 }
956
957 #[test]
958 fn rejects_non_canic_key_below_application_range() {
959 reset_for_tests();
960
961 MemoryRegistry::reserve_range("crate_a", 1, 4).expect("reserve low framework range");
962 let err = MemoryRegistry::register_with_key(1, "crate_a", "slot", "app.crate_a.slot.v1")
963 .expect_err("application key below 100 should fail");
964 assert!(matches!(
965 err,
966 MemoryRegistryError::RangeAuthorityViolation { .. }
967 ));
968 }
969
970 #[test]
971 fn rejects_canic_key_above_framework_range() {
972 reset_for_tests();
973
974 MemoryRegistry::reserve_range("canic-core", 100, 102).expect("reserve app range");
975 let err =
976 MemoryRegistry::register_with_key(100, "canic-core", "slot", "canic.core.slot.v1")
977 .expect_err("canic key above 99 should fail");
978 assert!(matches!(
979 err,
980 MemoryRegistryError::RangeAuthorityViolation { .. }
981 ));
982 }
983
984 #[test]
985 fn rejects_canic_namespace_from_non_framework_crate() {
986 reset_for_tests();
987
988 MemoryRegistry::reserve_range("crate_a", 11, 12).expect("reserve framework range");
989 let err = MemoryRegistry::register_with_key(11, "crate_a", "slot", "canic.core.slot.v1")
990 .expect_err("non-framework crate must not claim canic namespace");
991 assert!(matches!(
992 err,
993 MemoryRegistryError::RangeAuthorityViolation { .. }
994 ));
995 }
996
997 #[test]
998 fn rejects_non_canonical_stable_key() {
999 reset_for_tests();
1000
1001 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1002 let err = MemoryRegistry::register_with_key(100, "crate_a", "slot", "App.Crate.Slot.v1")
1003 .expect_err("uppercase stable key should fail");
1004 assert!(matches!(err, MemoryRegistryError::InvalidStableKey { .. }));
1005 }
1006
1007 #[test]
1008 fn internal_layout_ledger_uses_only_id_zero_self_record() {
1009 reset_for_tests();
1010
1011 MemoryRegistry::reserve_internal_layout_ledger().expect("reserve ledger");
1012
1013 assert_eq!(
1014 MemoryRegistry::export_ranges(),
1015 vec![(
1016 ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
1017 MemoryRange { start: 0, end: 0 },
1018 )]
1019 );
1020 assert_eq!(
1021 MemoryRegistry::get(0).map(|entry| entry.stable_key),
1022 Some(ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY.to_string())
1023 );
1024 for id in 1..=4 {
1025 assert!(MemoryRegistry::get(id).is_none());
1026 }
1027 }
1028
1029 #[test]
1030 fn historical_stable_key_conflict_survives_runtime_reset() {
1031 reset_for_tests();
1032
1033 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1034 MemoryRegistry::register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
1035 .expect("register slot");
1036 reset_runtime_for_tests();
1037
1038 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A again");
1039 let err = MemoryRegistry::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
1040 .expect_err("stable key must not move to another ID");
1041 assert!(matches!(
1042 err,
1043 MemoryRegistryError::HistoricalStableKeyConflict { .. }
1044 ));
1045 }
1046
1047 #[test]
1048 fn rejects_internal_reserved_id_on_register() {
1049 reset_for_tests();
1050
1051 MemoryRegistry::reserve_range("crate_a", 5, 254).expect("reserve range");
1052 let err = MemoryRegistry::register(u8::MAX, "crate_a", "slot")
1053 .expect_err("reserved id should be rejected");
1054 assert!(matches!(
1055 err,
1056 MemoryRegistryError::ReservedInternalId { .. }
1057 ));
1058 }
1059
1060 #[test]
1061 fn rejects_layout_metadata_range_reservation() {
1062 reset_for_tests();
1063
1064 let err = MemoryRegistry::reserve_range("crate_a", 0, 4)
1065 .expect_err("layout metadata range must not be reservable by applications");
1066 assert!(matches!(
1067 err,
1068 MemoryRegistryError::HistoricalRangeConflict { .. }
1069 ));
1070 }
1071
1072 #[test]
1073 fn rejects_layout_metadata_range_overlap() {
1074 reset_for_tests();
1075
1076 let err = MemoryRegistry::reserve_range("crate_a", 0, 8)
1077 .expect_err("layout metadata overlap must not be reservable by applications");
1078 assert!(matches!(
1079 err,
1080 MemoryRegistryError::HistoricalRangeConflict { .. }
1081 ));
1082 }
1083
1084 #[test]
1085 fn rejects_layout_metadata_range_on_deferred_reservation() {
1086 reset_for_tests();
1087
1088 let err = defer_reserve_range("crate_a", 0, 4)
1089 .expect_err("layout metadata range should fail before init");
1090 assert!(matches!(
1091 err,
1092 MemoryRegistryError::HistoricalRangeConflict { .. }
1093 ));
1094 }
1095
1096 #[test]
1097 fn rejects_layout_metadata_id_on_register() {
1098 reset_for_tests();
1099
1100 MemoryRegistry::reserve_range("crate_a", 100, 108).expect("reserve range");
1101 let err = MemoryRegistry::register(0, "crate_a", "slot")
1102 .expect_err("layout metadata ID must not be registrable by applications");
1103 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
1104 }
1105
1106 #[test]
1107 fn rejects_layout_metadata_id_on_deferred_register() {
1108 reset_for_tests();
1109
1110 let err = defer_register(0, "crate_a", "slot")
1111 .expect_err("layout metadata ID should fail before init");
1112 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
1113 }
1114
1115 #[test]
1116 fn rejects_internal_reserved_id_on_range_reservation() {
1117 reset_for_tests();
1118
1119 let err = MemoryRegistry::reserve_range("crate_a", 250, u8::MAX)
1120 .expect_err("reserved internal id must not be reservable");
1121 assert!(matches!(
1122 err,
1123 MemoryRegistryError::ReservedInternalId { .. }
1124 ));
1125 }
1126
1127 #[test]
1128 fn rejects_internal_reserved_id_on_deferred_register() {
1129 reset_for_tests();
1130
1131 let err = defer_register(u8::MAX, "crate_a", "slot")
1132 .expect_err("reserved id should fail before init");
1133 assert!(matches!(
1134 err,
1135 MemoryRegistryError::ReservedInternalId { .. }
1136 ));
1137 }
1138
1139 #[test]
1140 fn rejects_internal_reserved_id_on_deferred_range_reservation() {
1141 reset_for_tests();
1142
1143 let err = defer_reserve_range("crate_a", 240, u8::MAX)
1144 .expect_err("reserved id should fail before init");
1145 assert!(matches!(
1146 err,
1147 MemoryRegistryError::ReservedInternalId { .. }
1148 ));
1149 }
1150}