Skip to main content

javm_cap/
cap.rs

1//! `Cap` — cap enum + shared constants.
2//!
3//! Cap types and their inner storage use the default `Global` allocator
4//! (= std heap on host, talc on guest via `#[global_allocator]`).
5//!
6//! ## `CapRef` is an `Arc`-backed handle
7//!
8//! A `CapRef` is the entry-lifetime token for `CacheDirectory.instances`.
9//! `Clone` bumps an inner `Arc` refcount; `Drop` decrements it.
10//! `CacheDirectory` itself owns one `CapRef` per live entry alongside the
11//! data; when external holders all drop their clones, the directory's
12//! `sweep_instances` finds entries whose stored CapRef has
13//! `strong_count == 1` and removes them. No callback-on-drop, no
14//! deadlock discipline.
15//!
16//! `Cap::CNode` slots and `Cap::Instance.root_cnode` hold
17//! `CapHashOrRef::Ref(CapRef)` directly, so cloning a Cap deep-bumps every
18//! nested handle and dropping a Cap deep-releases them. Recursive cleanup
19//! is automatic via Rust's Drop semantics; cycles are structurally
20//! impossible (data-flow principle: no shared mutable state across
21//! Instance boundaries).
22
23use alloc::sync::Arc;
24use alloc::vec::Vec;
25
26use super::cnode::CNodeCap;
27use super::data::DataCap;
28use super::image_cap::ImageCap;
29use super::instance::InstanceCap;
30
31/// 32-byte digest used for all v3 cap identity / content hashes.
32pub type CapHash = [u8; 32];
33
34/// Cache-local lifetime handle to a working `Cap::Instance` in
35/// `CacheDirectory.instances`. `Clone` bumps the refcount; the
36/// directory's `sweep_instances` reclaims entries whose only holder is
37/// the directory itself. Two separate `CacheDirectory` instances produce
38/// independent `CapRef` id namespaces — refs must not cross caches.
39#[derive(Clone, Debug)]
40pub struct CapRef {
41    id: u64,
42    /// Refcount tracker. The Arc's strong count is the number of
43    /// live `CapRef` holders for this id (including the directory's
44    /// own self-reference).
45    rc: Arc<()>,
46}
47
48impl CapRef {
49    /// Construct a fresh `CapRef`. Only [`crate::cache::CacheDirectory::
50    /// put_instance`] and tests are expected to call this.
51    pub fn new(id: u64) -> Self {
52        Self {
53            id,
54            rc: Arc::new(()),
55        }
56    }
57
58    /// The id this handle resolves to inside `CacheDirectory.instances`.
59    pub fn id(&self) -> u64 {
60        self.id
61    }
62
63    /// Number of live `CapRef` clones for this id, including the
64    /// directory's own self-reference. `sweep_instances` reclaims
65    /// entries whose stored handle has `strong_count == 1`.
66    pub fn strong_count(&self) -> usize {
67        Arc::strong_count(&self.rc)
68    }
69}
70
71impl PartialEq for CapRef {
72    fn eq(&self, other: &Self) -> bool {
73        self.id == other.id
74    }
75}
76impl Eq for CapRef {}
77
78impl core::hash::Hash for CapRef {
79    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
80        self.id.hash(state)
81    }
82}
83
84/// Slot/field reference: either a content-addressed blob in
85/// `cache.blobs` or a mutable working entry in `cache.instances`.
86///
87/// **SSZ note**: `CapHashOrRef`'s `HashTreeRoot` impl is hand-rolled
88/// (see below), not derived. The pass-through semantics — `Hash(h)`
89/// hashes to `h` — let a freshly-published cap substitute for a
90/// `Ref` reference without changing the hash of any cap that holds
91/// it. The `Ref` arm panics: callers must `settle` a cap graph before
92/// hashing it. `Encode` mirrors `HashTreeRoot` (panic on Ref);
93/// `Decode` rejects the Ref selector (no directory context).
94///
95/// **Not `Copy`**: the `Ref(CapRef)` arm carries a refcounted handle,
96/// so the enum is `Clone`-only.
97#[derive(Clone, PartialEq, Eq, Hash, Debug)]
98pub enum CapHashOrRef {
99    Hash(CapHash),
100    Ref(CapRef),
101}
102
103impl ssz::HashTreeRoot for CapHashOrRef {
104    fn hash_tree_root<D: ::ssz::digest::Digest<OutputSize = ::ssz::digest::typenum::U32>>(
105        &self,
106    ) -> [u8; 32] {
107        match self {
108            CapHashOrRef::Hash(h) => *h,
109            CapHashOrRef::Ref(_) => {
110                panic!("cap_hash: unresolved CapRef in cap graph; settle first")
111            }
112        }
113    }
114}
115
116impl ssz::Encode for CapHashOrRef {
117    fn is_ssz_fixed_len() -> bool {
118        false
119    }
120    fn ssz_fixed_len() -> usize {
121        ssz::BYTES_PER_LENGTH_OFFSET
122    }
123    fn ssz_bytes_len(&self) -> usize {
124        match self {
125            CapHashOrRef::Hash(_) => 1 + 32,
126            // Ref must be settled before serialisation; matches the
127            // `HashTreeRoot` contract above. Reached only by buggy code.
128            CapHashOrRef::Ref(_) => {
129                panic!("ssz_bytes_len: unresolved CapRef in cap graph; settle first")
130            }
131        }
132    }
133    fn ssz_append(&self, buf: &mut Vec<u8>) {
134        match self {
135            CapHashOrRef::Hash(h) => {
136                buf.push(0);
137                buf.extend_from_slice(h);
138            }
139            CapHashOrRef::Ref(_) => {
140                panic!("ssz_append: unresolved CapRef in cap graph; settle first")
141            }
142        }
143    }
144}
145
146impl ssz::Decode for CapHashOrRef {
147    fn is_ssz_fixed_len() -> bool {
148        false
149    }
150    fn ssz_fixed_len() -> usize {
151        ssz::BYTES_PER_LENGTH_OFFSET
152    }
153    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
154        if bytes.is_empty() {
155            return Err(ssz::DecodeError::UnexpectedEof {
156                expected: 1,
157                actual: 0,
158            });
159        }
160        match bytes[0] {
161            0 => {
162                if bytes.len() != 1 + 32 {
163                    return Err(ssz::DecodeError::UnexpectedEof {
164                        expected: 1 + 32,
165                        actual: bytes.len(),
166                    });
167                }
168                let mut h = [0u8; 32];
169                h.copy_from_slice(&bytes[1..1 + 32]);
170                Ok(CapHashOrRef::Hash(h))
171            }
172            // Refs are cache-local lifetime handles; the wire has no
173            // directory context to reconstruct one. Caller bugs that
174            // serialise a Ref into wire bytes surface here.
175            1 => Err(ssz::DecodeError::Custom(
176                "CapHashOrRef::Ref cannot be decoded from wire bytes",
177            )),
178            v => Err(ssz::DecodeError::InvalidSelector(v)),
179        }
180    }
181}
182
183/// Number of PVM general-purpose registers (φ\[0\]..φ\[12\]).
184pub const NUM_REGS: usize = 13;
185
186/// Maximum depth of a `MemoryMapping.source_path`. v3 cap graphs
187/// stay shallow; eight is plenty.
188pub const MAX_SOURCE_DEPTH: usize = 8;
189
190/// Maximum number of endpoints per Image.
191pub const MAX_ENDPOINTS: usize = 64;
192
193/// One of the five v3 cap kinds.
194///
195/// **SSZ note**: the `HashTreeRoot` derive treats `Cap` as an SSZ
196/// Union over the five variants. Each variant's selector provides the
197/// domain separation that the legacy byte-protocol kind tags
198/// (`0x10..0x50`) provided; the per-variant root is computed by that
199/// variant's own `HashTreeRoot` impl. We do not derive `Encode +
200/// Decode` on `Cap` itself; caps move through the cache by direct
201/// allocation and aren't wire-transmitted at this layer.
202///
203/// **Clone**: the derived `Clone` recursively clones field-by-field.
204/// `Cap::Instance` and `Cap::CNode` carry `CapHashOrRef` values; the
205/// `Ref(CapRef)` arm `Arc::clone`s the handle, so cloning a Cap
206/// deep-bumps every nested instance reference. Drop is symmetric.
207#[derive(Clone, Debug, ssz_derive::HashTreeRoot)]
208pub enum Cap {
209    #[ssz(selector = 0)]
210    Instance(InstanceCap),
211    #[ssz(selector = 1)]
212    Image(ImageCap),
213    #[ssz(selector = 2)]
214    Data(DataCap),
215    #[ssz(selector = 3)]
216    CNode(CNodeCap),
217    #[ssz(selector = 4)]
218    Type(TypeCap),
219}
220
221/// `Cap::Type` payload. Pure identifier; no owned content, so no
222/// allocator parameter needed.
223#[derive(
224    Clone,
225    Copy,
226    Debug,
227    PartialEq,
228    Eq,
229    Hash,
230    ssz_derive::Encode,
231    ssz_derive::Decode,
232    ssz_derive::HashTreeRoot,
233)]
234pub struct TypeCap {
235    pub image_hash_chain: CapHash,
236}
237
238/// Discriminant for `Cap`. Useful for matching, error messages, and
239/// places where the payload is irrelevant.
240#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
241pub enum CapKind {
242    Instance,
243    Image,
244    Data,
245    CNode,
246    Type,
247}
248
249impl Cap {
250    pub fn kind(&self) -> CapKind {
251        match self {
252            Cap::Instance(_) => CapKind::Instance,
253            Cap::Image(_) => CapKind::Image,
254            Cap::Data(_) => CapKind::Data,
255            Cap::CNode(_) => CapKind::CNode,
256            Cap::Type(_) => CapKind::Type,
257        }
258    }
259
260    /// Build a heap `Cap::Data` whose content is `bytes` padded up to
261    /// the next [`PAGE_SIZE`](super::data::PAGE_SIZE) boundary with
262    /// zeros. The backing allocation is page-aligned so the kernel
263    /// can later map the cap's pages directly into a ring-3 PT.
264    ///
265    /// `DataCap.content_len()` returns the padded length (always a
266    /// 4 KiB-multiple). There is no separate logical-size field;
267    /// callers needing a shorter logical payload (e.g. variable-length
268    /// args) interpret the meaningful prefix themselves.
269    pub fn data_inline(bytes: &[u8]) -> Self {
270        let mut buf = super::data::alloc_page_aligned_zeroed(bytes.len());
271        buf[..bytes.len()].copy_from_slice(bytes);
272        Cap::Data(DataCap {
273            content: super::data::DataContent::Inline(buf),
274        })
275    }
276
277    /// Build a heap `Cap::Data` whose backing buffer is at least
278    /// `target_size` bytes (rounded up to the next page boundary).
279    /// `bytes` is copied to the start of the buffer; the remainder is
280    /// zero-padded. Used by callers that need a cap matching a specific
281    /// `MemoryMapping.size` from an image manifest (e.g. genesis +
282    /// transpiler-emitted initial data).
283    ///
284    /// If `target_size < bytes.len()`, the buffer is sized to fit
285    /// `bytes` (still page-multiple) — i.e. `target_size` is a floor,
286    /// not a ceiling.
287    pub fn data_inline_with_size(bytes: &[u8], target_size: u64) -> Self {
288        let target = (target_size as usize).max(bytes.len());
289        let mut buf = super::data::alloc_page_aligned_zeroed(target);
290        buf[..bytes.len()].copy_from_slice(bytes);
291        Cap::Data(DataCap {
292            content: super::data::DataContent::Inline(buf),
293        })
294    }
295
296    /// Build a heap `Cap::Image` from a SCALE `Image` value. Pinned
297    /// and initial slot references are left empty; callers that need
298    /// them should drive [`super::image_cap::image_cap`] directly
299    /// with the already-resolved `(slot, CapHash)` pairs.
300    pub fn image_from(
301        image: &crate::image::Image,
302    ) -> Result<Self, super::image_cap::ImageConvertError> {
303        Ok(Cap::Image(super::image_cap::image_cap(image, &[], &[])?))
304    }
305
306    /// Build an empty heap `Cap::CNode` of `2^size_log` slots. Rejects
307    /// `size_log > 16`.
308    pub fn empty_cnode(size_log: u8) -> Result<Self, crate::error::CapError> {
309        Ok(Cap::CNode(CNodeCap::new(size_log)?))
310    }
311
312    /// Build a heap `Cap::Image` from a SCALE `Image` plus the caller-resolved
313    /// pinned/initial slot `CapHash` pairs.
314    ///
315    /// Wraps [`super::image_cap::image_cap`] with the `Cap::Image`
316    /// constructor. Use this when the caller has already
317    /// published (or knows the hashes of) the pinned/initial data blobs that
318    /// the image references.
319    pub fn image_with_slots(
320        image: &crate::image::Image,
321        pinned_hashes: &[(crate::slot::SlotIdx, CapHash)],
322        initial_hashes: &[(crate::slot::SlotIdx, CapHash)],
323    ) -> Result<Self, super::image_cap::ImageConvertError> {
324        Ok(Cap::Image(super::image_cap::image_cap(
325            image,
326            pinned_hashes,
327            initial_hashes,
328        )?))
329    }
330
331    /// Build a heap `Cap::Instance` directly from field values. Mirrors the
332    /// shape the old `CacheDirectory::publish_instance_blob` reconstructed
333    /// field-by-field but produces a `Cap::Instance(InstanceCap)`
334    /// the caller owns.
335    ///
336    /// `rw_overlays` is the list of `(start_va, bytes)` overlays the
337    /// Instance carries — each becomes one `RwOverlay` entry.
338    #[allow(clippy::too_many_arguments)]
339    pub fn instance_with_overlays(
340        image_hash_chain: CapHash,
341        image_hash: CapHash,
342        root_cnode: CapHash,
343        rw_overlays: &[(u32, &[u8])],
344        mem_size: u32,
345        regs: [u64; NUM_REGS],
346        pc: u64,
347        gas_remaining: u64,
348    ) -> Self {
349        let mut overlays: Vec<super::instance::RwOverlay> = Vec::new();
350        for (start, bytes) in rw_overlays {
351            let mut buf = Vec::with_capacity(bytes.len());
352            buf.extend_from_slice(bytes);
353            overlays.push(super::instance::RwOverlay {
354                start: *start,
355                bytes: buf,
356            });
357        }
358        Cap::Instance(super::instance::InstanceCap {
359            image_hash_chain,
360            image_hash,
361            root_cnode: CapHashOrRef::Hash(root_cnode),
362            rw_overlays: overlays,
363            mem_size,
364            regs,
365            pc,
366            gas_remaining,
367        })
368    }
369}