1use crate::ledger;
2use crate::{
3 cdk::structures::{
4 DefaultMemoryImpl,
5 memory::{MemoryId, VirtualMemory},
6 },
7 manager::MEMORY_MANAGER,
8 registry::{
9 MemoryRange, MemoryRangeAuthority, MemoryRegistry, MemoryRegistryError, defer_register,
10 defer_register_with_key_metadata,
11 },
12 runtime::{MemoryRuntimeApi, registry::MemoryRegistryRuntime},
13};
14
15pub struct MemoryApi;
22
23#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct MemoryInspection {
30 pub id: u8,
32 pub owner: String,
34 pub range: MemoryRange,
36 pub label: Option<String>,
38 pub stable_key: Option<String>,
40 pub schema_version: Option<u32>,
42 pub schema_fingerprint: Option<String>,
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct RegisteredMemory {
53 pub id: u8,
55 pub owner: String,
57 pub range: MemoryRange,
59 pub label: String,
61 pub stable_key: String,
63 pub schema_version: Option<u32>,
65 pub schema_fingerprint: Option<String>,
67}
68
69#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct LedgerSnapshot {
76 pub magic: u64,
78 pub format_id: u32,
80 pub schema_version: u32,
82 pub layout_epoch: u32,
84 pub header_len: u32,
86 pub header_checksum: u64,
88 pub current_generation: u64,
90 pub authorities: Vec<MemoryRangeAuthority>,
92 pub ranges: Vec<(String, MemoryRange)>,
94 pub entries: Vec<(u8, crate::registry::MemoryRegistryEntry)>,
96}
97
98impl MemoryApi {
99 pub fn bootstrap_owner_range(
101 crate_name: &'static str,
102 start: u8,
103 end: u8,
104 ) -> Result<(), MemoryRegistryError> {
105 let _ = MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)?;
106 Ok(())
107 }
108
109 pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
112 let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
113 Ok(())
114 }
115
116 pub fn declare(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
120 if MemoryRegistryRuntime::is_initialized() {
121 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
122 ranges: 0,
123 registrations: 1,
124 });
125 }
126
127 defer_register(id, crate_name, label)
128 }
129
130 pub fn declare_with_key(
134 id: u8,
135 crate_name: &str,
136 label: &str,
137 stable_key: &str,
138 ) -> Result<(), MemoryRegistryError> {
139 Self::declare_with_key_metadata(id, crate_name, label, stable_key, None, None)
140 }
141
142 pub fn declare_with_key_metadata(
147 id: u8,
148 crate_name: &str,
149 label: &str,
150 stable_key: &str,
151 schema_version: Option<u32>,
152 schema_fingerprint: Option<&str>,
153 ) -> Result<(), MemoryRegistryError> {
154 if MemoryRegistryRuntime::is_initialized() {
155 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
156 ranges: 0,
157 registrations: 1,
158 });
159 }
160
161 defer_register_with_key_metadata(
162 id,
163 crate_name,
164 label,
165 stable_key,
166 schema_version,
167 schema_fingerprint,
168 )
169 }
170
171 pub fn register(
176 id: u8,
177 crate_name: &str,
178 label: &str,
179 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
180 if !MemoryRegistryRuntime::is_initialized() {
181 return Err(MemoryRegistryError::RegistryNotBootstrapped);
182 }
183
184 if let Some(entry) = MemoryRegistry::get(id)
185 && entry.crate_name == crate_name
186 && entry.label == label
187 {
188 return Ok(open_memory(id));
189 }
190
191 Err(MemoryRegistryError::RegistrationAfterBootstrap {
192 ranges: 0,
193 registrations: 1,
194 })
195 }
196
197 pub fn register_with_key(
199 id: u8,
200 _crate_name: &str,
201 _label: &str,
202 stable_key: &str,
203 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
204 if !MemoryRegistryRuntime::is_initialized() {
205 return Err(MemoryRegistryError::RegistryNotBootstrapped);
206 }
207
208 if let Some(entry) = MemoryRegistry::get(id)
209 && entry.stable_key == stable_key
210 {
211 return Ok(open_memory(id));
212 }
213
214 Err(MemoryRegistryError::RegistrationAfterBootstrap {
215 ranges: 0,
216 registrations: 1,
217 })
218 }
219
220 #[must_use]
222 pub fn inspect(id: u8) -> Option<MemoryInspection> {
223 let range = MemoryRegistry::export_range_entries()
224 .into_iter()
225 .find(|entry| entry.range.contains(id))?;
226 let entry = MemoryRegistry::get(id);
227 let label = entry.as_ref().map(|entry| entry.label.clone());
228 let stable_key = entry.as_ref().map(|entry| entry.stable_key.clone());
229 let schema_version = entry.as_ref().and_then(|entry| entry.schema_version);
230 let schema_fingerprint = entry.and_then(|entry| entry.schema_fingerprint);
231
232 Some(MemoryInspection {
233 id,
234 owner: range.owner,
235 range: range.range,
236 label,
237 stable_key,
238 schema_version,
239 schema_fingerprint,
240 })
241 }
242
243 #[must_use]
245 pub fn registered() -> Vec<RegisteredMemory> {
246 MemoryRegistry::export_ids_by_range()
247 .into_iter()
248 .flat_map(|snapshot| {
249 snapshot
250 .entries
251 .into_iter()
252 .map(move |(id, entry)| RegisteredMemory {
253 id,
254 owner: snapshot.owner.clone(),
255 range: snapshot.range,
256 label: entry.label,
257 stable_key: entry.stable_key,
258 schema_version: entry.schema_version,
259 schema_fingerprint: entry.schema_fingerprint,
260 })
261 })
262 .collect()
263 }
264
265 #[must_use]
267 pub fn registered_for_owner(owner: &str) -> Vec<RegisteredMemory> {
268 Self::registered()
269 .into_iter()
270 .filter(|entry| entry.owner == owner)
271 .collect()
272 }
273
274 #[must_use]
276 pub fn find(owner: &str, label: &str) -> Option<RegisteredMemory> {
277 Self::registered()
278 .into_iter()
279 .find(|entry| entry.owner == owner && entry.label == label)
280 }
281
282 pub fn ledger_snapshot() -> Result<LedgerSnapshot, MemoryRegistryError> {
284 #[cfg(target_arch = "wasm32")]
285 {
286 let snapshot = ledger::try_diagnostic_snapshot()?;
287 Ok(LedgerSnapshot::from(snapshot))
288 }
289
290 #[cfg(not(target_arch = "wasm32"))]
291 {
292 let snapshot = ledger::try_snapshot()?;
293 Ok(LedgerSnapshot::from(snapshot))
294 }
295 }
296}
297
298impl From<ledger::MemoryLayoutLedgerSnapshot> for LedgerSnapshot {
299 fn from(snapshot: ledger::MemoryLayoutLedgerSnapshot) -> Self {
300 Self {
301 magic: snapshot.magic,
302 format_id: snapshot.format_id,
303 schema_version: snapshot.schema_version,
304 layout_epoch: snapshot.layout_epoch,
305 header_len: snapshot.header_len,
306 header_checksum: snapshot.header_checksum,
307 current_generation: snapshot.current_generation,
308 authorities: snapshot.authorities,
309 ranges: snapshot.ranges,
310 entries: snapshot.entries,
311 }
312 }
313}
314
315fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
317 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
318}
319
320#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::registry::{
328 MemoryRegistryError, defer_register, defer_reserve_range, reset_for_tests,
329 };
330
331 #[test]
332 fn register_memory_opens_validated_memory_for_reserved_slot() {
333 reset_for_tests();
334 defer_reserve_range("crate_a", 100, 102).expect("defer range");
335 defer_register(101, "crate_a", "slot").expect("defer register");
336 MemoryApi::bootstrap_pending().expect("bootstrap registry");
337
338 let _memory = MemoryApi::register(101, "crate_a", "slot").expect("open memory");
339 }
340
341 #[test]
342 fn register_memory_is_idempotent_for_same_entry() {
343 reset_for_tests();
344 defer_reserve_range("crate_a", 100, 102).expect("defer range");
345 defer_register(101, "crate_a", "slot").expect("defer register");
346 MemoryApi::bootstrap_pending().expect("bootstrap registry");
347 let _ = MemoryApi::register(101, "crate_a", "slot").expect("first open succeeds");
348
349 let _ = MemoryApi::register(101, "crate_a", "slot").expect("second open succeeds");
350 }
351
352 #[test]
353 fn register_with_key_opens_validated_explicit_key() {
354 reset_for_tests();
355 defer_reserve_range("crate_a", 100, 102).expect("defer range");
356 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
357 .expect("defer register");
358 MemoryApi::bootstrap_pending().expect("bootstrap registry");
359
360 let _memory = MemoryApi::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
361 .expect("open memory");
362 }
363
364 #[test]
365 fn declare_with_key_metadata_records_schema_metadata() {
366 reset_for_tests();
367 defer_reserve_range("crate_a", 100, 102).expect("defer range");
368 MemoryApi::declare_with_key_metadata(
369 101,
370 "crate_a",
371 "slot",
372 "app.crate_a.slot.v1",
373 Some(3),
374 Some("sha256:abc123"),
375 )
376 .expect("defer register");
377 MemoryApi::bootstrap_pending().expect("bootstrap registry");
378
379 let registered = MemoryApi::find("crate_a", "slot").expect("registered memory");
380 assert_eq!(registered.schema_version, Some(3));
381 assert_eq!(
382 registered.schema_fingerprint.as_deref(),
383 Some("sha256:abc123")
384 );
385
386 let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
387 assert_eq!(snapshot.format_id, 1);
388 assert_eq!(snapshot.schema_version, 1);
389 assert_eq!(snapshot.layout_epoch, 1);
390 assert!(snapshot.current_generation > 0);
391 let (_, entry) = snapshot
392 .entries
393 .into_iter()
394 .find(|(id, _)| *id == 101)
395 .expect("ledger entry");
396 assert_eq!(entry.schema_version, Some(3));
397 assert_eq!(entry.schema_fingerprint.as_deref(), Some("sha256:abc123"));
398 }
399
400 #[test]
401 fn declare_memory_does_not_open_before_bootstrap() {
402 reset_for_tests();
403
404 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
405 .expect("declare memory");
406
407 assert!(MemoryRegistry::get(101).is_none());
408 }
409
410 #[test]
411 fn declare_memory_rejects_after_bootstrap_seal() {
412 reset_for_tests();
413 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
414
415 let err = MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
416 .expect_err("late declaration should fail");
417 assert!(matches!(
418 err,
419 MemoryRegistryError::RegistrationAfterBootstrap {
420 ranges: 0,
421 registrations: 1,
422 }
423 ));
424 }
425
426 #[test]
427 fn register_memory_rejects_before_bootstrap_validation() {
428 reset_for_tests();
429
430 let Err(err) = MemoryApi::register(100, "crate_a", "slot") else {
431 panic!("opening before bootstrap must fail")
432 };
433 assert!(matches!(err, MemoryRegistryError::RegistryNotBootstrapped));
434 }
435
436 #[test]
437 fn register_memory_rejects_new_claim_after_bootstrap_seal() {
438 reset_for_tests();
439 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
440
441 let Err(err) = MemoryApi::register(101, "crate_a", "slot") else {
442 panic!("new registration after bootstrap must fail")
443 };
444 assert!(matches!(
445 err,
446 MemoryRegistryError::RegistrationAfterBootstrap {
447 ranges: 0,
448 registrations: 1,
449 }
450 ));
451 }
452
453 #[test]
454 fn bootstrap_pending_flushes_deferred_state() {
455 reset_for_tests();
456 defer_reserve_range("crate_a", 100, 102).expect("defer range");
457 defer_register(101, "crate_a", "slot").expect("defer register");
458
459 MemoryApi::bootstrap_pending().expect("bootstrap pending");
460
461 assert!(MemoryRegistry::export_ranges().contains(&(
462 "crate_a".to_string(),
463 MemoryRange {
464 start: 100,
465 end: 102
466 }
467 )));
468 let entries = MemoryRegistry::export();
469 assert!(entries.iter().any(|(id, entry)| {
470 *id == 101 && entry.crate_name == "crate_a" && entry.label == "slot"
471 }));
472 }
473
474 #[test]
475 fn inspect_memory_returns_reserved_owner_without_label() {
476 reset_for_tests();
477 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
478
479 let inspection = MemoryApi::inspect(101).expect("reserved slot should inspect");
480 assert_eq!(inspection.owner, "crate_a");
481 assert_eq!(
482 inspection.range,
483 MemoryRange {
484 start: 100,
485 end: 102
486 }
487 );
488 assert_eq!(inspection.label, None);
489 }
490
491 #[test]
492 fn inspect_memory_returns_registered_label() {
493 reset_for_tests();
494 defer_reserve_range("crate_a", 100, 102).expect("defer range");
495 defer_register(101, "crate_a", "slot").expect("defer register");
496 MemoryApi::bootstrap_pending().expect("bootstrap registry");
497
498 let inspection = MemoryApi::inspect(101).expect("registered slot should inspect");
499 assert_eq!(inspection.owner, "crate_a");
500 assert_eq!(
501 inspection.range,
502 MemoryRange {
503 start: 100,
504 end: 102
505 }
506 );
507 assert_eq!(inspection.label.as_deref(), Some("slot"));
508 assert_eq!(
509 inspection.stable_key.as_deref(),
510 Some("legacy.crate_a.slot.v1")
511 );
512 }
513
514 #[test]
515 fn inspect_memory_returns_none_for_unowned_id() {
516 reset_for_tests();
517 assert_eq!(MemoryApi::inspect(99), None);
518 }
519
520 #[test]
521 fn registered_memories_lists_registered_slots_with_owner_context() {
522 reset_for_tests();
523 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
524 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
525 defer_register(101, "crate_a", "slot_a").expect("defer register A");
526 defer_register(111, "crate_b", "slot_b").expect("defer register B");
527 MemoryApi::bootstrap_pending().expect("bootstrap registry");
528
529 let registrations = MemoryApi::registered();
530 assert_eq!(registrations.len(), 3);
531 assert!(registrations.contains(&RegisteredMemory {
532 id: 101,
533 owner: "crate_a".to_string(),
534 range: MemoryRange {
535 start: 100,
536 end: 102
537 },
538 label: "slot_a".to_string(),
539 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
540 schema_version: None,
541 schema_fingerprint: None,
542 }));
543 assert!(registrations.contains(&RegisteredMemory {
544 id: 111,
545 owner: "crate_b".to_string(),
546 range: MemoryRange {
547 start: 110,
548 end: 112
549 },
550 label: "slot_b".to_string(),
551 stable_key: "legacy.crate_b.slot_b.v1".to_string(),
552 schema_version: None,
553 schema_fingerprint: None,
554 }));
555 }
556
557 #[test]
558 fn registered_memories_for_owner_filters_to_owner() {
559 reset_for_tests();
560 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
561 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
562 defer_register(101, "crate_a", "slot_a").expect("defer register A");
563 defer_register(111, "crate_b", "slot_b").expect("defer register B");
564 MemoryApi::bootstrap_pending().expect("bootstrap registry");
565
566 let registrations = MemoryApi::registered_for_owner("crate_a");
567 assert_eq!(
568 registrations,
569 vec![RegisteredMemory {
570 id: 101,
571 owner: "crate_a".to_string(),
572 range: MemoryRange {
573 start: 100,
574 end: 102
575 },
576 label: "slot_a".to_string(),
577 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
578 schema_version: None,
579 schema_fingerprint: None,
580 }]
581 );
582 }
583
584 #[test]
585 fn find_registered_memory_returns_match_for_owner_and_label() {
586 reset_for_tests();
587 defer_reserve_range("crate_a", 100, 102).expect("defer range");
588 defer_register(101, "crate_a", "slot_a").expect("defer register");
589 MemoryApi::bootstrap_pending().expect("bootstrap registry");
590
591 let registration = MemoryApi::find("crate_a", "slot_a").expect("slot should exist");
592 assert_eq!(
593 registration,
594 RegisteredMemory {
595 id: 101,
596 owner: "crate_a".to_string(),
597 range: MemoryRange {
598 start: 100,
599 end: 102
600 },
601 label: "slot_a".to_string(),
602 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
603 schema_version: None,
604 schema_fingerprint: None,
605 }
606 );
607 }
608
609 #[test]
610 fn find_registered_memory_returns_none_when_missing() {
611 reset_for_tests();
612 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
613 assert_eq!(MemoryApi::find("crate_a", "slot_a"), None);
614 }
615
616 #[test]
617 fn ledger_snapshot_reads_historical_records() {
618 reset_for_tests();
619 defer_reserve_range("crate_a", 100, 102).expect("defer range");
620 defer_register(101, "crate_a", "slot").expect("defer register");
621 MemoryApi::bootstrap_pending().expect("bootstrap registry");
622
623 let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
624 assert_eq!(snapshot.format_id, 1);
625 assert_eq!(snapshot.schema_version, 1);
626 assert_eq!(snapshot.layout_epoch, 1);
627 assert!(snapshot.authorities.iter().any(|authority| {
628 authority.owner == "canic.framework"
629 && authority.range == MemoryRange { start: 0, end: 99 }
630 }));
631 assert!(snapshot.authorities.iter().any(|authority| {
632 authority.owner == "applications"
633 && authority.range
634 == MemoryRange {
635 start: 100,
636 end: 254,
637 }
638 }));
639 assert!(snapshot.ranges.iter().any(|(owner, range)| {
640 owner == "crate_a"
641 && *range
642 == MemoryRange {
643 start: 100,
644 end: 102,
645 }
646 }));
647 assert!(snapshot.entries.iter().any(|(id, entry)| {
648 *id == 101
649 && entry.crate_name == "crate_a"
650 && entry.label == "slot"
651 && entry.stable_key == "legacy.crate_a.slot.v1"
652 }));
653 }
654}