use tpt_archon_core::block::{BlockDevice, StorageError};
use tpt_archon_core::page::{BufferPool, Page};
use crate::capability::{Capability, Resource, Right, SharedIssuer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CacheError {
Denied,
Storage(StorageError),
}
impl From<StorageError> for CacheError {
fn from(e: StorageError) -> Self {
CacheError::Storage(e)
}
}
pub trait UnifiedPageCache {
fn map_read(&mut self, cap: &Capability, block_id: u64) -> Result<&Page, CacheError>;
fn map_write(&mut self, cap: &Capability, block_id: u64) -> Result<&mut Page, CacheError>;
fn unmap(&mut self, block_id: u64);
}
pub struct CorePageCache<D: BlockDevice> {
pool: BufferPool<D>,
issuer: SharedIssuer,
}
impl<D: BlockDevice> CorePageCache<D> {
pub fn new(pool: BufferPool<D>, issuer: SharedIssuer) -> Self {
Self { pool, issuer }
}
pub fn into_pool(self) -> BufferPool<D> {
self.pool
}
}
impl<D: BlockDevice> UnifiedPageCache for CorePageCache<D> {
fn map_read(&mut self, cap: &Capability, block_id: u64) -> Result<&Page, CacheError> {
if !self
.issuer
.borrow()
.authorizes(cap, Resource::Page(block_id), Right::Read)
{
return Err(CacheError::Denied);
}
Ok(self.pool.fetch(block_id)?)
}
fn map_write(&mut self, cap: &Capability, block_id: u64) -> Result<&mut Page, CacheError> {
if !self
.issuer
.borrow()
.authorizes(cap, Resource::Page(block_id), Right::Write)
{
return Err(CacheError::Denied);
}
Ok(self.pool.fetch_mut(block_id)?)
}
fn unmap(&mut self, block_id: u64) {
self.pool.unpin(block_id);
}
}
#[cfg(all(feature = "std", feature = "mmap"))]
pub trait MmapPageSource {
fn map_read_zero_copy(
&self,
cap: &Capability,
block_id: u64,
) -> Result<&[u8; tpt_archon_core::page::PAGE_SIZE], CacheError>;
}
#[cfg(all(feature = "std", feature = "mmap"))]
pub struct MmapPageCache {
device: tpt_archon_core::block::MmapBlockDevice,
issuer: SharedIssuer,
}
#[cfg(all(feature = "std", feature = "mmap"))]
impl MmapPageCache {
pub fn new(device: tpt_archon_core::block::MmapBlockDevice, issuer: SharedIssuer) -> Self {
Self { device, issuer }
}
pub fn refresh<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), StorageError> {
self.device = tpt_archon_core::block::MmapBlockDevice::open(path)?;
Ok(())
}
}
#[cfg(all(feature = "std", feature = "mmap"))]
impl MmapPageSource for MmapPageCache {
fn map_read_zero_copy(
&self,
cap: &Capability,
block_id: u64,
) -> Result<&[u8; tpt_archon_core::page::PAGE_SIZE], CacheError> {
if !self
.issuer
.borrow()
.authorizes(cap, Resource::Page(block_id), Right::Read)
{
return Err(CacheError::Denied);
}
Ok(self.device.page_ref(block_id)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capability::CapabilityIssuer;
use alloc::rc::Rc;
use core::cell::RefCell;
use tpt_archon_core::block::InMemoryBlockDevice;
fn cache(blocks: u64, cap: usize, issuer: SharedIssuer) -> CorePageCache<InMemoryBlockDevice> {
CorePageCache::new(
BufferPool::new(InMemoryBlockDevice::new(blocks), cap),
issuer,
)
}
#[test]
fn write_then_read_is_zero_copy_visible() {
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let rw = issuer
.borrow_mut()
.mint(Resource::Page(2), Right::ReadWrite);
let mut c = cache(8, 4, issuer);
{
let page = c.map_write(&rw, 2).unwrap();
page.as_bytes_mut()[0] = 0xCC;
}
c.unmap(2);
let page = c.map_read(&rw, 2).unwrap();
assert_eq!(page.as_bytes()[0], 0xCC);
c.unmap(2);
}
#[test]
fn access_without_capability_is_denied() {
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let read_only = issuer.borrow_mut().mint(Resource::Page(0), Right::Read);
let mut c = cache(4, 2, issuer);
assert_eq!(c.map_write(&read_only, 0).err(), Some(CacheError::Denied));
assert_eq!(c.map_read(&read_only, 1).err(), Some(CacheError::Denied));
}
#[test]
fn revoked_capability_is_denied_at_map_read_and_map_write() {
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let rw = issuer
.borrow_mut()
.mint(Resource::Page(0), Right::ReadWrite);
let mut c = cache(4, 2, issuer.clone());
c.map_write(&rw, 0).unwrap();
c.unmap(0);
c.map_read(&rw, 0).unwrap();
c.unmap(0);
issuer.borrow_mut().revoke(&rw);
assert_eq!(c.map_read(&rw, 0).err(), Some(CacheError::Denied));
assert_eq!(c.map_write(&rw, 0).err(), Some(CacheError::Denied));
}
}
#[cfg(all(test, feature = "std", feature = "mmap"))]
mod mmap_tests {
use super::*;
use crate::capability::CapabilityIssuer;
use alloc::rc::Rc;
use core::cell::RefCell;
use tpt_archon_core::block::MmapBlockDevice;
use tpt_archon_core::page::PAGE_SIZE;
use tpt_archon_core::storage::Database;
fn temp_db(name: &str) -> std::path::PathBuf {
let mut p = std::env::temp_dir();
p.push(format!(
"tpt-archon-bridge-mmap-{}-{}.bin",
name,
std::process::id()
));
let _ = std::fs::remove_file(&p);
p
}
fn mmap_cache(path: &std::path::Path, issuer: SharedIssuer) -> MmapPageCache {
MmapPageCache::new(MmapBlockDevice::open(path).unwrap(), issuer)
}
#[test]
fn mmap_cache_write_then_read_is_zero_copy_visible() {
let path = temp_db("visible");
let mut db = Database::create(&path, 4).unwrap();
db.put(2, &[0xCCu8; PAGE_SIZE]).unwrap();
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let ro = issuer.borrow_mut().mint(Resource::Page(2), Right::Read);
let c = mmap_cache(&path, issuer);
assert_eq!(c.map_read_zero_copy(&ro, 2).unwrap()[0], 0xCC);
let _ = std::fs::remove_file(&path);
}
#[test]
fn mmap_cache_access_without_capability_is_denied() {
let path = temp_db("denied");
let _ = Database::create(&path, 4).unwrap();
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let read_only = issuer.borrow_mut().mint(Resource::Page(0), Right::Read);
let c = mmap_cache(&path, issuer);
assert_eq!(
c.map_read_zero_copy(&read_only, 1).err(),
Some(CacheError::Denied)
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn mmap_cache_revoked_capability_is_denied() {
let path = temp_db("revoked");
let _ = Database::create(&path, 4).unwrap();
let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
let ro = issuer.borrow_mut().mint(Resource::Page(0), Right::Read);
let c = mmap_cache(&path, issuer.clone());
c.map_read_zero_copy(&ro, 0).unwrap();
issuer.borrow_mut().revoke(&ro);
assert_eq!(c.map_read_zero_copy(&ro, 0).err(), Some(CacheError::Denied));
let _ = std::fs::remove_file(&path);
}
}