use crate::cap::Cap;
use crate::error::CapSecError;
use crate::permission::Permission;
use std::marker::PhantomData;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
pub struct RuntimeCap<P: Permission> {
_phantom: PhantomData<P>,
_not_send: PhantomData<*const ()>,
active: Arc<AtomicBool>,
}
impl<P: Permission> RuntimeCap<P> {
pub fn new(_cap: Cap<P>) -> (Self, Revoker) {
let active = Arc::new(AtomicBool::new(true));
let revoker = Revoker {
active: Arc::clone(&active),
};
let cap = Self {
_phantom: PhantomData,
_not_send: PhantomData,
active,
};
(cap, revoker)
}
pub fn try_cap(&self) -> Result<Cap<P>, CapSecError> {
if self.active.load(Ordering::Acquire) {
Ok(Cap::new())
} else {
Err(CapSecError::Revoked)
}
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Acquire)
}
pub fn make_send(self) -> RuntimeSendCap<P> {
RuntimeSendCap {
_phantom: PhantomData,
active: self.active,
}
}
}
impl<P: Permission> Clone for RuntimeCap<P> {
fn clone(&self) -> Self {
Self {
_phantom: PhantomData,
_not_send: PhantomData,
active: Arc::clone(&self.active),
}
}
}
pub struct Revoker {
active: Arc<AtomicBool>,
}
impl Revoker {
pub fn revoke(&self) {
self.active.store(false, Ordering::Release);
}
pub fn is_revoked(&self) -> bool {
!self.active.load(Ordering::Acquire)
}
}
impl Clone for Revoker {
fn clone(&self) -> Self {
Self {
active: Arc::clone(&self.active),
}
}
}
pub struct RuntimeSendCap<P: Permission> {
_phantom: PhantomData<P>,
active: Arc<AtomicBool>,
}
unsafe impl<P: Permission> Send for RuntimeSendCap<P> {}
unsafe impl<P: Permission> Sync for RuntimeSendCap<P> {}
impl<P: Permission> RuntimeSendCap<P> {
pub fn try_cap(&self) -> Result<Cap<P>, CapSecError> {
if self.active.load(Ordering::Acquire) {
Ok(Cap::new())
} else {
Err(CapSecError::Revoked)
}
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Acquire)
}
}
impl<P: Permission> Clone for RuntimeSendCap<P> {
fn clone(&self) -> Self {
Self {
_phantom: PhantomData,
active: Arc::clone(&self.active),
}
}
}
pub struct TimedCap<P: Permission> {
_phantom: PhantomData<P>,
_not_send: PhantomData<*const ()>,
expires_at: Instant,
}
impl<P: Permission> TimedCap<P> {
pub fn new(_cap: Cap<P>, ttl: Duration) -> Self {
Self {
_phantom: PhantomData,
_not_send: PhantomData,
expires_at: Instant::now() + ttl,
}
}
pub fn try_cap(&self) -> Result<Cap<P>, CapSecError> {
if Instant::now() < self.expires_at {
Ok(Cap::new())
} else {
Err(CapSecError::Expired)
}
}
pub fn is_active(&self) -> bool {
Instant::now() < self.expires_at
}
pub fn remaining(&self) -> Duration {
self.expires_at.saturating_duration_since(Instant::now())
}
pub fn make_send(self) -> TimedSendCap<P> {
TimedSendCap {
_phantom: PhantomData,
expires_at: self.expires_at,
}
}
}
impl<P: Permission> Clone for TimedCap<P> {
fn clone(&self) -> Self {
Self {
_phantom: PhantomData,
_not_send: PhantomData,
expires_at: self.expires_at,
}
}
}
pub struct TimedSendCap<P: Permission> {
_phantom: PhantomData<P>,
expires_at: Instant,
}
unsafe impl<P: Permission> Send for TimedSendCap<P> {}
unsafe impl<P: Permission> Sync for TimedSendCap<P> {}
impl<P: Permission> TimedSendCap<P> {
pub fn try_cap(&self) -> Result<Cap<P>, CapSecError> {
if Instant::now() < self.expires_at {
Ok(Cap::new())
} else {
Err(CapSecError::Expired)
}
}
pub fn is_active(&self) -> bool {
Instant::now() < self.expires_at
}
pub fn remaining(&self) -> Duration {
self.expires_at.saturating_duration_since(Instant::now())
}
}
impl<P: Permission> Clone for TimedSendCap<P> {
fn clone(&self) -> Self {
Self {
_phantom: PhantomData,
expires_at: self.expires_at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::permission::FsRead;
use std::mem::size_of;
#[test]
fn runtime_cap_try_cap_succeeds_when_active() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (rcap, _revoker) = RuntimeCap::new(cap);
assert!(rcap.try_cap().is_ok());
}
#[test]
fn runtime_cap_try_cap_fails_after_revocation() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (rcap, revoker) = RuntimeCap::new(cap);
revoker.revoke();
assert!(matches!(rcap.try_cap(), Err(CapSecError::Revoked)));
}
#[test]
fn revoker_is_idempotent() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (_rcap, revoker) = RuntimeCap::new(cap);
revoker.revoke();
revoker.revoke(); assert!(revoker.is_revoked());
}
#[test]
fn revoker_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Revoker>();
}
#[test]
fn runtime_send_cap_crosses_threads() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (rcap, _revoker) = RuntimeCap::new(cap);
let send_cap = rcap.make_send();
std::thread::spawn(move || {
assert!(send_cap.try_cap().is_ok());
})
.join()
.unwrap();
}
#[test]
fn runtime_send_cap_revocation_crosses_threads() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (rcap, revoker) = RuntimeCap::new(cap);
let send_cap = rcap.make_send();
revoker.revoke();
std::thread::spawn(move || {
assert!(matches!(send_cap.try_cap(), Err(CapSecError::Revoked)));
})
.join()
.unwrap();
}
#[test]
fn cloned_runtime_cap_shares_revocation() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let (rcap, revoker) = RuntimeCap::new(cap);
let rcap2 = rcap.clone();
revoker.revoke();
assert!(matches!(rcap.try_cap(), Err(CapSecError::Revoked)));
assert!(matches!(rcap2.try_cap(), Err(CapSecError::Revoked)));
}
#[test]
fn runtime_cap_is_small() {
assert!(size_of::<RuntimeCap<FsRead>>() <= 2 * size_of::<usize>());
}
#[test]
fn timed_cap_succeeds_before_expiry() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let tcap = TimedCap::new(cap, Duration::from_secs(60));
assert!(tcap.try_cap().is_ok());
}
#[test]
fn timed_cap_fails_after_expiry() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let tcap = TimedCap::new(cap, Duration::from_millis(5));
std::thread::sleep(Duration::from_millis(50));
assert!(matches!(tcap.try_cap(), Err(CapSecError::Expired)));
}
#[test]
fn timed_cap_remaining_decreases() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let tcap = TimedCap::new(cap, Duration::from_secs(60));
let r1 = tcap.remaining();
std::thread::sleep(Duration::from_millis(10));
let r2 = tcap.remaining();
assert!(r2 < r1);
}
#[test]
fn timed_cap_remaining_is_zero_after_expiry() {
let root = crate::root::test_root();
let cap = root.grant::<FsRead>();
let tcap = TimedCap::new(cap, Duration::from_millis(5));
std::thread::sleep(Duration::from_millis(50));
assert_eq!(tcap.remaining(), Duration::ZERO);
}
}