Skip to main content

rvm_cap/
derivation.rs

1//! Derivation tree for capability revocation propagation.
2//!
3//! When a capability is derived (via `grant`), a parent-child relationship
4//! is established in this tree. Revoking a parent invalidates all derived
5//! capabilities (children, grandchildren, etc.) via subtree walk.
6
7use crate::error::{CapError, CapResult};
8use crate::DEFAULT_CAP_TABLE_CAPACITY;
9
10/// A node in the derivation tree.
11///
12/// Uses a first-child / next-sibling linked list layout for O(1)
13/// insertion and efficient subtree traversal without allocation.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct DerivationNode {
16    /// Whether this node is valid (not revoked).
17    pub is_valid: bool,
18    /// Depth in the derivation tree (0 = root).
19    pub depth: u8,
20    /// Epoch at which this capability was created.
21    pub epoch: u64,
22    /// Index of the first child (or `u32::MAX` if no children).
23    pub first_child: u32,
24    /// Index of the next sibling (or `u32::MAX` if no sibling).
25    pub next_sibling: u32,
26    /// Cached parent index for O(1) parent lookup.
27    /// `u32::MAX` means no parent (root node).
28    pub parent_index: u32,
29}
30
31impl DerivationNode {
32    /// An empty (unused) node.
33    #[inline]
34    #[must_use]
35    pub const fn empty() -> Self {
36        Self {
37            is_valid: false,
38            depth: 0,
39            epoch: 0,
40            first_child: u32::MAX,
41            next_sibling: u32::MAX,
42            parent_index: u32::MAX,
43        }
44    }
45
46    /// A new root node at the given epoch.
47    #[inline]
48    #[must_use]
49    pub const fn new_root(epoch: u64) -> Self {
50        Self {
51            is_valid: true,
52            depth: 0,
53            epoch,
54            first_child: u32::MAX,
55            next_sibling: u32::MAX,
56            parent_index: u32::MAX,
57        }
58    }
59
60    /// A new child node at the given depth and epoch.
61    #[inline]
62    #[must_use]
63    pub const fn new_child(depth: u8, epoch: u64) -> Self {
64        Self {
65            is_valid: true,
66            depth,
67            epoch,
68            first_child: u32::MAX,
69            next_sibling: u32::MAX,
70            parent_index: u32::MAX,
71        }
72    }
73
74    /// Returns true if this node has children.
75    #[inline]
76    #[must_use]
77    pub const fn has_children(&self) -> bool {
78        self.first_child != u32::MAX
79    }
80}
81
82impl Default for DerivationNode {
83    fn default() -> Self {
84        Self::empty()
85    }
86}
87
88/// Derivation tree for tracking parent-child capability relationships.
89///
90/// Fixed-size array indexed by slot index. No heap allocation.
91pub struct DerivationTree<const N: usize = DEFAULT_CAP_TABLE_CAPACITY> {
92    /// Nodes indexed by capability slot index.
93    nodes: [DerivationNode; N],
94    /// Number of active (valid) nodes.
95    count: usize,
96}
97
98impl<const N: usize> DerivationTree<N> {
99    /// Creates a new empty derivation tree.
100    #[inline]
101    #[must_use]
102    pub const fn new() -> Self {
103        Self {
104            nodes: [DerivationNode::empty(); N],
105            count: 0,
106        }
107    }
108
109    /// Returns the number of active nodes.
110    #[inline]
111    #[must_use]
112    pub const fn len(&self) -> usize {
113        self.count
114    }
115
116    /// Returns true if the tree has no active nodes.
117    #[inline]
118    #[must_use]
119    pub const fn is_empty(&self) -> bool {
120        self.count == 0
121    }
122
123    /// Registers a root capability in the tree.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`CapError::TreeFull`] if the index is out of bounds.
128    pub fn add_root(&mut self, index: u32, epoch: u64) -> CapResult<()> {
129        let idx = index as usize;
130        if idx >= N {
131            return Err(CapError::TreeFull);
132        }
133        self.nodes[idx] = DerivationNode::new_root(epoch);
134        self.count += 1;
135        Ok(())
136    }
137
138    /// Registers a derived capability in the tree, linking it to its parent.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`CapError::TreeFull`] if either index is out of bounds.
143    /// Returns [`CapError::Revoked`] if the parent node has been revoked.
144    pub fn add_child(
145        &mut self,
146        parent_index: u32,
147        child_index: u32,
148        depth: u8,
149        epoch: u64,
150    ) -> CapResult<()> {
151        let pidx = parent_index as usize;
152        let cidx = child_index as usize;
153
154        if pidx >= N || cidx >= N {
155            return Err(CapError::TreeFull);
156        }
157        if !self.nodes[pidx].is_valid {
158            return Err(CapError::Revoked);
159        }
160
161        // Create child node and link to parent's child list (prepend).
162        let mut child = DerivationNode::new_child(depth, epoch);
163        child.next_sibling = self.nodes[pidx].first_child;
164        child.parent_index = parent_index;
165        self.nodes[pidx].first_child = child_index;
166        self.nodes[cidx] = child;
167        self.count += 1;
168
169        Ok(())
170    }
171
172    /// Revokes a node and all its descendants. Returns the count of revoked nodes.
173    ///
174    /// # Errors
175    ///
176    /// Returns [`CapError::InvalidHandle`] if the index is out of bounds.
177    /// Returns [`CapError::Revoked`] if the node has already been revoked.
178    pub fn revoke(&mut self, index: u32) -> CapResult<usize> {
179        let idx = index as usize;
180        if idx >= N {
181            return Err(CapError::InvalidHandle);
182        }
183        if !self.nodes[idx].is_valid {
184            return Err(CapError::Revoked);
185        }
186        Ok(self.revoke_subtree(index))
187    }
188
189    /// Returns the depth of the node at the given index.
190    ///
191    /// # Errors
192    ///
193    /// Returns [`CapError::InvalidHandle`] if the index is invalid or the node is revoked.
194    pub fn depth(&self, index: u32) -> CapResult<u8> {
195        let idx = index as usize;
196        if idx >= N || !self.nodes[idx].is_valid {
197            return Err(CapError::InvalidHandle);
198        }
199        Ok(self.nodes[idx].depth)
200    }
201
202    /// Returns true if the node at the given index is valid.
203    #[must_use]
204    pub fn is_valid(&self, index: u32) -> bool {
205        let idx = index as usize;
206        idx < N && self.nodes[idx].is_valid
207    }
208
209    /// Returns a reference to a node by index.
210    #[must_use]
211    pub fn get(&self, index: u32) -> Option<&DerivationNode> {
212        let idx = index as usize;
213        if idx < N && self.nodes[idx].is_valid {
214            Some(&self.nodes[idx])
215        } else {
216            None
217        }
218    }
219
220    /// Collect all valid indices in the subtree rooted at `index`.
221    ///
222    /// Returns a fixed-size array of indices (up to N). This is used
223    /// by revocation to synchronize the capability table.
224    ///
225    /// Uses an iterative stack to avoid stack overflow on wide trees.
226    #[must_use]
227    pub fn collect_subtree(&self, index: u32) -> [u32; N] {
228        let mut result = [u32::MAX; N];
229        let mut result_count = 0;
230        let mut stack = [u32::MAX; N];
231        let mut stack_top = 0;
232
233        // Push the root.
234        let idx = index as usize;
235        if idx < N && self.nodes[idx].is_valid {
236            stack[stack_top] = index;
237            stack_top += 1;
238        }
239
240        while stack_top > 0 {
241            stack_top -= 1;
242            let current = stack[stack_top];
243            let cidx = current as usize;
244            if cidx >= N || !self.nodes[cidx].is_valid {
245                continue;
246            }
247
248            if result_count < N {
249                result[result_count] = current;
250                result_count += 1;
251            }
252
253            // Push children.
254            let mut child = self.nodes[cidx].first_child;
255            while child != u32::MAX {
256                let child_idx = child as usize;
257                if child_idx >= N {
258                    break;
259                }
260                if self.nodes[child_idx].is_valid && stack_top < N {
261                    stack[stack_top] = child;
262                    stack_top += 1;
263                }
264                child = self.nodes[child_idx].next_sibling;
265            }
266        }
267
268        result
269    }
270
271    /// Find the parent of a given node.
272    ///
273    /// Uses the cached `parent_index` field for O(1) lookup. Falls back
274    /// to O(N) scan if the cached index is stale (should not happen in
275    /// normal operation).
276    ///
277    /// Returns `None` for root nodes or if the parent is not found.
278    #[must_use]
279    pub fn find_parent(&self, child_index: u32) -> Option<u32> {
280        let cidx = child_index as usize;
281        if cidx >= N || !self.nodes[cidx].is_valid {
282            return None;
283        }
284        // Root nodes have no parent.
285        if self.nodes[cidx].depth == 0 {
286            return None;
287        }
288        // O(1) fast path via cached parent_index.
289        let pidx = self.nodes[cidx].parent_index;
290        if pidx != u32::MAX {
291            let pi = pidx as usize;
292            if pi < N && self.nodes[pi].is_valid {
293                return Some(pidx);
294            }
295        }
296        // Fallback: scan all nodes to find one whose child chain
297        // includes child_index (handles stale parent_index).
298        for i in 0..N {
299            if !self.nodes[i].is_valid {
300                continue;
301            }
302            let mut cursor = self.nodes[i].first_child;
303            while cursor != u32::MAX {
304                if cursor == child_index {
305                    return Some(u32::try_from(i).unwrap_or(u32::MAX));
306                }
307                let c = cursor as usize;
308                if c >= N {
309                    break;
310                }
311                cursor = self.nodes[c].next_sibling;
312            }
313        }
314        None
315    }
316
317    /// # Security
318    ///
319    /// The previous recursive implementation could overflow the stack on
320    /// wide trees (e.g., 256 siblings under one parent). This iterative
321    /// version uses a fixed-size stack bounded by N to prevent stack
322    /// exhaustion denial-of-service.
323    fn revoke_subtree(&mut self, index: u32) -> usize {
324        let mut stack = [u32::MAX; N];
325        let mut stack_top: usize = 0;
326        let mut count: usize = 0;
327
328        // Push the root of the subtree.
329        let root_idx = index as usize;
330        if root_idx >= N || !self.nodes[root_idx].is_valid {
331            return 0;
332        }
333        stack[stack_top] = index;
334        stack_top += 1;
335
336        while stack_top > 0 {
337            stack_top -= 1;
338            let current = stack[stack_top];
339            let cidx = current as usize;
340
341            if cidx >= N || !self.nodes[cidx].is_valid {
342                continue;
343            }
344
345            // Revoke this node.
346            self.nodes[cidx].is_valid = false;
347            self.count = self.count.saturating_sub(1);
348            count += 1;
349
350            // Push all children onto the stack.
351            let mut child = self.nodes[cidx].first_child;
352            while child != u32::MAX {
353                let child_idx = child as usize;
354                if child_idx >= N {
355                    break;
356                }
357                if self.nodes[child_idx].is_valid && stack_top < N {
358                    stack[stack_top] = child;
359                    stack_top += 1;
360                }
361                // Read sibling BEFORE potentially invalidating the node.
362                child = self.nodes[child_idx].next_sibling;
363            }
364        }
365
366        count
367    }
368}
369
370impl<const N: usize> Default for DerivationTree<N> {
371    fn default() -> Self {
372        Self::new()
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_add_root() {
382        let mut tree = DerivationTree::<64>::new();
383        tree.add_root(0, 1).unwrap();
384        assert_eq!(tree.len(), 1);
385        assert!(tree.is_valid(0));
386        assert_eq!(tree.depth(0).unwrap(), 0);
387    }
388
389    #[test]
390    fn test_add_child() {
391        let mut tree = DerivationTree::<64>::new();
392        tree.add_root(0, 1).unwrap();
393        tree.add_child(0, 1, 1, 1).unwrap();
394        assert_eq!(tree.len(), 2);
395        assert!(tree.is_valid(1));
396        assert_eq!(tree.depth(1).unwrap(), 1);
397        assert!(tree.get(0).unwrap().has_children());
398    }
399
400    #[test]
401    fn test_revoke_subtree() {
402        let mut tree = DerivationTree::<64>::new();
403        tree.add_root(0, 1).unwrap();
404        tree.add_child(0, 1, 1, 1).unwrap();
405        tree.add_child(0, 2, 1, 1).unwrap();
406        tree.add_child(1, 3, 2, 1).unwrap();
407
408        let revoked = tree.revoke(0).unwrap();
409        assert_eq!(revoked, 4);
410        assert_eq!(tree.len(), 0);
411    }
412
413    #[test]
414    fn test_partial_revoke() {
415        let mut tree = DerivationTree::<64>::new();
416        tree.add_root(0, 1).unwrap();
417        tree.add_child(0, 1, 1, 1).unwrap();
418        tree.add_child(0, 2, 1, 1).unwrap();
419        tree.add_child(1, 3, 2, 1).unwrap();
420
421        let revoked = tree.revoke(1).unwrap();
422        assert_eq!(revoked, 2);
423        assert!(tree.is_valid(0));
424        assert!(!tree.is_valid(1));
425        assert!(tree.is_valid(2));
426        assert!(!tree.is_valid(3));
427    }
428
429    #[test]
430    fn test_add_child_to_revoked_parent() {
431        let mut tree = DerivationTree::<64>::new();
432        tree.add_root(0, 1).unwrap();
433        tree.revoke(0).unwrap();
434        assert_eq!(tree.add_child(0, 1, 1, 1), Err(CapError::Revoked));
435    }
436
437    #[test]
438    fn test_out_of_bounds() {
439        let mut tree = DerivationTree::<4>::new();
440        assert_eq!(tree.add_root(10, 1), Err(CapError::TreeFull));
441    }
442}