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}