Skip to main content

canic_memory/api/
mod.rs

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
15///
16/// MemoryApi
17///
18/// Supported facade for memory bootstrap, dynamic slot registration, and
19/// registry inspection.
20
21pub struct MemoryApi;
22
23///
24/// MemoryInspection
25///
26/// Read-only description of the owner range for one memory ID.
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct MemoryInspection {
30    /// Stable-memory ID being inspected.
31    pub id: u8,
32    /// Crate name that reserved the range containing `id`.
33    pub owner: String,
34    /// Reserved range containing `id`.
35    pub range: MemoryRange,
36    /// Registered slot label for `id`, when the ID has already been registered.
37    pub label: Option<String>,
38    /// ABI-stable key for `id`, when the ID has already been registered.
39    pub stable_key: Option<String>,
40    /// Optional in-place schema version metadata for diagnostics.
41    pub schema_version: Option<u32>,
42    /// Optional opaque schema fingerprint metadata for diagnostics.
43    pub schema_fingerprint: Option<String>,
44}
45
46///
47/// RegisteredMemory
48///
49/// Read-only description of one registered stable-memory slot.
50
51#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct RegisteredMemory {
53    /// Registered stable-memory ID.
54    pub id: u8,
55    /// Crate name that owns the slot's reserved range.
56    pub owner: String,
57    /// Reserved range containing `id`.
58    pub range: MemoryRange,
59    /// Human-readable slot label supplied by the registering crate.
60    pub label: String,
61    /// ABI-stable key that owns this memory ID permanently.
62    pub stable_key: String,
63    /// Optional in-place schema version metadata for diagnostics.
64    pub schema_version: Option<u32>,
65    /// Optional opaque schema fingerprint metadata for diagnostics.
66    pub schema_fingerprint: Option<String>,
67}
68
69///
70/// LedgerSnapshot
71///
72/// Read-only snapshot of the persisted ABI ledger.
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct LedgerSnapshot {
76    /// Ledger magic value from the physical header.
77    pub magic: u64,
78    /// Ledger physical format identifier from the header.
79    pub format_id: u32,
80    /// Ledger schema version from the header.
81    pub schema_version: u32,
82    /// Encoded ledger header length.
83    pub header_len: u32,
84    /// Header checksum covering the persisted header fields.
85    pub header_checksum: u64,
86    /// Authoritative committed generation selected by recovery validation.
87    pub current_generation: u64,
88    /// Canonical allocation authority ranges recorded by the persisted ABI ledger.
89    pub authorities: Vec<MemoryRangeAuthority>,
90    /// Historical owner ranges recorded by the persisted ABI ledger.
91    pub ranges: Vec<(String, MemoryRange)>,
92    /// Historical memory ID records recorded by the persisted ABI ledger.
93    pub entries: Vec<(u8, crate::registry::MemoryRegistryEntry)>,
94}
95
96impl MemoryApi {
97    /// Bootstrap eager TLS, eager-init hooks, and the caller's initial reserved range.
98    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    /// Bootstrap eager TLS, eager-init hooks, and flush deferred registry state
108    /// without reserving a new owner range.
109    pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
110        let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
111        Ok(())
112    }
113
114    /// Declare one legacy-key stable-memory ID for bootstrap validation.
115    ///
116    /// This queues metadata only. It does not open the underlying virtual memory.
117    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    /// Declare one explicit-key stable-memory ID for bootstrap validation.
129    ///
130    /// This queues metadata only. It does not open the underlying virtual memory.
131    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    /// Declare one explicit-key stable-memory ID with optional schema metadata.
141    ///
142    /// Schema metadata is informational in 0.38 and does not affect allocation
143    /// ownership. This queues metadata only. It does not open virtual memory.
144    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    /// Open one already-validated stable-memory ID and return its virtual memory handle.
170    ///
171    /// The ID must have been declared before bootstrap and accepted by the
172    /// sealed runtime declaration snapshot. This is not a dynamic allocation API.
173    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    /// Open one already-validated stable-memory ID using its explicit ABI-stable key.
196    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    /// Inspect who currently owns one memory id and whether it is registered.
219    #[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    /// List every registered memory slot with owner/range/label context.
242    #[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    /// List all registered memory slots for one owner.
264    #[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    /// Find one registered memory slot by owner and label.
273    #[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    /// Read the persisted ABI ledger without relying on current registry reconstruction.
281    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
312// Open a registered virtual memory slot through the shared manager.
313fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
314    MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
315}
316
317///
318/// TESTS
319///
320
321#[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}