Skip to main content

javm_cap/
slot.rs

1//! Slot addressing for cnodes.
2//!
3//! A `SlotIdx` names one slot in a single cnode (root or nested).
4//! A `SlotPath` walks from the root cnode through nested
5//! `Cap::CNode` slots down to a target slot.
6//!
7//! The root cnode is fixed-size 256 slots per v3 spec; nested
8//! `Cap::CNode` values have variable size `2^k`. `SlotIdx` is a u32
9//! to accommodate the largest cnode reachable in practice.
10
11use crate::error::CapError;
12use alloc::vec::Vec;
13use ssz_derive::{Decode, Encode};
14
15/// Index into a single cnode (root or nested).
16///
17/// For the root cnode (256 slots) only `0..256` is valid; for a
18/// nested `Cap::CNode` of `size_log = k`, `0..2^k` is valid.
19/// `CNodeCap` rejects out-of-range indices.
20#[derive(
21    Debug,
22    Clone,
23    Copy,
24    PartialEq,
25    Eq,
26    Hash,
27    PartialOrd,
28    Ord,
29    Encode,
30    Decode,
31    ssz_derive::HashTreeRoot,
32)]
33pub struct SlotIdx(#[ssz(transparent)] pub u32);
34
35impl SlotIdx {
36    pub const fn new(idx: u32) -> Self {
37        Self(idx)
38    }
39
40    pub const fn get(self) -> u32 {
41        self.0
42    }
43
44    /// True iff this index fits in a cnode of `2^size_log` slots.
45    pub fn fits(self, size_log: u8) -> bool {
46        if size_log >= 32 {
47            // 2^32 indices addressable
48            true
49        } else {
50            self.0 < (1u32 << size_log)
51        }
52    }
53
54    /// Convert to a `usize` for indexing into an in-memory slot vector.
55    pub fn as_usize(self) -> usize {
56        self.0 as usize
57    }
58}
59
60impl From<u8> for SlotIdx {
61    fn from(v: u8) -> Self {
62        Self(v as u32)
63    }
64}
65
66impl From<u16> for SlotIdx {
67    fn from(v: u16) -> Self {
68        Self(v as u32)
69    }
70}
71
72/// Path from the root cnode through nested cnodes to a slot.
73///
74/// `steps` is the sequence of slot indices walked through nested
75/// `Cap::CNode` slots; the final step's slot is the target. An
76/// empty `steps` is invalid (must address some slot).
77///
78/// Example: `SlotPath { steps: vec![SlotIdx(7)] }` addresses
79/// slot 7 of the root cnode. `SlotPath { steps: vec![SlotIdx(7),
80/// SlotIdx(3)] }` addresses slot 3 of the Cap::CNode held in
81/// root slot 7.
82#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode, ssz_derive::HashTreeRoot)]
83pub struct SlotPath {
84    pub steps: Vec<SlotIdx>,
85}
86
87impl SlotPath {
88    /// Construct from a single root-cnode slot index.
89    pub fn root(idx: SlotIdx) -> Self {
90        Self { steps: vec![idx] }
91    }
92
93    /// Construct from a list of steps. Returns `Err` if empty.
94    pub fn new(steps: Vec<SlotIdx>) -> Result<Self, CapError> {
95        if steps.is_empty() {
96            // We need a "Path::Empty" error; use SlotOutOfRange as a
97            // placeholder for now. A dedicated variant could be added
98            // if this surfaces.
99            Err(CapError::SlotOutOfRange(0, 0))
100        } else {
101            Ok(Self { steps })
102        }
103    }
104
105    /// True iff this path has exactly one step (i.e., addresses a
106    /// slot in the root cnode, not nested).
107    pub fn is_root_slot(&self) -> bool {
108        self.steps.len() == 1
109    }
110
111    /// The final step (the slot index in the deepest cnode this
112    /// path addresses).
113    pub fn target(&self) -> SlotIdx {
114        // Invariant from `new` / `root`: steps is non-empty.
115        *self.steps.last().unwrap()
116    }
117
118    /// All steps before the target — the chain of nested-cnode
119    /// indices that must be walked to reach the target's cnode.
120    pub fn prefix(&self) -> &[SlotIdx] {
121        let len = self.steps.len();
122        &self.steps[..len - 1]
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn slot_idx_fits_within_size_log() {
132        assert!(SlotIdx(0).fits(0)); // 2^0 = 1 slot; idx 0 fits
133        assert!(!SlotIdx(1).fits(0)); // doesn't fit
134        assert!(SlotIdx(255).fits(8)); // 2^8 = 256
135        assert!(!SlotIdx(256).fits(8));
136        assert!(SlotIdx(255).fits(16));
137        assert!(SlotIdx(u32::MAX).fits(32));
138    }
139
140    #[test]
141    fn slot_idx_conversions() {
142        assert_eq!(SlotIdx::from(7u8).get(), 7);
143        assert_eq!(SlotIdx::from(1000u16).get(), 1000);
144        assert_eq!(SlotIdx(42).as_usize(), 42);
145    }
146
147    #[test]
148    fn slot_path_root_single_step() {
149        let p = SlotPath::root(SlotIdx(7));
150        assert!(p.is_root_slot());
151        assert_eq!(p.target(), SlotIdx(7));
152        assert_eq!(p.prefix(), &[]);
153    }
154
155    #[test]
156    fn slot_path_nested() {
157        let p = SlotPath::new(vec![SlotIdx(7), SlotIdx(3), SlotIdx(12)]).unwrap();
158        assert!(!p.is_root_slot());
159        assert_eq!(p.target(), SlotIdx(12));
160        assert_eq!(p.prefix(), &[SlotIdx(7), SlotIdx(3)]);
161    }
162
163    #[test]
164    fn slot_path_empty_rejected() {
165        assert!(SlotPath::new(vec![]).is_err());
166    }
167
168    #[test]
169    fn slot_path_equality_by_value() {
170        let a = SlotPath::root(SlotIdx(5));
171        let b = SlotPath::new(vec![SlotIdx(5)]).unwrap();
172        assert_eq!(a, b);
173    }
174}