Skip to main content

javm_cap/cap/
mod.rs

1//! `Cap` — cap enum + shared constants.
2//!
3//! The four Cap variants live one-per-submodule under this directory:
4//! [`cnode`], [`data`], [`image`], [`instance`] (plus [`page`], a `data`
5//! detail). The root [`Cap`] enum dispatches to those structs.
6//!
7//! Cap types and their inner storage use the default `Global` allocator
8//! (= std heap on host, talc on guest via `#[global_allocator]`).
9//!
10//! ## Wire form
11//!
12//! `Cap` itself is the wire form. It derives
13//! `rkyv::Archive`/`Serialize`/`Deserialize` so callers write
14//! `rkyv::to_bytes(&cap)?` directly. The slot-target
15//! [`super::cache::CapHashOrRef`] has a hand-rolled rkyv impl whose
16//! `Serialize` returns an error ([`super::cache::CapHasRefError`]) if
17//! the cap graph still contains an inline `Owned` cap. The archived form
18//! of the `Hash` arm is `[u8; 32]` (= the `CapHash` archived form), so a
19//! settled cap graph serialises to a stable byte form and an
20//! `Owned`-bearing graph surfaces as a typed `Result::Err` at encode
21//! time (no panic).
22//!
23//! See [`super::cache::CapRef`] for the (dormant) cache-handle lifecycle.
24
25pub mod cnode;
26pub mod data;
27pub mod image;
28pub mod instance;
29pub mod page;
30
31use cnode::CNodeCap;
32use data::DataCap;
33use image::ImageCap;
34use instance::InstanceCap;
35
36/// 32-byte digest used for all v3 cap identity / content hashes.
37pub type CapHash = [u8; 32];
38
39/// Number of PVM general-purpose registers (φ\[0\]..φ\[12\]).
40pub const NUM_REGS: usize = 13;
41
42/// Maximum depth of a `MemoryMapping.source_path`. v3 cap graphs
43/// stay shallow; eight is plenty.
44pub const MAX_SOURCE_DEPTH: usize = 8;
45
46/// One of the four v3 cap kinds.
47///
48/// **SSZ note**: the `HashTreeRoot` derive treats `Cap` as an SSZ
49/// Union over the four variants. Each variant's selector provides the
50/// domain separation that the legacy byte-protocol kind tags
51/// (`0x10..0x50`) provided; the per-variant root is computed by that
52/// variant's own `HashTreeRoot` impl. We do not derive `Encode +
53/// Decode` on `Cap` itself; caps move through the cache by direct
54/// allocation and aren't wire-transmitted at this SSZ-encoded layer.
55///
56/// **rkyv note**: the `Archive`/`Serialize`/`Deserialize` derives
57/// provide the I/O-boundary wire form. Serialization errors out (no
58/// panic) if any slot target is an inline `CapHashOrRef::Owned` cap —
59/// see the module docs.
60///
61/// **Clone**: the derived `Clone` recursively clones field-by-field. An
62/// inline `Owned(Box<Cap>)` slot deep-clones the boxed cap. Drop is
63/// symmetric.
64#[derive(
65    Clone, Debug, ssz_derive::HashTreeRoot, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
66)]
67pub enum Cap {
68    #[ssz(selector = 0)]
69    Instance(InstanceCap),
70    #[ssz(selector = 1)]
71    Image(ImageCap),
72    #[ssz(selector = 2)]
73    Data(DataCap),
74    #[ssz(selector = 3)]
75    CNode(CNodeCap),
76}
77
78impl Cap {
79    /// 32-byte content hash. Walks the cap tree via SSZ `HashTreeRoot`
80    /// with SHA-256 as the digest; the four variants get their domain
81    /// separation from the SSZ Union selector.
82    ///
83    /// **Substitution invariants** preserved by hand-written
84    /// `HashTreeRoot` impls on [`page::PageSlot`], [`page::PageBytes`],
85    /// and [`super::cache::CapHashOrRef`]:
86    /// - `PageSlot::Loaded(p)` hashes identically to
87    ///   `PageSlot::Missing(p.hash)` — a freshly-loaded page
88    ///   substitutes for a missing page without changing the
89    ///   enclosing cap's hash.
90    /// - `CapHashOrRef::Hash(h)` hashes to `h` exactly — a freshly-
91    ///   published cap blob substitutes for a content reference
92    ///   without changing the enclosing cap's hash.
93    ///
94    /// **In-flight `Owned` caps panic**: hashing a `Cap` whose graph
95    /// still contains an inline `CapHashOrRef::Owned(_)` target will
96    /// panic in the SSZ path. Callers must `settle` the cap graph first.
97    /// (The rkyv serialise path is fallible for the same case — see the
98    /// module docs.)
99    ///
100    /// **Image hash distinction**: `Cap::Image(_).cap_hash()` and
101    /// `crate::image::image_content_hash` hash different types — the
102    /// cap-resident `ImageCap` has a flatter layout than the SSZ
103    /// `Image`. The cache publishes by `cap_hash`; the image-hash
104    /// chain protocol uses `image_content_hash`.
105    pub fn cap_hash(&self) -> CapHash {
106        ssz::hash_tree_root(self)
107    }
108
109    /// Build a heap `Cap::Data` whose content is `bytes` padded up to
110    /// the next [`PAGE_SIZE`](data::PAGE_SIZE) boundary with
111    /// zeros. The backing allocation is page-aligned so the kernel
112    /// can later map the cap's pages directly into a ring-3 PT.
113    ///
114    /// `DataCap.content_len()` returns the padded length (always a
115    /// 4 KiB-multiple). There is no separate logical-size field;
116    /// callers needing a shorter logical payload (e.g. variable-length
117    /// args) interpret the meaningful prefix themselves.
118    pub fn data_inline(bytes: &[u8]) -> Self {
119        Cap::Data(DataCap::from_bytes(bytes))
120    }
121
122    /// Build a heap `Cap::Data` whose logical size is at least `target_size`
123    /// bytes (rounded up to the next page boundary). `bytes` fills the low
124    /// bytes; the remainder is zero (sparse).
125    pub fn data_inline_with_size(bytes: &[u8], target_size: u64) -> Self {
126        Cap::Data(DataCap::from_bytes_sized(bytes, target_size))
127    }
128
129    /// Build a heap `Cap::Data` from a page-granular [`crate::image::DataDesc`]
130    /// over an Image `arena`: each named page is materialized from its `PAGE_SIZE`
131    /// arena window, omitted pages are the canonical zero page. The single
132    /// materialization point for Image data slots; the resulting cap hash
133    /// equals `data_inline_with_size(equivalent_contiguous_content, size)`.
134    /// The caller must have validated the descriptor
135    /// ([`crate::image::DataDesc::validate`]) against `arena.len()`.
136    pub fn data_from_desc(arena: &[u8], desc: &crate::image::DataDesc) -> Self {
137        Cap::Data(desc.to_data_cap(arena))
138    }
139
140    /// Build an empty heap `Cap::CNode`. A CNode is an unbounded
141    /// hash-keyed map (bounded by storage quota), so there is no
142    /// `size_log` to declare.
143    pub fn empty_cnode() -> Self {
144        Cap::CNode(CNodeCap::new())
145    }
146
147    /// Build a heap `Cap::Image` from an SSZ `Image` plus the
148    /// caller-resolved pinned/initial slot `CapHash` pairs.
149    pub fn image_with_slots(
150        image: &crate::image::Image,
151        pinned_hashes: &[(crate::slot::Key, CapHash)],
152        initial_hashes: &[(crate::slot::Key, CapHash)],
153    ) -> Result<Self, image::ImageConvertError> {
154        Ok(Cap::Image(image::image_cap(
155            image,
156            pinned_hashes,
157            initial_hashes,
158        )?))
159    }
160
161    /// Build a heap `Cap::Instance` directly from field values.
162    ///
163    /// `mem` is the Instance's read-write memory image (a dense [`DataCap`]
164    /// covering the data extent; pinned read-only mappings are not part of it —
165    /// see [`instance::InstanceCap::mem`]).
166    #[allow(clippy::too_many_arguments)]
167    pub fn instance_with_mem(
168        image_hash_chain: CapHash,
169        image_hash: CapHash,
170        root_cnode: CapHash,
171        mem: DataCap,
172        regs: [u64; NUM_REGS],
173        pc: u64,
174        gas_remaining: u64,
175    ) -> Self {
176        Cap::Instance(instance::InstanceCap {
177            image_hash_chain,
178            image_hash,
179            root_cnode: crate::cache::CapHashOrRef::Hash(root_cnode),
180            mem,
181            regs,
182            pc,
183            gas_remaining,
184        })
185    }
186}