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 pub schema_version: Option<u32>,
41 pub schema_fingerprint: Option<String>,
43}
44
45#[derive(Clone, Debug)]
51pub struct MemoryRangeEntry {
52 pub owner: String,
54 pub range: MemoryRange,
56}
57
58#[derive(Clone, Debug)]
64pub struct MemoryRangeSnapshot {
65 pub owner: String,
67 pub range: MemoryRange,
69 pub entries: Vec<(u8, MemoryRegistryEntry)>,
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct MemoryRangeAuthority {
80 pub owner: String,
82 pub range: MemoryRange,
84 pub purpose: String,
86}
87
88#[derive(Clone, Debug, Eq, PartialEq)]
94pub(crate) struct PendingRegistration {
95 pub id: u8,
96 pub crate_name: String,
97 pub label: String,
98 pub stable_key: String,
99 pub schema_version: Option<u32>,
100 pub schema_fingerprint: Option<String>,
101}
102
103#[derive(Debug, ThisError)]
109pub enum MemoryRegistryError {
110 #[error(
112 "memory range overlap: crate '{existing_crate}' [{existing_start}-{existing_end}]
113conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
114 )]
115 Overlap {
116 existing_crate: String,
118 existing_start: u8,
120 existing_end: u8,
122 new_crate: String,
124 new_start: u8,
126 new_end: u8,
128 },
129
130 #[error("memory range is invalid: start={start} end={end}")]
132 InvalidRange {
133 start: u8,
135 end: u8,
137 },
138
139 #[error("memory id {0} is already registered; each memory id must be globally unique")]
141 DuplicateId(u8),
142
143 #[error(
145 "memory stable key '{0}' is declared more than once; each stable key must be globally unique"
146 )]
147 DuplicateStableKey(String),
148
149 #[error("memory id {id} has no reserved range for crate '{crate_name}'")]
151 NoReservedRange {
152 crate_name: String,
154 id: u8,
156 },
157
158 #[error(
160 "memory id {id} reserved to crate '{owner}' [{owner_start}-{owner_end}], not '{crate_name}'"
161 )]
162 IdOwnedByOther {
163 crate_name: String,
165 id: u8,
167 owner: String,
169 owner_start: u8,
171 owner_end: u8,
173 },
174
175 #[error("memory id {id} is outside reserved ranges for crate '{crate_name}'")]
177 IdOutOfRange {
178 crate_name: String,
180 id: u8,
182 },
183
184 #[error(
186 "memory id {id} is the unallocated-bucket sentinel and is not a usable virtual memory id"
187 )]
188 ReservedInternalId {
189 id: u8,
191 },
192
193 #[error(
195 "memory range historical conflict: crate '{existing_crate}' [{existing_start}-{existing_end}]
196conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
197 )]
198 HistoricalRangeConflict {
199 existing_crate: String,
201 existing_start: u8,
203 existing_end: u8,
205 new_crate: String,
207 new_start: u8,
209 new_end: u8,
211 },
212
213 #[error(
215 "memory id {id} was historically registered to crate '{existing_crate}' label '{existing_label}', not crate '{new_crate}' label '{new_label}'"
216 )]
217 HistoricalIdConflict {
218 id: u8,
220 existing_crate: String,
222 existing_label: String,
224 new_crate: String,
226 new_label: String,
228 new_stable_key: String,
230 },
231
232 #[error(
234 "memory stable key '{stable_key}' was historically registered to id {existing_id}, not id {new_id}"
235 )]
236 HistoricalStableKeyConflict {
237 stable_key: String,
239 existing_id: u8,
241 new_id: u8,
243 },
244
245 #[error("memory stable key '{stable_key}' is invalid: {reason}")]
247 InvalidStableKey {
248 stable_key: String,
250 reason: &'static str,
252 },
253
254 #[error("memory schema metadata is invalid for stable key '{stable_key}': {reason}")]
256 InvalidSchemaMetadata {
257 stable_key: String,
259 reason: &'static str,
261 },
262
263 #[error(
265 "memory stable key '{stable_key}' with id {id} violates namespace/range authority: {reason}"
266 )]
267 RangeAuthorityViolation {
268 stable_key: String,
270 id: u8,
272 reason: &'static str,
274 },
275
276 #[error(
278 "memory registration after bootstrap is sealed is not allowed: {ranges} range(s), {registrations} registration(s)"
279 )]
280 RegistrationAfterBootstrap {
281 ranges: usize,
283 registrations: usize,
285 },
286
287 #[error("memory registry has not completed bootstrap validation")]
289 RegistryNotBootstrapped,
290
291 #[error("memory layout ledger is corrupt: {reason}")]
293 LedgerCorrupt {
294 reason: &'static str,
296 },
297}
298
299thread_local! {
304 static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
305 static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
306
307 static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
309 static PENDING_REGISTRATIONS: RefCell<Vec<PendingRegistration>> = const { RefCell::new(Vec::new()) };
310}
311
312pub struct MemoryRegistry;
318
319impl MemoryRegistry {
320 pub(crate) fn reserve_internal_layout_ledger() -> Result<(), MemoryRegistryError> {
322 Self::reserve_range(
323 ledger::MEMORY_LAYOUT_LEDGER_OWNER,
324 ledger::MEMORY_LAYOUT_RESERVED_MIN,
325 ledger::MEMORY_LAYOUT_RESERVED_MAX,
326 )?;
327
328 if let Some(entry) = Self::get(ledger::MEMORY_LAYOUT_LEDGER_ID)
329 && entry.crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
330 && entry.label == ledger::MEMORY_LAYOUT_LEDGER_LABEL
331 && entry.stable_key == ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY
332 {
333 return Ok(());
334 }
335
336 Self::register_with_key(
337 ledger::MEMORY_LAYOUT_LEDGER_ID,
338 ledger::MEMORY_LAYOUT_LEDGER_OWNER,
339 ledger::MEMORY_LAYOUT_LEDGER_LABEL,
340 ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY,
341 )
342 }
343
344 pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
349 if start > end {
350 return Err(MemoryRegistryError::InvalidRange { start, end });
351 }
352 validate_range_excludes_reserved_internal_id(start, end)?;
353 validate_range_excludes_layout_metadata(crate_name, start, end)?;
354
355 let range = MemoryRange { start, end };
356 let mut already_reserved = false;
357
358 RESERVED_RANGES.with_borrow(|ranges| {
359 for (existing_crate, existing_range) in ranges {
360 if ranges_overlap(*existing_range, range) {
361 if existing_crate == crate_name
362 && existing_range.start == start
363 && existing_range.end == end
364 {
365 already_reserved = true;
367 return Ok(());
368 }
369 return Err(MemoryRegistryError::Overlap {
370 existing_crate: existing_crate.clone(),
371 existing_start: existing_range.start,
372 existing_end: existing_range.end,
373 new_crate: crate_name.to_string(),
374 new_start: start,
375 new_end: end,
376 });
377 }
378 }
379
380 Ok(())
381 })?;
382
383 ledger::record_range(crate_name, range)?;
384
385 if already_reserved {
386 return Ok(());
387 }
388
389 RESERVED_RANGES.with_borrow_mut(|ranges| {
390 ranges.push((crate_name.to_string(), range));
391 });
392
393 Ok(())
394 }
395
396 pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
398 Self::register_with_key(
399 id,
400 crate_name,
401 label,
402 &fallback_stable_key(crate_name, label),
403 )
404 }
405
406 pub fn register_with_key(
408 id: u8,
409 crate_name: &str,
410 label: &str,
411 stable_key: &str,
412 ) -> Result<(), MemoryRegistryError> {
413 Self::register_with_key_metadata(id, crate_name, label, stable_key, None, None)
414 }
415
416 pub fn register_with_key_metadata(
418 id: u8,
419 crate_name: &str,
420 label: &str,
421 stable_key: &str,
422 schema_version: Option<u32>,
423 schema_fingerprint: Option<&str>,
424 ) -> Result<(), MemoryRegistryError> {
425 validate_non_internal_id(id)?;
426 validate_id_excludes_layout_metadata(crate_name, id)?;
427 validate_registration_range(crate_name, id)?;
428 validate_stable_key(stable_key)?;
429 validate_schema_metadata(stable_key, schema_version, schema_fingerprint)?;
430 validate_id_authority(id, crate_name, stable_key)?;
431
432 REGISTRY.with_borrow(|reg| {
433 if reg.contains_key(&id) {
434 return Err(MemoryRegistryError::DuplicateId(id));
435 }
436 Ok(())
437 })?;
438
439 ledger::record_entry(
440 id,
441 crate_name,
442 label,
443 stable_key,
444 schema_version,
445 schema_fingerprint,
446 )?;
447
448 REGISTRY.with_borrow_mut(|reg| {
449 reg.insert(
450 id,
451 MemoryRegistryEntry {
452 crate_name: crate_name.to_string(),
453 label: label.to_string(),
454 stable_key: stable_key.to_string(),
455 schema_version,
456 schema_fingerprint: schema_fingerprint.map(str::to_string),
457 },
458 );
459 });
460
461 Ok(())
462 }
463
464 #[must_use]
466 pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
467 REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
468 }
469
470 #[must_use]
472 pub fn export_ranges() -> Vec<(String, MemoryRange)> {
473 RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
474 }
475
476 #[must_use]
478 pub fn export_range_entries() -> Vec<MemoryRangeEntry> {
479 RESERVED_RANGES.with_borrow(|ranges| {
480 ranges
481 .iter()
482 .map(|(owner, range)| MemoryRangeEntry {
483 owner: owner.clone(),
484 range: *range,
485 })
486 .collect()
487 })
488 }
489
490 #[must_use]
492 pub fn export_ids_by_range() -> Vec<MemoryRangeSnapshot> {
493 let mut ranges = RESERVED_RANGES.with_borrow(std::clone::Clone::clone);
494 let entries = REGISTRY.with_borrow(std::clone::Clone::clone);
495
496 ranges.sort_by_key(|(_, range)| range.start);
497
498 ranges
499 .into_iter()
500 .map(|(owner, range)| {
501 let entries = entries
502 .iter()
503 .filter(|(id, _)| range.contains(**id))
504 .map(|(id, entry)| (*id, entry.clone()))
505 .collect();
506
507 MemoryRangeSnapshot {
508 owner,
509 range,
510 entries,
511 }
512 })
513 .collect()
514 }
515
516 #[must_use]
518 pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
519 REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
520 }
521
522 #[must_use]
524 pub fn export_historical_ranges() -> Vec<(String, MemoryRange)> {
525 ledger::export_ranges()
526 }
527
528 #[must_use]
530 pub fn export_historical_authorities() -> Vec<MemoryRangeAuthority> {
531 ledger::export_authorities()
532 }
533
534 pub fn try_export_historical_ranges() -> Result<Vec<(String, MemoryRange)>, MemoryRegistryError>
536 {
537 ledger::try_export_ranges()
538 }
539
540 pub fn try_export_historical_authorities()
542 -> Result<Vec<MemoryRangeAuthority>, MemoryRegistryError> {
543 ledger::try_export_authorities()
544 }
545
546 #[must_use]
548 pub fn export_historical() -> Vec<(u8, MemoryRegistryEntry)> {
549 ledger::export_entries()
550 }
551
552 pub fn try_export_historical() -> Result<Vec<(u8, MemoryRegistryEntry)>, MemoryRegistryError> {
554 ledger::try_export_entries()
555 }
556}
557
558#[doc(hidden)]
564pub fn defer_reserve_range(
565 crate_name: &str,
566 start: u8,
567 end: u8,
568) -> Result<(), MemoryRegistryError> {
569 if start > end {
570 return Err(MemoryRegistryError::InvalidRange { start, end });
571 }
572 validate_range_excludes_reserved_internal_id(start, end)?;
573 validate_range_excludes_layout_metadata(crate_name, start, end)?;
574
575 PENDING_RANGES.with_borrow_mut(|ranges| {
577 ranges.push((crate_name.to_string(), start, end));
578 });
579
580 Ok(())
581}
582
583#[doc(hidden)]
585pub fn defer_register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
586 defer_register_with_key(
587 id,
588 crate_name,
589 label,
590 &fallback_stable_key(crate_name, label),
591 )
592}
593
594#[doc(hidden)]
596pub fn defer_register_with_key(
597 id: u8,
598 crate_name: &str,
599 label: &str,
600 stable_key: &str,
601) -> Result<(), MemoryRegistryError> {
602 defer_register_with_key_metadata(id, crate_name, label, stable_key, None, None)
603}
604
605#[doc(hidden)]
607pub fn defer_register_with_key_metadata(
608 id: u8,
609 crate_name: &str,
610 label: &str,
611 stable_key: &str,
612 schema_version: Option<u32>,
613 schema_fingerprint: Option<&str>,
614) -> Result<(), MemoryRegistryError> {
615 validate_non_internal_id(id)?;
616 validate_id_excludes_layout_metadata(crate_name, id)?;
617 validate_stable_key(stable_key)?;
618 validate_schema_metadata(stable_key, schema_version, schema_fingerprint)?;
619 validate_id_authority(id, crate_name, stable_key)?;
620
621 PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
623 regs.push(PendingRegistration {
624 id,
625 crate_name: crate_name.to_string(),
626 label: label.to_string(),
627 stable_key: stable_key.to_string(),
628 schema_version,
629 schema_fingerprint: schema_fingerprint.map(str::to_string),
630 });
631 });
632
633 Ok(())
634}
635
636#[must_use]
638pub(crate) fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
639 PENDING_RANGES.with_borrow_mut(std::mem::take)
640}
641
642#[must_use]
644pub(crate) fn drain_pending_registrations() -> Vec<PendingRegistration> {
645 PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
646}
647
648#[cfg(test)]
653pub fn reset_for_tests() {
655 reset_runtime_for_tests();
656 ledger::reset_for_tests();
657 crate::runtime::registry::reset_initialized_for_tests();
658}
659
660#[cfg(test)]
661fn reset_runtime_for_tests() {
662 RESERVED_RANGES.with_borrow_mut(Vec::clear);
663 REGISTRY.with_borrow_mut(BTreeMap::clear);
664 PENDING_RANGES.with_borrow_mut(Vec::clear);
665 PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
666}
667
668const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
673 a.start <= b.end && b.start <= a.end
674}
675
676const INTERNAL_RESERVED_MEMORY_ID: u8 = u8::MAX;
677const CANIC_FRAMEWORK_MAX_ID: u8 = 99;
678const APPLICATION_MIN_ID: u8 = 100;
679
680const MEMORY_LAYOUT_RESERVED_RANGE: MemoryRange = MemoryRange {
681 start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
682 end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
683};
684
685const fn validate_non_internal_id(id: u8) -> Result<(), MemoryRegistryError> {
686 if id == INTERNAL_RESERVED_MEMORY_ID {
687 return Err(MemoryRegistryError::ReservedInternalId { id });
688 }
689 Ok(())
690}
691
692fn fallback_stable_key(crate_name: &str, label: &str) -> String {
693 format!(
694 "legacy.{}.{}.v1",
695 canonical_segment(crate_name),
696 canonical_segment(label)
697 )
698}
699
700fn validate_stable_key(stable_key: &str) -> Result<(), MemoryRegistryError> {
701 if stable_key.is_empty() {
702 return invalid_stable_key(stable_key, "must not be empty");
703 }
704 if stable_key.len() > 128 {
705 return invalid_stable_key(stable_key, "must be at most 128 bytes");
706 }
707 if !stable_key.is_ascii() {
708 return invalid_stable_key(stable_key, "must be ASCII");
709 }
710 if stable_key.bytes().any(|b| b.is_ascii_uppercase()) {
711 return invalid_stable_key(stable_key, "must be lowercase");
712 }
713 if stable_key.contains(char::is_whitespace) {
714 return invalid_stable_key(stable_key, "must not contain whitespace");
715 }
716 if stable_key.contains('/') || stable_key.contains('-') {
717 return invalid_stable_key(stable_key, "must not contain slashes or hyphens");
718 }
719 if stable_key.starts_with('.') || stable_key.ends_with('.') {
720 return invalid_stable_key(stable_key, "must not start or end with a dot");
721 }
722
723 let Some(version_index) = stable_key.rfind(".v") else {
724 return invalid_stable_key(stable_key, "must end with .vN");
725 };
726 let version = &stable_key[version_index + 2..];
727 if version.is_empty()
728 || version.starts_with('0')
729 || !version.bytes().all(|b| b.is_ascii_digit())
730 {
731 return invalid_stable_key(stable_key, "version suffix must be nonzero .vN");
732 }
733
734 let prefix = &stable_key[..version_index];
735 if prefix.is_empty() {
736 return invalid_stable_key(
737 stable_key,
738 "must contain at least one segment before version",
739 );
740 }
741
742 for segment in prefix.split('.') {
743 validate_stable_key_segment(stable_key, segment)?;
744 }
745
746 Ok(())
747}
748
749fn validate_stable_key_segment(stable_key: &str, segment: &str) -> Result<(), MemoryRegistryError> {
750 if segment.is_empty() {
751 return invalid_stable_key(stable_key, "must not contain empty segments");
752 }
753 let mut bytes = segment.bytes();
754 let Some(first) = bytes.next() else {
755 return invalid_stable_key(stable_key, "must not contain empty segments");
756 };
757 if !first.is_ascii_lowercase() {
758 return invalid_stable_key(stable_key, "segments must start with a lowercase letter");
759 }
760 if !bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') {
761 return invalid_stable_key(
762 stable_key,
763 "segments may contain only lowercase letters, digits, and underscores",
764 );
765 }
766 Ok(())
767}
768
769fn validate_schema_metadata(
770 stable_key: &str,
771 schema_version: Option<u32>,
772 schema_fingerprint: Option<&str>,
773) -> Result<(), MemoryRegistryError> {
774 if schema_version == Some(0) {
775 return Err(MemoryRegistryError::InvalidSchemaMetadata {
776 stable_key: stable_key.to_string(),
777 reason: "schema_version must be greater than zero when present",
778 });
779 }
780
781 let Some(fingerprint) = schema_fingerprint else {
782 return Ok(());
783 };
784
785 if fingerprint.is_empty() {
786 return Err(MemoryRegistryError::InvalidSchemaMetadata {
787 stable_key: stable_key.to_string(),
788 reason: "schema_fingerprint must not be empty when present",
789 });
790 }
791 if fingerprint.len() > 256 {
792 return Err(MemoryRegistryError::InvalidSchemaMetadata {
793 stable_key: stable_key.to_string(),
794 reason: "schema_fingerprint must be at most 256 bytes",
795 });
796 }
797 if !fingerprint.is_ascii() {
798 return Err(MemoryRegistryError::InvalidSchemaMetadata {
799 stable_key: stable_key.to_string(),
800 reason: "schema_fingerprint must be ASCII",
801 });
802 }
803 if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
804 return Err(MemoryRegistryError::InvalidSchemaMetadata {
805 stable_key: stable_key.to_string(),
806 reason: "schema_fingerprint must not contain ASCII control characters",
807 });
808 }
809
810 Ok(())
811}
812
813fn invalid_stable_key<T>(stable_key: &str, reason: &'static str) -> Result<T, MemoryRegistryError> {
814 Err(MemoryRegistryError::InvalidStableKey {
815 stable_key: stable_key.to_string(),
816 reason,
817 })
818}
819
820fn validate_id_authority(
821 id: u8,
822 crate_name: &str,
823 stable_key: &str,
824) -> Result<(), MemoryRegistryError> {
825 if stable_key.starts_with("canic.") {
826 if !crate_name.starts_with("canic") {
827 return Err(MemoryRegistryError::RangeAuthorityViolation {
828 stable_key: stable_key.to_string(),
829 id,
830 reason: "canic.* keys may only be declared by Canic framework crates",
831 });
832 }
833
834 if id <= CANIC_FRAMEWORK_MAX_ID {
835 return Ok(());
836 }
837
838 return Err(MemoryRegistryError::RangeAuthorityViolation {
839 stable_key: stable_key.to_string(),
840 id,
841 reason: "canic.* keys must use ids 0-99",
842 });
843 }
844
845 if (APPLICATION_MIN_ID..INTERNAL_RESERVED_MEMORY_ID).contains(&id) {
846 return Ok(());
847 }
848
849 Err(MemoryRegistryError::RangeAuthorityViolation {
850 stable_key: stable_key.to_string(),
851 id,
852 reason: "application keys must use ids 100-254",
853 })
854}
855
856fn canonical_segment(value: &str) -> String {
857 let mut out = String::new();
858 let mut last_was_underscore = false;
859
860 for ch in value.chars().flat_map(char::to_lowercase) {
861 if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
862 out.push(ch);
863 last_was_underscore = false;
864 } else if !last_was_underscore {
865 out.push('_');
866 last_was_underscore = true;
867 }
868 }
869
870 let trimmed = out.trim_matches('_');
871 if trimmed.is_empty() {
872 return "unnamed".to_string();
873 }
874 if trimmed.as_bytes()[0].is_ascii_digit() {
875 return format!("n_{trimmed}");
876 }
877 trimmed.to_string()
878}
879
880const fn validate_range_excludes_reserved_internal_id(
881 _start: u8,
882 end: u8,
883) -> Result<(), MemoryRegistryError> {
884 if end == INTERNAL_RESERVED_MEMORY_ID {
885 return Err(MemoryRegistryError::ReservedInternalId {
886 id: INTERNAL_RESERVED_MEMORY_ID,
887 });
888 }
889 Ok(())
890}
891
892fn validate_range_excludes_layout_metadata(
893 crate_name: &str,
894 start: u8,
895 end: u8,
896) -> Result<(), MemoryRegistryError> {
897 let requested = MemoryRange { start, end };
898 if !ranges_overlap(requested, MEMORY_LAYOUT_RESERVED_RANGE) {
899 return Ok(());
900 }
901
902 if crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
903 && start == ledger::MEMORY_LAYOUT_RESERVED_MIN
904 && end == ledger::MEMORY_LAYOUT_RESERVED_MAX
905 {
906 return Ok(());
907 }
908
909 Err(MemoryRegistryError::HistoricalRangeConflict {
910 existing_crate: ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
911 existing_start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
912 existing_end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
913 new_crate: crate_name.to_string(),
914 new_start: start,
915 new_end: end,
916 })
917}
918
919fn validate_id_excludes_layout_metadata(
920 crate_name: &str,
921 id: u8,
922) -> Result<(), MemoryRegistryError> {
923 if !MEMORY_LAYOUT_RESERVED_RANGE.contains(id)
924 || crate_name == ledger::MEMORY_LAYOUT_LEDGER_OWNER
925 {
926 return Ok(());
927 }
928
929 Err(MemoryRegistryError::IdOwnedByOther {
930 crate_name: crate_name.to_string(),
931 id,
932 owner: ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
933 owner_start: ledger::MEMORY_LAYOUT_RESERVED_MIN,
934 owner_end: ledger::MEMORY_LAYOUT_RESERVED_MAX,
935 })
936}
937
938fn validate_registration_range(crate_name: &str, id: u8) -> Result<(), MemoryRegistryError> {
939 let mut has_range = false;
940 let mut owner_match = false;
941 let mut owner_for_id: Option<(String, MemoryRange)> = None;
942
943 RESERVED_RANGES.with_borrow(|ranges| {
944 for (owner, range) in ranges {
945 if owner == crate_name {
946 has_range = true;
947 if range.contains(id) {
948 owner_match = true;
949 break;
950 }
951 }
952
953 if owner_for_id.is_none() && range.contains(id) {
954 owner_for_id = Some((owner.clone(), *range));
955 }
956 }
957 });
958
959 if owner_match {
960 return Ok(());
961 }
962
963 if !has_range {
964 return Err(MemoryRegistryError::NoReservedRange {
965 crate_name: crate_name.to_string(),
966 id,
967 });
968 }
969
970 if let Some((owner, range)) = owner_for_id {
971 return Err(MemoryRegistryError::IdOwnedByOther {
972 crate_name: crate_name.to_string(),
973 id,
974 owner,
975 owner_start: range.start,
976 owner_end: range.end,
977 });
978 }
979
980 Err(MemoryRegistryError::IdOutOfRange {
981 crate_name: crate_name.to_string(),
982 id,
983 })
984}
985
986#[cfg(test)]
991mod tests {
992 use super::*;
993
994 #[test]
995 fn allows_in_range() {
996 reset_for_tests();
997
998 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range");
999 MemoryRegistry::register(101, "crate_a", "slot").expect("register in range");
1000 }
1001
1002 #[test]
1003 fn rejects_unreserved() {
1004 reset_for_tests();
1005
1006 let err = MemoryRegistry::register(100, "crate_a", "slot").expect_err("missing range");
1007 assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
1008 }
1009
1010 #[test]
1011 fn rejects_other_owner() {
1012 reset_for_tests();
1013
1014 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1015 MemoryRegistry::reserve_range("crate_b", 110, 112).expect("reserve range B");
1016
1017 let err = MemoryRegistry::register(101, "crate_b", "slot").expect_err("owned by other");
1018 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
1019 }
1020
1021 #[test]
1022 fn export_ids_by_range_groups_entries() {
1023 reset_for_tests();
1024
1025 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1026 MemoryRegistry::reserve_range("crate_b", 110, 112).expect("reserve range B");
1027 MemoryRegistry::register(100, "crate_a", "a100").expect("register a100");
1028 MemoryRegistry::register(111, "crate_b", "b111").expect("register b111");
1029
1030 let snapshots = MemoryRegistry::export_ids_by_range();
1031 assert_eq!(snapshots.len(), 2);
1032 assert_eq!(snapshots[0].entries.len(), 1);
1033 assert_eq!(snapshots[1].entries.len(), 1);
1034 }
1035
1036 #[test]
1037 fn historical_range_conflict_survives_runtime_reset() {
1038 reset_for_tests();
1039
1040 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1041 reset_runtime_for_tests();
1042
1043 let err = MemoryRegistry::reserve_range("crate_b", 101, 103)
1044 .expect_err("historical range overlap should fail");
1045 assert!(matches!(
1046 err,
1047 MemoryRegistryError::HistoricalRangeConflict { .. }
1048 ));
1049 }
1050
1051 #[test]
1052 fn historical_id_conflict_survives_runtime_reset() {
1053 reset_for_tests();
1054
1055 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1056 MemoryRegistry::register_with_key_metadata(
1057 100,
1058 "crate_a",
1059 "slot",
1060 "app.crate_a.slot.v1",
1061 Some(1),
1062 Some("sha256:aaa"),
1063 )
1064 .expect("register slot");
1065 reset_runtime_for_tests();
1066
1067 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A again");
1068 let err =
1069 MemoryRegistry::register_with_key(100, "crate_a", "other", "app.crate_a.other.v1")
1070 .expect_err("historical id label drift should fail");
1071 assert!(matches!(
1072 err,
1073 MemoryRegistryError::HistoricalIdConflict { .. }
1074 ));
1075 }
1076
1077 #[test]
1078 fn stable_key_allows_owner_and_label_metadata_drift() {
1079 reset_for_tests();
1080
1081 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1082 MemoryRegistry::register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
1083 .expect("register slot");
1084 reset_runtime_for_tests();
1085
1086 MemoryRegistry::reserve_range("crate_renamed", 100, 102).expect("reserve range A again");
1087 MemoryRegistry::register_with_key_metadata(
1088 100,
1089 "crate_renamed",
1090 "SlotRenamed",
1091 "app.crate_a.slot.v1",
1092 Some(2),
1093 Some("sha256:bbb"),
1094 )
1095 .expect("stable key should survive owner/label drift");
1096
1097 let entry = MemoryRegistry::get(100).expect("entry should exist");
1098 assert_eq!(entry.crate_name, "crate_renamed");
1099 assert_eq!(entry.label, "SlotRenamed");
1100 assert_eq!(entry.stable_key, "app.crate_a.slot.v1");
1101 assert_eq!(entry.schema_version, Some(2));
1102 assert_eq!(entry.schema_fingerprint.as_deref(), Some("sha256:bbb"));
1103 }
1104
1105 #[test]
1106 fn rejects_invalid_schema_metadata() {
1107 reset_for_tests();
1108
1109 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1110 let err = MemoryRegistry::register_with_key_metadata(
1111 100,
1112 "crate_a",
1113 "slot",
1114 "app.crate_a.slot.v1",
1115 Some(0),
1116 None,
1117 )
1118 .expect_err("zero schema version should fail");
1119 assert!(matches!(
1120 err,
1121 MemoryRegistryError::InvalidSchemaMetadata { .. }
1122 ));
1123
1124 let err = MemoryRegistry::register_with_key_metadata(
1125 100,
1126 "crate_a",
1127 "slot",
1128 "app.crate_a.slot.v1",
1129 Some(1),
1130 Some("fingerprint\nwith-control"),
1131 )
1132 .expect_err("control characters should fail");
1133 assert!(matches!(
1134 err,
1135 MemoryRegistryError::InvalidSchemaMetadata { .. }
1136 ));
1137 }
1138
1139 #[test]
1140 fn rejects_non_canic_key_below_application_range() {
1141 reset_for_tests();
1142
1143 MemoryRegistry::reserve_range("crate_a", 1, 4).expect("reserve low framework range");
1144 let err = MemoryRegistry::register_with_key(1, "crate_a", "slot", "app.crate_a.slot.v1")
1145 .expect_err("application key below 100 should fail");
1146 assert!(matches!(
1147 err,
1148 MemoryRegistryError::RangeAuthorityViolation { .. }
1149 ));
1150 }
1151
1152 #[test]
1153 fn rejects_canic_key_above_framework_range() {
1154 reset_for_tests();
1155
1156 MemoryRegistry::reserve_range("canic-core", 100, 102).expect("reserve app range");
1157 let err =
1158 MemoryRegistry::register_with_key(100, "canic-core", "slot", "canic.core.slot.v1")
1159 .expect_err("canic key above 99 should fail");
1160 assert!(matches!(
1161 err,
1162 MemoryRegistryError::RangeAuthorityViolation { .. }
1163 ));
1164 }
1165
1166 #[test]
1167 fn rejects_canic_namespace_from_non_framework_crate() {
1168 reset_for_tests();
1169
1170 MemoryRegistry::reserve_range("crate_a", 11, 12).expect("reserve framework range");
1171 let err = MemoryRegistry::register_with_key(11, "crate_a", "slot", "canic.core.slot.v1")
1172 .expect_err("non-framework crate must not claim canic namespace");
1173 assert!(matches!(
1174 err,
1175 MemoryRegistryError::RangeAuthorityViolation { .. }
1176 ));
1177 }
1178
1179 #[test]
1180 fn rejects_non_canonical_stable_key() {
1181 reset_for_tests();
1182
1183 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1184 let err = MemoryRegistry::register_with_key(100, "crate_a", "slot", "App.Crate.Slot.v1")
1185 .expect_err("uppercase stable key should fail");
1186 assert!(matches!(err, MemoryRegistryError::InvalidStableKey { .. }));
1187 }
1188
1189 #[test]
1190 fn internal_layout_ledger_uses_only_id_zero_self_record() {
1191 reset_for_tests();
1192
1193 MemoryRegistry::reserve_internal_layout_ledger().expect("reserve ledger");
1194
1195 assert_eq!(
1196 MemoryRegistry::export_ranges(),
1197 vec![(
1198 ledger::MEMORY_LAYOUT_LEDGER_OWNER.to_string(),
1199 MemoryRange { start: 0, end: 0 },
1200 )]
1201 );
1202 assert_eq!(
1203 MemoryRegistry::get(0).map(|entry| entry.stable_key),
1204 Some(ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY.to_string())
1205 );
1206 for id in 1..=4 {
1207 assert!(MemoryRegistry::get(id).is_none());
1208 }
1209 }
1210
1211 #[test]
1212 fn historical_stable_key_conflict_survives_runtime_reset() {
1213 reset_for_tests();
1214
1215 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A");
1216 MemoryRegistry::register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
1217 .expect("register slot");
1218 reset_runtime_for_tests();
1219
1220 MemoryRegistry::reserve_range("crate_a", 100, 102).expect("reserve range A again");
1221 let err = MemoryRegistry::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
1222 .expect_err("stable key must not move to another ID");
1223 assert!(matches!(
1224 err,
1225 MemoryRegistryError::HistoricalStableKeyConflict { .. }
1226 ));
1227 }
1228
1229 #[test]
1230 fn rejects_internal_reserved_id_on_register() {
1231 reset_for_tests();
1232
1233 MemoryRegistry::reserve_range("crate_a", 5, 254).expect("reserve range");
1234 let err = MemoryRegistry::register(u8::MAX, "crate_a", "slot")
1235 .expect_err("reserved id should be rejected");
1236 assert!(matches!(
1237 err,
1238 MemoryRegistryError::ReservedInternalId { .. }
1239 ));
1240 }
1241
1242 #[test]
1243 fn rejects_layout_metadata_range_reservation() {
1244 reset_for_tests();
1245
1246 let err = MemoryRegistry::reserve_range("crate_a", 0, 4)
1247 .expect_err("layout metadata range must not be reservable by applications");
1248 assert!(matches!(
1249 err,
1250 MemoryRegistryError::HistoricalRangeConflict { .. }
1251 ));
1252 }
1253
1254 #[test]
1255 fn rejects_layout_metadata_range_overlap() {
1256 reset_for_tests();
1257
1258 let err = MemoryRegistry::reserve_range("crate_a", 0, 8)
1259 .expect_err("layout metadata overlap must not be reservable by applications");
1260 assert!(matches!(
1261 err,
1262 MemoryRegistryError::HistoricalRangeConflict { .. }
1263 ));
1264 }
1265
1266 #[test]
1267 fn rejects_layout_metadata_range_on_deferred_reservation() {
1268 reset_for_tests();
1269
1270 let err = defer_reserve_range("crate_a", 0, 4)
1271 .expect_err("layout metadata range should fail before init");
1272 assert!(matches!(
1273 err,
1274 MemoryRegistryError::HistoricalRangeConflict { .. }
1275 ));
1276 }
1277
1278 #[test]
1279 fn rejects_layout_metadata_id_on_register() {
1280 reset_for_tests();
1281
1282 MemoryRegistry::reserve_range("crate_a", 100, 108).expect("reserve range");
1283 let err = MemoryRegistry::register(0, "crate_a", "slot")
1284 .expect_err("layout metadata ID must not be registrable by applications");
1285 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
1286 }
1287
1288 #[test]
1289 fn rejects_layout_metadata_id_on_deferred_register() {
1290 reset_for_tests();
1291
1292 let err = defer_register(0, "crate_a", "slot")
1293 .expect_err("layout metadata ID should fail before init");
1294 assert!(matches!(err, MemoryRegistryError::IdOwnedByOther { .. }));
1295 }
1296
1297 #[test]
1298 fn rejects_internal_reserved_id_on_range_reservation() {
1299 reset_for_tests();
1300
1301 let err = MemoryRegistry::reserve_range("crate_a", 250, u8::MAX)
1302 .expect_err("reserved internal id must not be reservable");
1303 assert!(matches!(
1304 err,
1305 MemoryRegistryError::ReservedInternalId { .. }
1306 ));
1307 }
1308
1309 #[test]
1310 fn rejects_internal_reserved_id_on_deferred_register() {
1311 reset_for_tests();
1312
1313 let err = defer_register(u8::MAX, "crate_a", "slot")
1314 .expect_err("reserved id should fail before init");
1315 assert!(matches!(
1316 err,
1317 MemoryRegistryError::ReservedInternalId { .. }
1318 ));
1319 }
1320
1321 #[test]
1322 fn rejects_internal_reserved_id_on_deferred_range_reservation() {
1323 reset_for_tests();
1324
1325 let err = defer_reserve_range("crate_a", 240, u8::MAX)
1326 .expect_err("reserved id should fail before init");
1327 assert!(matches!(
1328 err,
1329 MemoryRegistryError::ReservedInternalId { .. }
1330 ));
1331 }
1332}