Skip to main content

javm_cap/
slot.rs

1//! Byte-string keys and slot addressing.
2//!
3//! [`Key`] is the **byte-string key type** used across the cap model: it names
4//! one slot in a single cnode (a *slot key*), and it also keys the kernel's
5//! resource tables (a *meter key* / *quota key* — same type, unrelated meaning;
6//! see the kernel-assisted Gas/Quota handles). A [`SlotPath`] walks from the
7//! root cnode through nested `Cap::CNode` slots down to a target slot.
8//!
9//! A cnode is a sparse direct map keyed by logical byte strings, not an
10//! integer-indexed array — so a slot name is a `Key` (a short byte string),
11//! and a path is a `SlotPath` (a sequence of `Key`s). There is no fixed slot
12//! count: a cnode is bounded by storage quota, not a compile-time capacity.
13//! The V1 ABI uses single-byte keys (`Key::from(b)`), but the type admits
14//! arbitrary-length keys for future ABI extensions (e.g.
15//! `address -> Cap::Instance`).
16
17use crate::cap::MAX_SOURCE_DEPTH;
18use crate::error::CapError;
19use smallvec::SmallVec;
20use ssz_derive::{Decode, Encode};
21
22/// Inline byte capacity of a [`Key`]. Keys longer than this spill to the
23/// heap — there is **no hard cap** (unlike a fixed array). The V1 ABI uses
24/// 1-byte keys; 8 bytes inline covers an address-sized key without
25/// allocating.
26pub const MAX_KEY_LEN: usize = 8;
27
28/// The logical key naming one slot in a single cnode.
29///
30/// A short byte string used directly as a cnode slot key. Backed by a
31/// [`SmallVec`] so the common single-byte key stays inline. The SSZ wire/hash
32/// form is **identical to `Vec<u8>`** (forwarded via `#[ssz(transparent)]`), so
33/// embedding a `Key` is byte-equivalent to embedding the raw key bytes.
34#[derive(
35    Debug,
36    Clone,
37    PartialEq,
38    Eq,
39    Hash,
40    PartialOrd,
41    Ord,
42    Encode,
43    Decode,
44    ssz_derive::HashTreeRoot,
45    rkyv::Archive,
46    rkyv::Serialize,
47    rkyv::Deserialize,
48)]
49#[rkyv(derive(Debug, PartialEq, Eq, PartialOrd, Ord))]
50pub struct Key(#[ssz(transparent)] pub SmallVec<[u8; MAX_KEY_LEN]>);
51
52impl Key {
53    /// The key's bytes.
54    pub fn as_slice(&self) -> &[u8] {
55        &self.0
56    }
57
58    /// True iff this is the empty key (zero bytes). The empty key is a valid
59    /// logical key (`[]`), distinct from `Key::from(0u8)` (`[0]`).
60    pub fn is_empty(&self) -> bool {
61        self.0.is_empty()
62    }
63
64    /// A best-effort numeric id for **diagnostics only** (error messages):
65    /// the V1 single-byte ABI value. A multi-byte key folds to its first
66    /// byte (0 if empty). Never use this for identity or lookup — the key's
67    /// bytes are the identity.
68    pub fn diag_id(&self) -> u32 {
69        self.0.first().copied().unwrap_or(0) as u32
70    }
71}
72
73impl From<u8> for Key {
74    /// V1 single-byte ABI: a slot index `b` is the 1-byte key `[b]`.
75    fn from(b: u8) -> Self {
76        Self(smallvec::smallvec![b])
77    }
78}
79
80impl From<&[u8]> for Key {
81    fn from(bytes: &[u8]) -> Self {
82        Self(SmallVec::from_slice(bytes))
83    }
84}
85
86impl core::ops::Deref for Key {
87    type Target = [u8];
88    fn deref(&self) -> &[u8] {
89        &self.0
90    }
91}
92
93/// Pack a [`Key`] (≤ [`MAX_KEY_LEN`] bytes) into two registers for storage in a
94/// kernel-assisted unit handle (`Gas{meter_key}` / `Quota{quota_key}`): the key
95/// bytes little-endian-packed into the first register, the byte length into the
96/// second. Inverse of [`key_from_regs`].
97///
98/// # Panics
99///
100/// Panics if the key is longer than [`MAX_KEY_LEN`] (8) bytes — the register
101/// packing has no room, and silently truncating would alias two distinct keys.
102pub fn key_to_regs(key: &Key) -> (u64, u64) {
103    let bytes = key.as_slice();
104    assert!(
105        bytes.len() <= MAX_KEY_LEN,
106        "key_to_regs: key length {} exceeds MAX_KEY_LEN {MAX_KEY_LEN}",
107        bytes.len()
108    );
109    let mut packed = [0u8; 8];
110    packed[..bytes.len()].copy_from_slice(bytes);
111    (u64::from_le_bytes(packed), bytes.len() as u64)
112}
113
114/// Reconstruct a [`Key`] from the `(packed, len)` register pair produced by
115/// [`key_to_regs`]. A `len > 8` is clamped to 8 (defensive; `key_to_regs`
116/// never emits one).
117pub fn key_from_regs(packed: u64, len: u64) -> Key {
118    let n = (len as usize).min(MAX_KEY_LEN);
119    Key::from(&packed.to_le_bytes()[..n])
120}
121
122/// Path from the root cnode through nested cnodes to a slot.
123///
124/// The sequence of [`Key`]s walked through nested `Cap::CNode` slots; the
125/// final key is the target. An empty path is invalid (must address some
126/// slot). Backed by a [`SmallVec`] sized to [`MAX_SOURCE_DEPTH`] so a typical
127/// (shallow) path stays inline; the SSZ wire/hash form is identical to
128/// `Vec<Key>` (forwarded via `#[ssz(transparent)]`).
129///
130/// Example: `SlotPath::root(Key::from(7))` addresses slot 7 of the root
131/// cnode; a two-step path addresses a slot of the `Cap::CNode` held in the
132/// first step's slot.
133#[derive(
134    Debug,
135    Clone,
136    PartialEq,
137    Eq,
138    Hash,
139    Encode,
140    Decode,
141    ssz_derive::HashTreeRoot,
142    rkyv::Archive,
143    rkyv::Serialize,
144    rkyv::Deserialize,
145)]
146pub struct SlotPath(#[ssz(transparent)] pub SmallVec<[Key; MAX_SOURCE_DEPTH]>);
147
148impl SlotPath {
149    /// Construct from a single root-cnode slot key.
150    pub fn root(key: Key) -> Self {
151        Self(smallvec::smallvec![key])
152    }
153
154    /// Construct from a list of steps. Returns `Err` if empty.
155    pub fn new(steps: impl IntoIterator<Item = Key>) -> Result<Self, CapError> {
156        let steps: SmallVec<[Key; MAX_SOURCE_DEPTH]> = steps.into_iter().collect();
157        if steps.is_empty() {
158            // No dedicated "empty path" error variant; reuse SlotOutOfRange.
159            Err(CapError::SlotOutOfRange(0, 0))
160        } else {
161            Ok(Self(steps))
162        }
163    }
164
165    /// The steps of this path (non-empty by construction).
166    pub fn steps(&self) -> &[Key] {
167        &self.0
168    }
169
170    /// Number of steps.
171    pub fn len(&self) -> usize {
172        self.0.len()
173    }
174
175    /// True iff this path has no steps. A well-formed path is never empty;
176    /// this exists for the `clippy::len_without_is_empty` lint and decode
177    /// guards.
178    pub fn is_empty(&self) -> bool {
179        self.0.is_empty()
180    }
181
182    /// True iff this path addresses a slot in the root cnode (one step).
183    pub fn is_root_slot(&self) -> bool {
184        self.0.len() == 1
185    }
186
187    /// The target slot key (the deepest cnode this path addresses).
188    ///
189    /// Returns `None` only for a malformed empty path (construction forbids
190    /// it; the decode/`image_cap` paths reject empty paths eagerly).
191    pub fn target(&self) -> Option<&Key> {
192        self.0.last()
193    }
194
195    /// All steps before the target — the nested-cnode keys to walk.
196    pub fn prefix(&self) -> &[Key] {
197        let len = self.0.len();
198        &self.0[..len.saturating_sub(1)]
199    }
200}