extern crate std; use crate::adapters::mock_fs::MockFileSystem; use crate::filesystem::FileHandle; use crate::protocol::*; use crate::server::{Host, StorageServer}; use server_framework::{Decode, Encode, MALFORMED, Server}; use std::boxed::Box; use std::vec; use std::vec::Vec; use types::bounded::CapSlot; /// Plain memory instead of regions; the buffer "capability" is a fixed /// slot, a grant's is its badge past `GRANT_SLOTS`. #[derive(Default)] struct TestHost { reclaimed: Vec, revoked: Vec, } const BUFFER: Option = Some(CapSlot::const_new(9)); const GRANT_SLOTS: usize = 32; fn badge_of(grant: CapSlot) -> usize { grant.value() as usize - GRANT_SLOTS } impl Host for TestHost { fn create(&mut self, _client: usize) -> Option<(&'static mut [u8], Option)> { Some((Box::leak(vec![0u8; 4096].into_boxed_slice()), BUFFER)) } fn reclaim(&mut self, buffer: CapSlot) { self.reclaimed.push(buffer); } fn mint(&mut self, badge: usize) -> Option { CapSlot::new(u8::try_from(GRANT_SLOTS + badge).ok()?) } fn revoke(&mut self, grant: CapSlot) { self.revoked.push(grant); } } type Srv = StorageServer; fn server() -> Srv { StorageServer::new(MockFileSystem::new(), TestHost::default()) } /// One request from `badge`, returning the decoded reply and any cap. fn call(s: &mut Srv, badge: usize, req: StorageRequest) -> (StorageResponse, Option) { let (words, cap) = s.handle_call(badge, req.encode(), None); ( StorageResponse::decode(words).expect("valid reply"), cap.map(|g| g.slot), ) } struct Client { badge: usize, } impl Client { fn attach(s: &mut Srv, badge: usize) -> Self { let (resp, cap) = call(s, badge, StorageRequest::Attach); assert_eq!(resp, StorageResponse::Attached { size: 4096 }); assert_eq!(cap, BUFFER, "the reply carries the buffer"); Self { badge } } /// The badge of a new grant to `path`, narrowed from this client's. fn grant(&self, s: &mut Srv, path: &[u8], write: bool) -> usize { self.put(s, path); let (words, cap) = s.handle_call( self.badge, StorageRequest::Grant { path_len: path.len(), write, } .encode(), None, ); assert_eq!( StorageResponse::decode(words), Some(StorageResponse::Granted) ); let cap = cap.expect("the reply carries the grant"); assert!(cap.keep_delegate, "a grant can be passed on"); badge_of(cap.slot) } /// Attaches through a new grant to `path`. fn narrowed(&self, s: &mut Srv, path: &[u8], write: bool) -> Client { let badge = self.grant(s, path, write); Client::attach(s, badge) } fn put(&self, s: &mut Srv, bytes: &[u8]) { s.client_buffer_for_test(self.badge).unwrap()[..bytes.len()].copy_from_slice(bytes); } fn get(&self, s: &mut Srv, len: usize) -> std::vec::Vec { s.client_buffer_for_test(self.badge).unwrap()[..len].to_vec() } fn with_path( &self, s: &mut Srv, path: &[u8], req: fn(usize) -> StorageRequest, ) -> StorageResponse { self.put(s, path); call(s, self.badge, req(path.len())).0 } fn create(&self, s: &mut Srv, path: &[u8]) -> FileHandle { match self.with_path(s, path, |path_len| StorageRequest::Create { path_len }) { StorageResponse::Handle(h) => h, other => panic!("create: {other:?}"), } } fn open(&self, s: &mut Srv, path: &[u8]) -> StorageResponse { self.with_path(s, path, |path_len| StorageRequest::Open { path_len }) } } #[test] fn nothing_works_before_attaching() { let mut s = server(); let (resp, _) = call(&mut s, 0, StorageRequest::Open { path_len: 3 }); assert_eq!(resp, StorageResponse::NotAttached); } #[test] fn attaching_again_hands_back_the_same_buffer() { let mut s = server(); let root = Client::attach(&mut s, 0); let (first, cap) = call(&mut s, 0, StorageRequest::Attach); assert_eq!(call(&mut s, 0, StorageRequest::Attach), (first, cap)); // Not a new client: the other seven places are still free. for _ in 1..8 { root.narrowed(&mut s, b"", true); } } #[test] fn data_goes_through_the_buffer_both_ways() { let mut s = server(); let c = Client::attach(&mut s, 0); let h = c.create(&mut s, b"note.txt"); c.put(&mut s, b"hello, storage"); assert_eq!( call(&mut s, 0, StorageRequest::Write { handle: h, len: 14 }).0, StorageResponse::Written { bytes: 14 } ); assert_eq!( call(&mut s, 0, StorageRequest::Close { handle: h }).0, StorageResponse::Ok ); c.put(&mut s, b"note.txt"); let StorageResponse::Handle(h) = call(&mut s, 0, StorageRequest::Open { path_len: 8 }).0 else { panic!("open"); }; assert_eq!( call(&mut s, 0, StorageRequest::FileSize { handle: h }).0, StorageResponse::Size(14) ); c.put(&mut s, &[0; 32]); assert_eq!( call(&mut s, 0, StorageRequest::Read { handle: h, len: 64 }).0, StorageResponse::Data { bytes: 14 } ); assert_eq!(c.get(&mut s, 14), b"hello, storage"); } #[test] fn a_handle_works_only_for_the_client_that_opened_it() { let mut s = server(); let root = Client::attach(&mut s, 0); let a = root.narrowed(&mut s, b"", true); let b = root.narrowed(&mut s, b"", true); let h = a.create(&mut s, b"mine"); for req in [ StorageRequest::Read { handle: h, len: 4 }, StorageRequest::Write { handle: h, len: 4 }, StorageRequest::FileSize { handle: h }, StorageRequest::Close { handle: h }, ] { assert_eq!( call(&mut s, b.badge, req).0, StorageResponse::Denied, "{req:?}" ); } assert_eq!( call(&mut s, a.badge, StorageRequest::FileSize { handle: h }).0, StorageResponse::Size(0) ); } #[test] fn hostile_lengths_are_clamped_to_the_buffer() { let mut s = server(); let c = Client::attach(&mut s, 0); let h = c.create(&mut s, b"x"); for len in [4097, usize::MAX] { assert!(matches!( call(&mut s, 0, StorageRequest::Write { handle: h, len }).0, StorageResponse::Written { .. } )); assert!(matches!( call(&mut s, 0, StorageRequest::Read { handle: h, len }).0, StorageResponse::Data { .. } )); assert_eq!( call(&mut s, 0, StorageRequest::Open { path_len: len }).0, StorageResponse::NotFound ); assert!(matches!( call(&mut s, 0, StorageRequest::ListDir { path_len: len }).0, StorageResponse::DirEntries { .. } )); } } #[test] fn handles_never_opened_are_refused() { let mut s = server(); Client::attach(&mut s, 0); for raw in [0, 15, 16, u16::MAX] { let h = FileHandle::new(raw); assert_eq!( call(&mut s, 0, StorageRequest::Read { handle: h, len: 1 }).0, StorageResponse::Denied ); } } #[test] fn a_read_only_disk_refuses_every_write_up_front() { let mut s = StorageServer::new(MockFileSystem::new().read_only(), TestHost::default()); let c = Client::attach(&mut s, 0); c.put(&mut s, b"f"); for req in [ StorageRequest::Create { path_len: 1 }, StorageRequest::Delete { path_len: 1 }, StorageRequest::Mkdir { path_len: 1 }, StorageRequest::Write { handle: FileHandle::new(0), len: 1, }, ] { let expect = match req { // A handle nobody opened is refused before the disk is asked. StorageRequest::Write { .. } => StorageResponse::Denied, _ => StorageResponse::ReadOnly, }; assert_eq!(call(&mut s, 0, req).0, expect, "{req:?}"); } } #[test] fn listings_are_records_in_the_buffer() { let mut s = server(); let c = Client::attach(&mut s, 0); for name in [b"a.txt".as_slice(), b"bee.bin"] { let h = c.create(&mut s, name); call(&mut s, 0, StorageRequest::Close { handle: h }); } c.put(&mut s, b"/"); assert_eq!( call(&mut s, 0, StorageRequest::ListDir { path_len: 1 }).0, StorageResponse::DirEntries { count: 2 } ); let records = c.get(&mut s, 2 * DIR_ENTRY_SIZE); assert_eq!(&records[..5], b"a.txt"); assert_eq!(records[5], 0); assert_eq!(&records[DIR_ENTRY_SIZE..DIR_ENTRY_SIZE + 7], b"bee.bin"); } #[test] fn garbage_gets_a_malformed_reply() { let mut s = server(); let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None); assert_eq!((words[0], cap), (MALFORMED, None)); } #[test] fn only_the_unbadged_holder_sets_the_log_and_only_once() { let mut s = server(); let log = CapSlot::new(7); let set = |s: &mut Srv, badge| { let (words, _) = s.handle_call(badge, StorageRequest::SetLog.encode(), log); StorageResponse::decode(words).unwrap() }; assert_eq!(set(&mut s, 5), StorageResponse::Error); assert_eq!(s.log(), None); assert_eq!(set(&mut s, 0), StorageResponse::Ok); assert_eq!(s.log(), log); assert_eq!(set(&mut s, 0), StorageResponse::Error); } #[test] fn a_badge_storage_never_issued_is_no_authority() { let mut s = server(); for req in [ StorageRequest::Attach, StorageRequest::Open { path_len: 1 }, StorageRequest::Release, ] { assert_eq!(call(&mut s, 5, req).0, StorageResponse::Denied, "{req:?}"); } } #[test] fn paths_resolve_inside_the_scope() { let mut s = server(); let root = Client::attach(&mut s, 0); let h = root.create(&mut s, b"docs/a.txt"); call(&mut s, 0, StorageRequest::Close { handle: h }); let h = root.create(&mut s, b"secret"); call(&mut s, 0, StorageRequest::Close { handle: h }); let docs = root.narrowed(&mut s, b"docs", false); assert!(matches!( docs.open(&mut s, b"a.txt"), StorageResponse::Handle(_) )); assert!( matches!(docs.open(&mut s, b"/a.txt"), StorageResponse::Handle(_)), "/ is the scope's root" ); assert_eq!( docs.open(&mut s, b"secret"), StorageResponse::NotFound, "only docs/secret, which is not there" ); for escape in [ b"../secret".as_slice(), b"./../secret", b"x/../../secret", b"..", ] { assert_eq!( docs.open(&mut s, escape), StorageResponse::Denied, "{escape:?}" ); } } #[test] fn a_read_only_scope_changes_nothing() { let mut s = server(); let root = Client::attach(&mut s, 0); let h = root.create(&mut s, b"f"); call(&mut s, 0, StorageRequest::Close { handle: h }); let ro = root.narrowed(&mut s, b"", false); for req in [ |path_len| StorageRequest::Create { path_len }, |path_len| StorageRequest::Delete { path_len }, |path_len| StorageRequest::Mkdir { path_len }, ] { assert_eq!(ro.with_path(&mut s, b"f", req), StorageResponse::Denied); } // It can open the file, but the handle cannot write. let StorageResponse::Handle(h) = ro.open(&mut s, b"f") else { panic!("open") }; assert_eq!( call( &mut s, ro.badge, StorageRequest::Write { handle: h, len: 1 } ) .0, StorageResponse::Denied ); assert!(matches!( call(&mut s, ro.badge, StorageRequest::Read { handle: h, len: 1 }).0, StorageResponse::Data { .. } )); } #[test] fn a_grant_is_never_wider_than_its_holder() { let mut s = server(); let root = Client::attach(&mut s, 0); let docs = root.narrowed(&mut s, b"docs", false); // Asking for write from a read-only scope yields read-only. let sub = docs.narrowed(&mut s, b"sub", true); assert_eq!( sub.with_path(&mut s, b"x", |path_len| StorageRequest::Create { path_len }), StorageResponse::Denied ); docs.put(&mut s, b"../"); assert_eq!( call( &mut s, docs.badge, StorageRequest::Grant { path_len: 3, write: false } ) .0, StorageResponse::Denied ); } #[test] fn releasing_takes_back_the_grant_and_everything_from_it() { let mut s = server(); let root = Client::attach(&mut s, 0); let f = root.create(&mut s, b"f"); call(&mut s, 0, StorageRequest::Close { handle: f }); let shell = root.narrowed(&mut s, b"", true); let program = shell.narrowed(&mut s, b"", false); let grandchild = program.grant(&mut s, b"", false); let sibling = root.narrowed(&mut s, b"", true); let StorageResponse::Handle(h) = program.open(&mut s, b"f") else { panic!("open") }; program.put(&mut s, b"leftover"); assert_eq!( call(&mut s, program.badge, StorageRequest::Release).0, StorageResponse::Ok ); for badge in [program.badge, grandchild] { assert_eq!( call(&mut s, badge, StorageRequest::Attach).0, StorageResponse::Denied ); } assert_eq!( call( &mut s, program.badge, StorageRequest::FileSize { handle: h } ) .0, StorageResponse::Denied ); // The kernel takes the capabilities back, and the buffer is unmapped. let revoked: Vec = s.host().revoked.iter().map(|c| badge_of(*c)).collect(); assert_eq!(revoked, [program.badge, grandchild]); assert_eq!(s.host().reclaimed, [BUFFER.unwrap()]); // The rest are untouched, and the freed buffer is reused, wiped. assert!(matches!( shell.open(&mut s, b"f"), StorageResponse::Handle(_) )); assert!(matches!( sibling.open(&mut s, b"f"), StorageResponse::Handle(_) )); let next = shell.narrowed(&mut s, b"", false); assert_eq!(next.get(&mut s, 8), [0; 8]); } #[test] fn the_unbadged_holder_cannot_be_released() { let mut s = server(); Client::attach(&mut s, 0); assert_eq!( call(&mut s, 0, StorageRequest::Release).0, StorageResponse::Error ); } #[test] fn only_init_unlocks_and_only_once() { let mut s = server(); let set = |s: &mut Srv, badge, req| call(s, badge, req).0; let key = |half| StorageRequest::SetKey { half, words: [1, 2] }; let seed = |half| StorageRequest::SetSeed { half, words: [3, 4] }; let user = StorageRequest::SetUser { id: 7, guest: false }; assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "not before saying whose"); assert_eq!(set(&mut s, 5, user), StorageResponse::Error, "a client cannot say"); assert_eq!(set(&mut s, 0, user), StorageResponse::Ok); assert_eq!(set(&mut s, 0, user), StorageResponse::Error, "once"); assert_eq!(set(&mut s, 5, key(0)), StorageResponse::Error, "a client cannot"); assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Ok); assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "nor can a half be replaced"); assert_eq!(set(&mut s, 0, key(2)), StorageResponse::Error, "two halves only"); assert_eq!(set(&mut s, 0, key(1)), StorageResponse::Ok); assert_eq!(set(&mut s, 0, seed(0)), StorageResponse::Ok); assert!(!s.unlocked(), "not without the whole seed"); assert_eq!(set(&mut s, 0, seed(1)), StorageResponse::Ok); assert!(s.unlocked()); assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "once unlocked, never again"); } #[test] fn locking_takes_every_grant_back_and_lets_the_next_user_in() { let mut s = server(); let set = |s: &mut Srv, badge, req| call(s, badge, req).0; let init = Client::attach(&mut s, 0); let shell = init.narrowed(&mut s, b"", true); let app = shell.narrowed(&mut s, b"", false); for req in [StorageRequest::SetUser { id: 1, guest: false }, StorageRequest::SetKey { half: 0, words: [1, 2] }, StorageRequest::SetKey { half: 1, words: [1, 2] }, StorageRequest::SetSeed { half: 0, words: [1, 2] }, StorageRequest::SetSeed { half: 1, words: [1, 2] }] { assert_eq!(set(&mut s, 0, req), StorageResponse::Ok); } assert!(s.unlocked()); assert_eq!(set(&mut s, shell.badge, StorageRequest::Lock), StorageResponse::Error, "only init signs out"); assert_eq!(set(&mut s, 0, StorageRequest::Lock), StorageResponse::Ok); assert!(!s.unlocked()); let revoked: Vec = s.host().revoked.iter().map(|c| badge_of(*c)).collect(); assert_eq!(revoked, [shell.badge, app.badge]); assert_eq!(set(&mut s, shell.badge, StorageRequest::FileSize { handle: crate::filesystem::FileHandle::new(0) }), StorageResponse::Denied); assert_eq!(set(&mut s, 0, StorageRequest::SetUser { id: 2, guest: true }), StorageResponse::Ok, "someone else may sign in"); } #[test] fn only_init_purges_only_users_and_only_while_locked() { let mut s = server(); let init = Client::attach(&mut s, 0); let shell = init.narrowed(&mut s, b"", true); let purge = |s: &mut Srv, badge: usize, path: &[u8]| { init.put(s, path); call(s, badge, StorageRequest::Purge { path_len: path.len() }).0 }; assert_eq!(purge(&mut s, shell.badge, b"users/x"), StorageResponse::Error); assert_eq!(purge(&mut s, 0, b"bin"), StorageResponse::Denied); assert_eq!(purge(&mut s, 0, b"users/../bin"), StorageResponse::Denied); assert_eq!(purge(&mut s, 0, b"users/"), StorageResponse::Denied); assert_eq!(purge(&mut s, 0, b"users/x"), StorageResponse::Ok); assert_eq!(purge(&mut s, 0, b"users"), StorageResponse::Ok); }