use crate::error::{CapError, CapResult};
use crate::DEFAULT_CAP_TABLE_CAPACITY;
use rvm_types::{CapRights, CapToken, CapType, PartitionId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CapSlot {
pub token: CapToken,
pub generation: u32,
pub owner: PartitionId,
pub depth: u8,
pub parent_index: u32,
pub badge: u64,
}
impl CapSlot {
#[inline]
#[must_use]
const fn empty() -> Self {
Self {
token: CapToken::new(0, CapType::Region, CapRights::empty(), 0),
generation: 0,
owner: PartitionId::new(0),
depth: 0,
parent_index: u32::MAX,
badge: 0,
}
}
#[inline]
#[must_use]
pub const fn is_valid(&self) -> bool {
self.generation != 0
}
#[inline]
#[must_use]
pub const fn matches(&self, generation: u32) -> bool {
self.is_valid() && self.generation == generation
}
#[inline]
pub fn invalidate(&mut self) {
let next_gen = self.generation.wrapping_add(1);
let safe_gen = if next_gen == 0 { 1 } else { next_gen };
self.parent_index = safe_gen;
self.generation = 0;
}
#[inline]
#[must_use]
const fn next_generation(&self) -> u32 {
if self.generation != 0 {
self.generation
} else if self.parent_index == u32::MAX {
1
} else {
self.parent_index
}
}
}
pub struct CapabilityTable<const N: usize = DEFAULT_CAP_TABLE_CAPACITY> {
slots: [CapSlot; N],
count: usize,
free_hint: usize,
}
impl<const N: usize> core::fmt::Debug for CapabilityTable<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CapabilityTable")
.field("count", &self.count)
.field("capacity", &N)
.finish_non_exhaustive()
}
}
impl<const N: usize> CapabilityTable<N> {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
slots: [CapSlot::empty(); N],
count: 0,
free_hint: 0,
}
}
#[inline]
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.count
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
#[must_use]
pub const fn is_full(&self) -> bool {
self.count >= N
}
#[allow(clippy::cast_possible_truncation)]
pub fn insert_root(
&mut self,
token: CapToken,
owner: PartitionId,
badge: u64,
) -> CapResult<(u32, u32)> {
let index = self.find_free_slot()?;
let generation = self.slots[index].next_generation();
self.slots[index] = CapSlot {
token,
generation,
owner,
depth: 0,
parent_index: u32::MAX,
badge,
};
self.count += 1;
Ok((index as u32, generation))
}
#[allow(clippy::cast_possible_truncation)]
pub fn insert_derived(
&mut self,
token: CapToken,
owner: PartitionId,
depth: u8,
parent_index: u32,
badge: u64,
) -> CapResult<(u32, u32)> {
let index = self.find_free_slot()?;
let generation = self.slots[index].next_generation();
self.slots[index] = CapSlot {
token,
generation,
owner,
depth,
parent_index,
badge,
};
self.count += 1;
Ok((index as u32, generation))
}
#[inline]
pub fn lookup(&self, index: u32, generation: u32) -> CapResult<&CapSlot> {
let idx = index as usize;
if idx >= N {
return Err(CapError::InvalidHandle);
}
let slot = &self.slots[idx];
if !slot.is_valid() {
return Err(CapError::InvalidHandle);
}
if slot.generation != generation {
return Err(CapError::StaleHandle);
}
Ok(slot)
}
pub fn lookup_mut(&mut self, index: u32, generation: u32) -> CapResult<&mut CapSlot> {
let idx = index as usize;
if idx >= N {
return Err(CapError::InvalidHandle);
}
let slot = &mut self.slots[idx];
if !slot.is_valid() {
return Err(CapError::InvalidHandle);
}
if slot.generation != generation {
return Err(CapError::StaleHandle);
}
Ok(slot)
}
pub fn remove(&mut self, index: u32, generation: u32) -> CapResult<()> {
let idx = index as usize;
if idx >= N {
return Err(CapError::InvalidHandle);
}
let slot = &mut self.slots[idx];
if !slot.is_valid() {
return Err(CapError::InvalidHandle);
}
if slot.generation != generation {
return Err(CapError::StaleHandle);
}
slot.invalidate();
self.count -= 1;
if idx < self.free_hint {
self.free_hint = idx;
}
Ok(())
}
pub(crate) fn force_invalidate(&mut self, index: u32) {
let idx = index as usize;
if idx < N && self.slots[idx].is_valid() {
self.slots[idx].invalidate();
self.count -= 1;
if idx < self.free_hint {
self.free_hint = idx;
}
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn iter(&self) -> impl Iterator<Item = (u32, &CapSlot)> {
self.slots
.iter()
.enumerate()
.filter(|(_, s)| s.is_valid())
.map(|(i, s)| (i as u32, s))
}
fn find_free_slot(&mut self) -> CapResult<usize> {
for i in self.free_hint..N {
if !self.slots[i].is_valid() {
self.free_hint = i + 1;
return Ok(i);
}
}
for i in 0..self.free_hint {
if !self.slots[i].is_valid() {
self.free_hint = i + 1;
return Ok(i);
}
}
Err(CapError::TableFull)
}
}
impl<const N: usize> Default for CapabilityTable<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_token(id: u64) -> CapToken {
CapToken::new(
id,
CapType::Region,
CapRights::READ.union(CapRights::WRITE),
0,
)
}
#[test]
fn test_insert_and_lookup() {
let mut table = CapabilityTable::<16>::new();
let owner = PartitionId::new(1);
let token = test_token(100);
let (idx, gen) = table.insert_root(token, owner, 0).unwrap();
assert_eq!(table.len(), 1);
let slot = table.lookup(idx, gen).unwrap();
assert_eq!(slot.token.id(), 100);
assert_eq!(slot.depth, 0);
assert_eq!(slot.parent_index, u32::MAX);
}
#[test]
fn test_remove_and_stale() {
let mut table = CapabilityTable::<16>::new();
let owner = PartitionId::new(1);
let token = test_token(200);
let (idx, gen) = table.insert_root(token, owner, 0).unwrap();
table.remove(idx, gen).unwrap();
assert_eq!(table.len(), 0);
assert!(table.lookup(idx, gen).is_err());
}
#[test]
fn test_generation_counter() {
let mut table = CapabilityTable::<16>::new();
let owner = PartitionId::new(1);
let token = test_token(300);
let (idx, gen1) = table.insert_root(token, owner, 0).unwrap();
table.remove(idx, gen1).unwrap();
let (idx2, gen2) = table.insert_root(token, owner, 0).unwrap();
assert_eq!(idx, idx2);
assert_ne!(gen1, gen2);
assert!(table.lookup(idx, gen1).is_err());
assert!(table.lookup(idx2, gen2).is_ok());
}
#[test]
fn test_table_full() {
let mut table = CapabilityTable::<2>::new();
let owner = PartitionId::new(1);
let token = test_token(400);
table.insert_root(token, owner, 0).unwrap();
table.insert_root(token, owner, 0).unwrap();
assert!(table.is_full());
assert_eq!(table.insert_root(token, owner, 0), Err(CapError::TableFull));
}
#[test]
fn test_insert_derived() {
let mut table = CapabilityTable::<16>::new();
let owner = PartitionId::new(1);
let token = test_token(500);
let (parent_idx, _) = table.insert_root(token, owner, 0).unwrap();
let derived = CapToken::new(501, CapType::Region, CapRights::READ, 0);
let (child_idx, child_gen) = table
.insert_derived(derived, owner, 1, parent_idx, 42)
.unwrap();
let slot = table.lookup(child_idx, child_gen).unwrap();
assert_eq!(slot.depth, 1);
assert_eq!(slot.parent_index, parent_idx);
assert_eq!(slot.badge, 42);
}
#[test]
fn test_iter_valid_entries() {
let mut table = CapabilityTable::<16>::new();
let owner = PartitionId::new(1);
table.insert_root(test_token(1), owner, 0).unwrap();
table.insert_root(test_token(2), owner, 0).unwrap();
table.insert_root(test_token(3), owner, 0).unwrap();
let count = table.iter().count();
assert_eq!(count, 3);
}
}