use super::LinearMemory;
use crate::types::{CapId, SecurityLevel, Size};
#[derive(Debug)]
pub enum PluginError {
CapNotHeld(CapId),
MemoryOutOfBounds(u64, u64, u64),
MemoryQuotaExceeded(u64, u64, u64),
CapQuotaExceeded(u64, u64),
IpcQueueLimitExceeded(u64, u64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PluginLocal;
pub const DEFAULT_QUOTA: u64 = u64::MAX / 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginState {
pub(crate) level: SecurityLevel,
pub(crate) memory: LinearMemory,
pub(crate) held_caps: Vec<CapId>,
pub(crate) local_state: PluginLocal,
pub(crate) memory_quota: u64,
pub(crate) memory_used: u64,
pub(crate) cap_quota: u64,
pub(crate) ipc_queue_limit: u64,
}
impl PluginState {
pub fn empty(level: SecurityLevel, mem_size: Size) -> Self {
PluginState {
level,
memory: LinearMemory::empty(mem_size),
held_caps: Vec::new(),
local_state: PluginLocal,
memory_quota: DEFAULT_QUOTA,
memory_used: 0,
cap_quota: DEFAULT_QUOTA,
ipc_queue_limit: DEFAULT_QUOTA,
}
}
pub fn with_quotas(
level: SecurityLevel,
mem_size: Size,
memory_quota: u64,
cap_quota: u64,
ipc_queue_limit: u64,
) -> Self {
PluginState {
level,
memory: LinearMemory::empty(mem_size),
held_caps: Vec::new(),
local_state: PluginLocal,
memory_quota,
memory_used: 0,
cap_quota,
ipc_queue_limit,
}
}
#[inline]
pub fn holds_cap(&self, cap_id: CapId) -> bool {
self.held_caps.binary_search(&cap_id).is_ok()
}
pub fn grant_cap(&self, cap_id: CapId) -> Self {
let mut new_caps = self.held_caps.clone();
Self::insert_cap_sorted(&mut new_caps, cap_id);
PluginState {
level: self.level,
memory: self.memory.clone(),
held_caps: new_caps,
local_state: self.local_state,
memory_quota: self.memory_quota,
memory_used: self.memory_used,
cap_quota: self.cap_quota,
ipc_queue_limit: self.ipc_queue_limit,
}
}
fn insert_cap_sorted(caps: &mut Vec<CapId>, cap_id: CapId) {
match caps.binary_search(&cap_id) {
Ok(_) => {} Err(pos) => caps.insert(pos, cap_id),
}
}
pub fn grant_cap_mut(&mut self, cap_id: CapId) {
Self::insert_cap_sorted(&mut self.held_caps, cap_id);
}
pub fn revoke_cap(&self, cap_id: CapId) -> Self {
let mut new_caps = self.held_caps.clone();
Self::remove_cap_sorted(&mut new_caps, cap_id);
PluginState {
level: self.level,
memory: self.memory.clone(),
held_caps: new_caps,
local_state: self.local_state,
memory_quota: self.memory_quota,
memory_used: self.memory_used,
cap_quota: self.cap_quota,
ipc_queue_limit: self.ipc_queue_limit,
}
}
fn remove_cap_sorted(caps: &mut Vec<CapId>, cap_id: CapId) {
if let Ok(pos) = caps.binary_search(&cap_id) {
caps.remove(pos);
}
}
pub fn revoke_cap_mut(&mut self, cap_id: CapId) {
Self::remove_cap_sorted(&mut self.held_caps, cap_id);
}
#[inline]
pub fn level(&self) -> SecurityLevel {
self.level
}
#[inline]
pub fn memory(&self) -> &LinearMemory {
&self.memory
}
#[inline]
#[allow(dead_code)] pub(crate) fn memory_mut(&mut self) -> &mut LinearMemory {
&mut self.memory
}
#[inline]
pub fn held_cap_count(&self) -> usize {
self.held_caps.len()
}
#[inline]
pub fn local_state(&self) -> PluginLocal {
self.local_state
}
pub fn held_caps(&self) -> Vec<CapId> {
self.held_caps.clone()
}
pub fn held_caps_ref(&self) -> &Vec<CapId> {
&self.held_caps
}
#[inline]
pub fn memory_bounds(&self) -> Size {
self.memory.bounds()
}
#[inline]
pub fn can_alloc_memory(&self, size: u64) -> bool {
match self.memory_used.checked_add(size) {
Some(total) => total <= self.memory_quota,
None => false, }
}
#[inline]
pub fn can_hold_cap(&self) -> bool {
(self.held_caps.len() as u64) < self.cap_quota
}
#[inline]
pub fn can_queue_ipc(&self, queue_len: u64) -> bool {
queue_len < self.ipc_queue_limit
}
pub fn alloc_memory(&mut self, size: u64) -> bool {
if !self.can_alloc_memory(size) {
return false;
}
self.memory_used = self.memory_used.saturating_add(size);
true
}
pub fn free_memory(&mut self, size: u64) {
self.memory_used = self.memory_used.saturating_sub(size);
}
#[inline]
pub fn memory_used(&self) -> u64 {
self.memory_used
}
#[inline]
pub fn memory_quota(&self) -> u64 {
self.memory_quota
}
#[inline]
pub fn cap_quota(&self) -> u64 {
self.cap_quota
}
#[inline]
pub fn ipc_queue_limit(&self) -> u64 {
self.ipc_queue_limit
}
}
impl Default for PluginState {
fn default() -> Self {
PluginState {
level: SecurityLevel::Public,
memory: LinearMemory::empty(0),
held_caps: Vec::new(),
local_state: PluginLocal,
memory_quota: DEFAULT_QUOTA,
memory_used: 0,
cap_quota: DEFAULT_QUOTA,
ipc_queue_limit: DEFAULT_QUOTA,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_local_default() {
let local = PluginLocal;
assert_eq!(local, PluginLocal);
}
#[test]
fn test_plugin_state_empty() {
let ps = PluginState::empty(SecurityLevel::Confidential, 1024);
assert_eq!(ps.level(), SecurityLevel::Confidential);
assert_eq!(ps.memory_bounds(), 1024);
assert_eq!(ps.held_cap_count(), 0);
}
#[test]
fn test_plugin_state_default_level() {
let ps = PluginState::default();
assert_eq!(ps.level(), SecurityLevel::Public);
}
#[test]
fn test_plugin_state_grant_cap() {
let mut ps = PluginState::empty(SecurityLevel::Public, 0);
ps.grant_cap_mut(42);
assert!(ps.holds_cap(42));
assert_eq!(ps.held_cap_count(), 1);
ps.grant_cap_mut(42);
assert!(ps.holds_cap(42));
assert_eq!(ps.held_cap_count(), 1);
}
#[test]
fn test_plugin_state_grant_cap_mem() {
let ps = PluginState::empty(SecurityLevel::Public, 0);
let ps = ps.grant_cap(42);
assert!(ps.holds_cap(42));
}
#[test]
fn test_plugin_state_grant_cap_preserves() {
let ps = PluginState::empty(SecurityLevel::Public, 0);
let ps = ps.grant_cap(1);
let ps = ps.grant_cap(2);
assert!(ps.holds_cap(1));
assert!(ps.holds_cap(2));
}
#[test]
fn test_plugin_state_revoke_cap_not_mem() {
let ps = PluginState::empty(SecurityLevel::Public, 0);
let ps = ps.grant_cap(42);
let ps = ps.revoke_cap(42);
assert!(!ps.holds_cap(42));
assert_eq!(ps.held_cap_count(), 0);
}
#[test]
fn test_plugin_state_revoke_cap_preserves() {
let ps = PluginState::empty(SecurityLevel::Public, 0);
let ps = ps.grant_cap(1);
let ps = ps.grant_cap(2);
let ps = ps.revoke_cap(1);
assert!(!ps.holds_cap(1));
assert!(ps.holds_cap(2));
}
#[test]
fn test_plugin_state_grant_cap_memory() {
let ps = PluginState::empty(SecurityLevel::Public, 1024);
let old_memory = ps.memory().clone();
let ps = ps.grant_cap(42);
assert_eq!(ps.memory(), &old_memory);
}
#[test]
fn test_plugin_state_grant_cap_memory_bounds() {
let ps = PluginState::empty(SecurityLevel::Public, 1024);
let ps = ps.grant_cap(42);
assert_eq!(ps.memory_bounds(), 1024);
}
#[test]
fn test_plugin_state_grant_cap_level() {
let ps = PluginState::empty(SecurityLevel::Confidential, 0);
let ps = ps.grant_cap(42);
assert_eq!(ps.level(), SecurityLevel::Confidential);
}
#[test]
fn test_plugin_state_grant_cap_local_state() {
let ps = PluginState::empty(SecurityLevel::Public, 0);
let old_local = ps.local_state();
let ps = ps.grant_cap(42);
assert_eq!(ps.local_state(), old_local);
}
#[test]
fn test_plugin_state_held_caps_iterator() {
let mut ps = PluginState::empty(SecurityLevel::Public, 0);
ps.grant_cap_mut(1);
ps.grant_cap_mut(3);
ps.grant_cap_mut(2);
let caps = ps.held_caps();
assert_eq!(caps, vec![1, 2, 3]);
}
#[test]
fn test_default_quota() {
let ps = PluginState::default();
assert_eq!(ps.memory_quota(), DEFAULT_QUOTA);
assert_eq!(ps.memory_used(), 0);
assert_eq!(ps.cap_quota(), DEFAULT_QUOTA);
assert_eq!(ps.ipc_queue_limit(), DEFAULT_QUOTA);
}
#[test]
fn test_with_quotas() {
let ps = PluginState::with_quotas(
SecurityLevel::Confidential,
1024,
4096, 10, 100, );
assert_eq!(ps.level(), SecurityLevel::Confidential);
assert_eq!(ps.memory_bounds(), 1024);
assert_eq!(ps.memory_quota(), 4096);
assert_eq!(ps.cap_quota(), 10);
assert_eq!(ps.ipc_queue_limit(), 100);
}
#[test]
fn test_can_alloc_memory() {
let ps = PluginState::with_quotas(
SecurityLevel::Public,
0,
1000, 10,
100,
);
assert!(ps.can_alloc_memory(500));
assert!(ps.can_alloc_memory(1000));
assert!(!ps.can_alloc_memory(1001));
}
#[test]
fn test_alloc_memory_tracking() {
let mut ps = PluginState::with_quotas(SecurityLevel::Public, 0, 1000, 10, 100);
assert!(ps.alloc_memory(400));
assert_eq!(ps.memory_used(), 400);
assert!(ps.alloc_memory(400));
assert_eq!(ps.memory_used(), 800);
assert!(!ps.alloc_memory(300));
assert_eq!(ps.memory_used(), 800);
assert!(ps.alloc_memory(200));
assert_eq!(ps.memory_used(), 1000);
}
#[test]
fn test_free_memory_tracking() {
let mut ps = PluginState::with_quotas(SecurityLevel::Public, 0, 1000, 10, 100);
ps.alloc_memory(800);
assert_eq!(ps.memory_used(), 800);
ps.free_memory(300);
assert_eq!(ps.memory_used(), 500);
assert!(ps.can_alloc_memory(500));
assert!(ps.alloc_memory(500));
assert_eq!(ps.memory_used(), 1000);
}
#[test]
fn test_can_hold_cap() {
let mut ps = PluginState::with_quotas(
SecurityLevel::Public,
0,
1000,
3, 100,
);
assert!(ps.can_hold_cap());
ps.grant_cap_mut(1);
assert!(ps.can_hold_cap());
ps.grant_cap_mut(2);
assert!(ps.can_hold_cap());
ps.grant_cap_mut(3);
assert!(!ps.can_hold_cap());
ps.revoke_cap_mut(1);
assert!(ps.can_hold_cap());
}
#[test]
fn test_can_queue_ipc() {
let ps = PluginState::with_quotas(
SecurityLevel::Public,
0,
1000,
10,
5, );
assert!(ps.can_queue_ipc(0));
assert!(ps.can_queue_ipc(4));
assert!(!ps.can_queue_ipc(5));
assert!(!ps.can_queue_ipc(100));
}
#[test]
fn test_quota_overflow_safety() {
let mut ps = PluginState::with_quotas(
SecurityLevel::Public,
0,
u64::MAX, 10,
100,
);
ps.memory_used = u64::MAX - 10;
assert!(!ps.can_alloc_memory(100)); assert!(ps.can_alloc_memory(5)); }
}