use alloc::collections::BTreeSet;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Right {
Read,
Write,
ReadWrite,
}
impl Right {
pub fn allows_read(self) -> bool {
matches!(self, Right::Read | Right::ReadWrite)
}
pub fn allows_write(self) -> bool {
matches!(self, Right::Write | Right::ReadWrite)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Resource {
Page(u64),
Channel(u64),
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Capability {
serial: u64,
resource: Resource,
right: Right,
}
impl fmt::Debug for Capability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Capability")
.field("resource", &self.resource)
.field("right", &self.right)
.field("serial", &"<redacted>")
.finish()
}
}
impl Capability {
pub fn resource(&self) -> Resource {
self.resource
}
pub fn right(&self) -> Right {
self.right
}
pub fn authorizes(&self, resource: Resource, right: Right) -> bool {
self.resource == resource
&& match right {
Right::Read => self.right.allows_read(),
Right::Write => self.right.allows_write(),
Right::ReadWrite => self.right.allows_read() && self.right.allows_write(),
}
}
}
#[derive(Debug, Default)]
pub struct CapabilityIssuer {
next_serial: u64,
live: BTreeSet<u64>,
}
impl CapabilityIssuer {
pub fn new() -> Self {
Self {
next_serial: 0,
live: BTreeSet::new(),
}
}
pub fn mint(&mut self, resource: Resource, right: Right) -> Capability {
let serial = self.next_serial;
self.next_serial = self
.next_serial
.checked_add(1)
.expect("capability serial space exhausted");
self.live.insert(serial);
Capability {
serial,
resource,
right,
}
}
pub fn revoke(&mut self, cap: &Capability) {
self.live.remove(&cap.serial);
}
pub fn validate(&self, cap: &Capability) -> bool {
self.live.contains(&cap.serial)
}
pub fn authorizes(&self, cap: &Capability, resource: Resource, right: Right) -> bool {
self.validate(cap) && cap.authorizes(resource, right)
}
}
pub type SharedIssuer = Rc<RefCell<CapabilityIssuer>>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minted_capability_validates_and_authorizes() {
let mut issuer = CapabilityIssuer::new();
let cap = issuer.mint(Resource::Page(3), Right::ReadWrite);
assert!(issuer.validate(&cap));
assert!(cap.authorizes(Resource::Page(3), Right::Read));
assert!(cap.authorizes(Resource::Page(3), Right::Write));
assert!(!cap.authorizes(Resource::Page(4), Right::Read));
}
#[test]
fn read_only_capability_denies_write() {
let mut issuer = CapabilityIssuer::new();
let cap = issuer.mint(Resource::Channel(1), Right::Read);
assert!(cap.authorizes(Resource::Channel(1), Right::Read));
assert!(!cap.authorizes(Resource::Channel(1), Right::Write));
}
#[test]
fn revoked_capability_fails_validation() {
let mut issuer = CapabilityIssuer::new();
let cap = issuer.mint(Resource::Page(0), Right::Read);
assert!(issuer.validate(&cap));
issuer.revoke(&cap);
assert!(!issuer.validate(&cap));
}
#[test]
fn capabilities_from_other_issuers_do_not_validate() {
let mut a = CapabilityIssuer::new();
let b = CapabilityIssuer::new();
let cap = a.mint(Resource::Page(0), Right::Read);
assert!(!b.validate(&cap));
}
}