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