#![no_std] //! Fixed-capacity, single-owner user handle claims. use core::cell::RefCell; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Fault { Invalid, Duplicate, Full, Stale } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Inventory { pub live: usize, pub claims: usize, pub close_attempts: usize, pub closes: usize, } struct State { slots: [Option; N], claims: usize, close_attempts: usize, closes: usize, } pub struct Table { state: RefCell> } impl Table { pub const fn new() -> Self { Self {state: RefCell::new(State { slots: [None; N], claims: 0, close_attempts: 0, closes: 0, })} } pub fn claim(&self, raw: u64) -> Result, Fault> { if raw == 0 { return Err(Fault::Invalid); } let mut state = self.state.borrow_mut(); if state.slots.contains(&Some(raw)) { return Err(Fault::Duplicate); } let slot = state.slots.iter().position(Option::is_none).ok_or(Fault::Full)?; state.slots[slot] = Some(raw); state.claims += 1; Ok(Owned {table: self, raw, armed: true}) } /// Kernel-facing close token check. A second close is stale. pub fn close_raw(&self, raw: u64) -> Result<(), Fault> { let mut state = self.state.borrow_mut(); state.close_attempts += 1; let slot = state.slots.iter().position(|entry| *entry == Some(raw)) .ok_or(Fault::Stale)?; state.slots[slot] = None; state.closes += 1; Ok(()) } pub fn inventory(&self) -> Inventory { let state = self.state.borrow(); Inventory {live: state.slots.iter().filter(|slot| slot.is_some()).count(), claims: state.claims, close_attempts: state.close_attempts, closes: state.closes} } } impl Default for Table { fn default() -> Self { Self::new() } } pub struct Owned<'a, const N: usize> { table: &'a Table, raw: u64, armed: bool, } impl Owned<'_, N> { pub const fn raw(&self) -> u64 { self.raw } /// Consumes the sole owner. Drop observes `armed = false` afterwards. pub fn close(mut self) -> Result<(), Fault> { self.armed = false; self.table.close_raw(self.raw) } } impl Drop for Owned<'_, N> { fn drop(&mut self) { if self.armed { let _ = self.table.close_raw(self.raw); self.armed = false; } } } /// Experimental guest-only version 3 handle wire. It is separate from the /// frozen v1 and v2 dispatchers and requires a matching kernel owner. #[cfg(all(target_arch = "aarch64", target_os = "none"))] pub mod native { use turkos_os_syscall_abi::syscall_abi::{AbiResult, DecodeError, Errno}; const VERSION: u64 = 3; const OPEN: u64 = 6; const CLOSE: u64 = 7; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Kernel(Errno), Wire(DecodeError), InvalidReturn } fn call(number: u64, argument: u64) -> Result { let mut result = argument; unsafe { core::arch::asm!("svc #0", inout("x0") result, in("x7") VERSION, in("x8") number, options(nostack)); } match AbiResult::decode(result).map_err(Error::Wire)? { AbiResult::Ok(value) => Ok(value.value()), AbiResult::Err(error) => Err(Error::Kernel(error)), } } pub struct NativeHandle { raw: u64, armed: bool } impl NativeHandle { pub fn open() -> Result { let raw = call(OPEN, 0)?; if raw == 0 { return Err(Error::InvalidReturn); } Ok(Self {raw, armed: true}) } pub const fn raw(&self) -> u64 { self.raw } pub fn close(mut self) -> Result<(), Error> { self.armed = false; unsafe { Self::close_raw(self.raw) } } /// # Safety /// The caller must know no live `NativeHandle` still owns this token. pub unsafe fn close_raw(raw: u64) -> Result<(), Error> { if call(CLOSE, raw)? == 0 { Ok(()) } else { Err(Error::InvalidReturn) } } } impl Drop for NativeHandle { fn drop(&mut self) { if self.armed { let _ = unsafe { Self::close_raw(self.raw) }; self.armed = false; } } } }