diff --git a/services/pddb/src/api.rs b/services/pddb/src/api.rs index 5b9aaa9b8..d04ebcd36 100644 --- a/services/pddb/src/api.rs +++ b/services/pddb/src/api.rs @@ -1,9 +1,9 @@ mod rkyv_enum; -pub use rkyv_enum::*; +use core::ops::{Deref, DerefMut}; +use std::num::NonZeroU32; use bitfield::bitfield; -use std::num::NonZeroU32; -use core::ops::{Deref, DerefMut}; +pub use rkyv_enum::*; // on the "[allow(dead_code)]" directives: these constants are used to define the PDDB, and are // sometimes used by both `bin` (main.rs) and `lib` (lib.rs) views, but also, sometimes used @@ -15,8 +15,8 @@ use core::ops::{Deref, DerefMut}; // here, because sometimes, clippy just can't see the big picture. // note this name cannot be changed because it is baked into `libstd` -pub(crate) const SERVER_NAME_PDDB: &str = "_Plausibly Deniable Database_"; -pub(crate) const SERVER_NAME_PDDB_POLLER: &str = "_PDDB Mount Poller_"; +pub(crate) const SERVER_NAME_PDDB: &str = "_Plausibly Deniable Database_"; +pub(crate) const SERVER_NAME_PDDB_POLLER: &str = "_PDDB Mount Poller_"; /// This is the registered name for a dedicated private API channel to the PDDB for doing the time reset /// Even though nobody but the PDDB should connect to this, we have to share it publicly so the PDDB can /// depend upon this constant. @@ -43,10 +43,10 @@ pub(crate) const PDDB_VERSION: u32 = 0x00_00_02_01; #[allow(dead_code)] // PDDB_A_LEN may be shorter than xous::PDDB_LEN, to speed up testing. #[allow(dead_code)] -#[cfg(not(any(feature="pddbtest",feature="autobasis",feature="ci",feature="smalldb")))] +#[cfg(not(any(feature = "pddbtest", feature = "autobasis", feature = "ci", feature = "smalldb")))] pub(crate) const PDDB_A_LEN: usize = xous::PDDB_LEN as usize; #[allow(dead_code)] -#[cfg(any(feature="pddbtest",feature="autobasis",feature="ci",feature="smalldb"))] +#[cfg(any(feature = "pddbtest", feature = "autobasis", feature = "ci", feature = "smalldb"))] pub const PDDB_A_LEN: usize = 4 * 1024 * 1024; /// range for the starting point of a journal number, picked from a random seed @@ -108,7 +108,7 @@ pub(crate) const PDDB_FAST_SPACE_SYSTEM_BASIS: &'static str = ".FastSpace"; #[allow(dead_code)] // TODO: add hardware acceleration for BCRYPT so we can hit the OWASP target without excessive UX delay -pub(crate) const BCRYPT_COST: u32 = 7; // 10 is the minimum recommended by OWASP; takes 5696 ms to verify @ 10 rounds; 804 ms to verify 7 rounds +pub(crate) const BCRYPT_COST: u32 = 7; // 10 is the minimum recommended by OWASP; takes 5696 ms to verify @ 10 rounds; 804 ms to verify 7 rounds #[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)] pub(crate) enum Opcode { @@ -160,7 +160,7 @@ pub(crate) enum Opcode { /// Write debug dump (only available in hosted mode) #[cfg(not(target_os = "xous"))] DangerousDebug = 25, - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] BasisTesting = 26, ListBasisStd = 26, @@ -262,9 +262,11 @@ pub type ApiToken = [u32; 3]; #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] #[repr(C)] pub struct PddbBasisList { - /// the first 63 that fit in the list -- generally we anticipate not more than a few being open at a time, so this should be enough. - pub list: [xous_ipc::String::; 63], - /// total number of basis open. Should be <= 63, but we allow it to be larger to indicate cases where this structure wasn't big enough. + /// the first 63 that fit in the list -- generally we anticipate not more than a few being open at a + /// time, so this should be enough. + pub list: [xous_ipc::String; 63], + /// total number of basis open. Should be <= 63, but we allow it to be larger to indicate cases where + /// this structure wasn't big enough. pub num: u32, } @@ -296,16 +298,16 @@ impl BasisRetentionPolicy { } #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct PddbBasisRequest { - pub name: xous_ipc::String::, + pub name: xous_ipc::String, pub code: PddbRequestCode, pub policy: Option, } #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct PddbDictRequest { pub basis_specified: bool, - pub basis: xous_ipc::String::, - pub dict: xous_ipc::String::, - pub key: xous_ipc::String::, + pub basis: xous_ipc::String, + pub dict: xous_ipc::String, + pub key: xous_ipc::String, pub index: u32, pub token: [u32; 4], pub code: PddbRequestCode, @@ -321,13 +323,14 @@ pub struct PddbDictRequest { #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct PddbKeyRequest { pub basis_specified: bool, - pub basis: xous_ipc::String::, - pub dict: xous_ipc::String::, - pub key: xous_ipc::String::, + pub basis: xous_ipc::String, + pub dict: xous_ipc::String, + pub key: xous_ipc::String, pub token: Option, pub create_dict: bool, pub create_key: bool, - pub alloc_hint: Option, // this is a usize but for IPC we must have defined memory sizes, so we pick the big option. + pub alloc_hint: Option, /* this is a usize but for IPC we must have defined memory sizes, so we + * pick the big option. */ pub cb_sid: Option<[u32; 4]>, pub result: PddbRequestCode, } @@ -335,7 +338,7 @@ pub struct PddbKeyRequest { pub(crate) const MAX_PDDBKLISTLEN: usize = 4064; /// A structure for requesting a token to access a particular key/value pair #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub (crate) struct PddbKeyList { +pub(crate) struct PddbKeyList { pub token: [u32; 4], pub data: [u8; MAX_PDDBKLISTLEN], pub retcode: PddbRetcode, @@ -345,17 +348,26 @@ pub (crate) struct PddbKeyList { /// A structure for bulk deletion of keys pub(crate) const MAX_PDDB_DELETE_LEN: usize = 3800; // approximate limit, might be a little higher if we cared to calculate it out #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub (crate) struct PddbDeleteList { +pub(crate) struct PddbDeleteList { pub basis_specified: bool, - pub basis: xous_ipc::String::, - pub dict: xous_ipc::String::, + pub basis: xous_ipc::String, + pub dict: xous_ipc::String, pub data: [u8; MAX_PDDB_DELETE_LEN], pub retcode: PddbRetcode, } /// Return codes for Read/Write API calls to the main server #[repr(u8)] -#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Eq, PartialEq)] +#[derive( + num_derive::FromPrimitive, + num_derive::ToPrimitive, + Debug, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, + Eq, + PartialEq, +)] pub(crate) enum PddbRetcode { Uninit = 0, Ok = 1, @@ -380,7 +392,8 @@ pub(crate) struct PddbBuf { reserved: u8, /// length of the data field pub(crate) len: u16, - /// point in the key stream. 64-bit for future-compatibility; but, can't be larger than 32 bits on a 32-bit target. + /// point in the key stream. 64-bit for future-compatibility; but, can't be larger than 32 bits on a + /// 32-bit target. pub(crate) position: u64, pub(crate) data: [u8; PDDB_BUF_DATA_LEN], } @@ -401,12 +414,19 @@ pub struct PddbKeyRecord { /// and the record must be explicitly re-read with a non-bulk call to fetch its data, /// or the key has zero length. Check the `len` field to differentiate the two. /// (Turns out that `rkyv` crashes when deserializing a zero-length Vec) - pub data: Option>, + pub data: Option>, } /// Return codes for Read/Write API calls to the main server #[repr(u32)] -#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + num_derive::FromPrimitive, + num_derive::ToPrimitive, + Debug, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] pub enum PddbBulkReadCode { /// Uninitialized value Uninit = 0, @@ -433,18 +453,23 @@ pub struct BulkReadHeader { } impl Deref for BulkReadHeader { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const BulkReadHeader as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const BulkReadHeader as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } impl DerefMut for BulkReadHeader { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut BulkReadHeader as *mut u8, core::mem::size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut BulkReadHeader as *mut u8, + core::mem::size_of::(), + ) as &mut [u8] } } } @@ -452,13 +477,15 @@ impl DerefMut for BulkReadHeader { #[allow(dead_code)] /// Ensure that the `PddbBuf` struct is exactly one page big const fn _assert_pddbbuf_is_4096_bytes() { - unsafe { core::mem::transmute::<_, PddbBuf>([0u8; 4096]); } + unsafe { + core::mem::transmute::<_, PddbBuf>([0u8; 4096]); + } } impl PddbBuf { pub(crate) fn from_slice_mut(slice: &mut [u8]) -> &mut PddbBuf { // this transforms the slice [u8] into a PddbBuf ref. - unsafe {core::mem::transmute::<*mut u8, &mut PddbBuf>(slice.as_mut_ptr()) } + unsafe { core::mem::transmute::<*mut u8, &mut PddbBuf>(slice.as_mut_ptr()) } } } @@ -497,8 +524,8 @@ pub struct PddbKeyAttrIpc { pub len: u64, pub reserved: u64, pub age: u64, - pub dict: xous_ipc::String::, - pub basis: xous_ipc::String::, + pub dict: xous_ipc::String, + pub basis: xous_ipc::String, pub flags: u32, pub index: u32, pub token: ApiToken, @@ -518,6 +545,7 @@ impl PddbKeyAttrIpc { code: PddbRequestCode::Uninit, } } + #[allow(dead_code)] pub fn to_attributes(&self) -> KeyAttributes { KeyAttributes { @@ -530,6 +558,7 @@ impl PddbKeyAttrIpc { index: NonZeroU32::new(self.index).unwrap(), } } + pub fn from_attributes(attr: KeyAttributes, token: ApiToken) -> PddbKeyAttrIpc { PddbKeyAttrIpc { len: attr.len as u64, @@ -550,7 +579,7 @@ impl PddbKeyAttrIpc { #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct PddbDangerousDebug { pub request: DebugRequest, - pub dump_name: xous_ipc::String::<128>, + pub dump_name: xous_ipc::String<128>, } #[cfg(not(target_os = "xous"))] #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] @@ -570,6 +599,9 @@ mod tests { } #[test] fn test_pddb_len() { - assert!(PDDB_A_LEN <= xous::PDDB_LEN as usize, "PDDB_A_LEN is larger than the maximum extents available in the hardware"); + assert!( + PDDB_A_LEN <= xous::PDDB_LEN as usize, + "PDDB_A_LEN is larger than the maximum extents available in the hardware" + ); } } diff --git a/services/pddb/src/api/rkyv_enum.rs b/services/pddb/src/api/rkyv_enum.rs index 75fd56f53..edd15e92b 100644 --- a/services/pddb/src/api/rkyv_enum.rs +++ b/services/pddb/src/api/rkyv_enum.rs @@ -12,8 +12,7 @@ // As of now, the current version is 0.7.x and there isn't a timeline yet for 0.8. #![allow(dead_code)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub enum BasisRetentionPolicy { Persist, ClearAfterSleeps(u32), @@ -51,11 +50,10 @@ pub enum PddbRekeyOp { /// at this current time). ChangePass(xous_ipc::String), */ - /// Return codes Success, AuthFail, UserAbort, VerifyFail, InternalError, -} \ No newline at end of file +} diff --git a/services/pddb/src/backend.rs b/services/pddb/src/backend.rs index eb34ad978..1e7a482dc 100644 --- a/services/pddb/src/backend.rs +++ b/services/pddb/src/backend.rs @@ -27,5 +27,5 @@ mod hosted; #[cfg(not(target_os = "xous"))] pub(crate) use hosted::*; -#[cfg(feature="migration1")] +#[cfg(feature = "migration1")] mod migration1to2; diff --git a/services/pddb/src/backend/basis.rs b/services/pddb/src/backend/basis.rs index bc5377ad4..0625caeff 100644 --- a/services/pddb/src/backend/basis.rs +++ b/services/pddb/src/backend/basis.rs @@ -1,43 +1,60 @@ +use core::mem::size_of; +use core::num::NonZeroU32; +use core::ops::{Deref, DerefMut}; +use std::cmp::Ordering; +use std::cmp::Reverse; +use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet}; +use std::convert::TryInto; +use std::io::{Error, ErrorKind, Result}; +use std::iter::IntoIterator; + +use aes::cipher::generic_array::GenericArray; +use aes::Aes256; +use aes_gcm_siv::{aead::KeyInit, Aes256GcmSiv}; + +use super::*; /// # The Organization of Basis Data /// /// ## Overview /// In basis space, the BasisRoot is located at VPAGE #1 (VPAGE #0 is always invalid). -/// A VPAGE is 0xFE0 (4,064) bytes long, which is equal to a PAGE of 4k minus 32 bytes of encryption+journal -/// overhead. +/// A VPAGE is 0xFE0 (4,064) bytes long, which is equal to a PAGE of 4k minus 32 bytes of +/// encryption+journal overhead. /// /// *NB: The factors of 4,064 are: 1, 2, 4, 8, 16, 32, 127, 254, 508, 1016, 2032, 4064* /// /// AAD associated with the BasisRoot consist of a bytewise concatenation of: /// - Basis name /// - version number (should match version inside; complicates downgrade attacks) -/// - FPGA's silicon DNA number (makes a naive raw-copy of the data to another device unusable; -/// but of course, the DNA ID can be forged minor efforts) +/// - FPGA's silicon DNA number (makes a naive raw-copy of the data to another device unusable; but of +/// course, the DNA ID can be forged minor efforts) /// /// Usage Assumptions: /// - Most mutability happens on the data keys themselves (keys are read/write/modify routinely). /// - Dictionary modifications (key addition or removal) are about 20x less frequent than key mods. -/// - Basis modifications (creation/removal of dictionaries) is about 10x less frequent than dictionary mods. -/// - According to https://www.pdl.cmu.edu/PDL-FTP/HECStorage/Yifan_Final.pdf, 0.01% of files (1 in 10,000) -/// require a name over 100 bytes long; 0.1% require longer than 64 bytes. There longest filename identified -/// was 143 bytes long. Study surveys ~14M files on the LANL network. +/// - Basis modifications (creation/removal of dictionaries) is about 10x less frequent than dictionary +/// mods. +/// - According to https://www.pdl.cmu.edu/PDL-FTP/HECStorage/Yifan_Final.pdf, 0.01% of files (1 in +/// 10,000) require a name over 100 bytes long; 0.1% require longer than 64 bytes. There longest +/// filename identified was 143 bytes long. Study surveys ~14M files on the LANL network. /// - Same study says 99.9% of directories have under 1k files, 99.999% under 10k /// /// The root basis structure takes up the first valid VPAGE in the virtual memory space. /// It contains a count of the number of valid dictionaries in the Basis. Dictionaries are found at -/// fixed offsets starting at 0xFE_0000 and repeating every 0xFE_0000 intervals, with up to 16383 dictionaries -/// allowed. A naive linear search is used to scan for dictionaries, starting at the lowest address, -/// scanning every 0xFE_0000, until the number of valid dictionaries have been found that matches the valid -/// dictionary count prescribed in the Basis root. A dictionary can be effectively deleted by just marking its -/// descriptor as invalid. +/// fixed offsets starting at 0xFE_0000 and repeating every 0xFE_0000 intervals, with up to 16383 +/// dictionaries allowed. A naive linear search is used to scan for dictionaries, starting at the lowest +/// address, scanning every 0xFE_0000, until the number of valid dictionaries have been found that +/// matches the valid dictionary count prescribed in the Basis root. A dictionary can be effectively +/// deleted by just marking its descriptor as invalid. /// /// A stride of 0xFE_0000 means that dictionary descriptors can be up to 4096 VPAGEs long. A dictionary /// descriptor consists of a `DictDescriptor` header, some bookkeeping data, plus a count of the number -/// of keys in the dictionary. Following the header is a list of key descriptors. Similar to the DictDescriptors, -/// the key descriptors are stored at a stride of 127 (or 32 per VPAGE); they can be deleted by being marked -/// as invalid, and a linear scan is used to identify all the entries. A KeyDescriptor contains the name -/// of the key, flags, its age, and pointers to the key data in virtual memory space + its length. -/// This leads to a name length restriction of roughly 115 characters for keys and dictionaries, which is -/// about half of what most filesystems allow, but accommodates roughly 99.99% of the use cases. +/// of keys in the dictionary. Following the header is a list of key descriptors. Similar to the +/// DictDescriptors, the key descriptors are stored at a stride of 127 (or 32 per VPAGE); they can be +/// deleted by being marked as invalid, and a linear scan is used to identify all the entries. A +/// KeyDescriptor contains the name of the key, flags, its age, and pointers to the key data in virtual +/// memory space + its length. This leads to a name length restriction of roughly 115 characters for keys +/// and dictionaries, which is about half of what most filesystems allow, but accommodates roughly 99.99% +/// of the use cases. /// /// Thus adding a new dictionary always consumes at least one 4k page, but you can have up to 15 keys /// in that dictionary with no extra bookkeeping cost once the dictionary is added. @@ -83,8 +100,8 @@ /// Large keys are the simplest - each key starts at a VPAGE-aligned address, and allocates /// up from there. Any unused amount is wasted, but with a ~32k threshold you'll have no worse /// than 12.5% unused space, probably closer to ~7%-ish if all your data hovered around the threshold. -/// The allocation is a simple pointer that just keeps going up. De-allocated space is never defragmented, -/// and we just rely on the space being "huge" to save us. +/// The allocation is a simple pointer that just keeps going up. De-allocated space is never +/// defragmented, and we just rely on the space being "huge" to save us. /// /// Small keys are kept in VPAGE-sized pools of data, and compacted together in RAM. The initial, naive /// implementation simply keeps all small keys in a HashMap in RAM, and when it comes time to sync them @@ -94,33 +111,35 @@ /// /// ## The Alignment and Serialization Chronicles /// -/// We're using Repr(C) and alignment to 64-bits to create a consistent "FFI" layout; we use an unsafe cast -/// to [u8] as our method to serialize the structure, which means we could be subject to breakage if the Rust -/// compiler decides to change its Repr(C) FFI (it's not guaranteed, but I think at this point in the lifecycle -/// with simple primitive types it's hard to see it changing). This puts some requirements on the ordering of -/// fields in the struct below. Note that the serialization is all double-checked by the pddbdbg.py script. +/// We're using Repr(C) and alignment to 64-bits to create a consistent "FFI" layout; we use an unsafe +/// cast to [u8] as our method to serialize the structure, which means we could be subject to breakage if +/// the Rust compiler decides to change its Repr(C) FFI (it's not guaranteed, but I think at this point +/// in the lifecycle with simple primitive types it's hard to see it changing). This puts some +/// requirements on the ordering of fields in the struct below. Note that the serialization is all +/// double-checked by the pddbdbg.py script. /// -/// In coming to the choice to use Repr(C), I experimented with rkyv and bincode. bincode relies on the serde -/// crate, which, as of Nov 2021, has troubles taking in const generics, and thus barfs on our fixed-sized -/// string allocations that are longer than 32 bytes. Version 2.0 of bincode /might/ do this better, but as -/// of the design of this crate, it's in "alpha" with no official release to crates.io, so we're avoiding it; -/// but for sure 1.3.3 of bincode (latest stable as of the design) cannot do the job, and there's a few other -/// users reporting the issue so I'm pretty sure it's not "user error" on my part. +/// In coming to the choice to use Repr(C), I experimented with rkyv and bincode. bincode relies on the +/// serde crate, which, as of Nov 2021, has troubles taking in const generics, and thus barfs on our +/// fixed-sized string allocations that are longer than 32 bytes. Version 2.0 of bincode /might/ do this +/// better, but as of the design of this crate, it's in "alpha" with no official release to crates.io, so +/// we're avoiding it; but for sure 1.3.3 of bincode (latest stable as of the design) cannot do the job, +/// and there's a few other users reporting the issue so I'm pretty sure it's not "user error" on my +/// part. /// -/// rkyv handles const generics well, and it perhaps very reasonably shuffles around the order of structures -/// in the struct to improve the packing efficiency. However, this has the property that rkyv ser will never break -/// rkyv deser, but unfortunately you can't interoperate with anything that isn't rkyv (e.g., describing the data -/// layout to someone who wants to do a C implementation). There's also a risk that if we are forced to -/// upgrade rkyv later on we might break compatibility with what's stored on disk, although I'm pretty sure the -/// maintainer of rkyv tries to avoid that as much as possible. +/// rkyv handles const generics well, and it perhaps very reasonably shuffles around the order of +/// structures in the struct to improve the packing efficiency. However, this has the property that rkyv +/// ser will never break rkyv deser, but unfortunately you can't interoperate with anything that isn't +/// rkyv (e.g., describing the data layout to someone who wants to do a C implementation). There's also a +/// risk that if we are forced to upgrade rkyv later on we might break compatibility with what's stored +/// on disk, although I'm pretty sure the maintainer of rkyv tries to avoid that as much as possible. /// /// Repr(C), while also not guaranteed to be stable, has pressure from the CFFI users at least to keep -/// things as stable as possible, and it is by definition inter-operable with C. Repr(C) is native to Rust, -/// with no additional dependencies to pull in, which helps reduce the code base size overall. -/// So, we're using a repr(C) with an align(8), and then carefully checking our structure organization and -/// elements to keep things "in spec" with what C can natively understand, in an effort to create a disk -/// storage structure that can persist through future versions of Rust and also other implementations in other -/// languages. +/// things as stable as possible, and it is by definition inter-operable with C. Repr(C) is native to +/// Rust, with no additional dependencies to pull in, which helps reduce the code base size overall. +/// So, we're using a repr(C) with an align(8), and then carefully checking our structure organization +/// and elements to keep things "in spec" with what C can natively understand, in an effort to create a +/// disk storage structure that can persist through future versions of Rust and also other +/// implementations in other languages. /// /// ## Known Repr(C) footguns: /// - When you start laying in 64-bit types, stuff has to be 64-bit aligned, or else you'll start to get @@ -129,25 +148,7 @@ /// wrapped, we're using a NonZeroU64 format. The compiler knows how to turn that into a 64-bit C-friendly /// structure and serialize/deserialize that into the correct Rust structure. See /// https://doc.rust-lang.org/nomicon/other-reprs.html for a citation on that. - use crate::api::*; -use super::*; - -use core::ops::{Deref, DerefMut}; -use core::mem::size_of; -use std::convert::TryInto; -use aes_gcm_siv::{ - aead::KeyInit, - Aes256GcmSiv, -}; -use aes::Aes256; -use aes::cipher::generic_array::GenericArray; -use std::iter::IntoIterator; -use std::collections::{BinaryHeap, HashMap, HashSet, BTreeSet}; -use std::io::{Result, Error, ErrorKind}; -use std::cmp::Reverse; -use std::cmp::Ordering; -use core::num::NonZeroU32; pub(crate) const SMALL_POOL_START: u64 = 0x0000_003F_8000_0000; pub(crate) const SMALL_POOL_END: u64 = 0x0000_007E_FF02_0000; @@ -213,12 +214,13 @@ pub(crate) struct BasisRoot { pub(crate) age: u32, /// number of dictionaries. pub(crate) num_dictionaries: u32, - /* at this point, we are aligned to a 64-bit boundary. All data must stay aligned to this boundary from here out! */ + /* at this point, we are aligned to a 64-bit boundary. All data must stay aligned to this boundary + * from here out! */ /// 64-byte name; aligns to 64-bits pub(crate) name: BasisRootName, } impl BasisRoot { - pub(crate) fn aad(&self, dna: u64) -> Vec:: { + pub(crate) fn aad(&self, dna: u64) -> Vec { let mut aad = Vec::::new(); aad.extend_from_slice(&self.name.data[..self.name.len as usize]); aad.extend_from_slice(&PDDB_VERSION.to_le_bytes()); @@ -228,30 +230,37 @@ impl BasisRoot { } impl Deref for BasisRoot { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const BasisRoot as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const BasisRoot as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } impl DerefMut for BasisRoot { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut BasisRoot as *mut u8, core::mem::size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut BasisRoot as *mut u8, + core::mem::size_of::(), + ) as &mut [u8] } } } -/// A list of open Basis that we can use to search and operate upon. Sort of the "root" data structure of the PDDB. +/// A list of open Basis that we can use to search and operate upon. Sort of the "root" data structure of the +/// PDDB. /// -/// Note to self: it's tempting to integrate the "hw" parameter (the pointer to the PddbOs structure). However, this -/// results in interior mutability problems. I guess we could wrap it in a Rc or RefCell or something like that; but -/// the inconvenience of passing the hw structure around doesn't seem too bad so far... +/// Note to self: it's tempting to integrate the "hw" parameter (the pointer to the PddbOs structure). +/// However, this results in interior mutability problems. I guess we could wrap it in a Rc or RefCell or +/// something like that; but the inconvenience of passing the hw structure around doesn't seem too bad so +/// far... pub(crate) struct BasisCache { /// the cache entries themselves - cache: Vec::, + cache: Vec, /// ticktimer reference, for managing atimes pub(crate) tt: ticktimer_server::Ticktimer, /// data cache - stores the most recently decrypted pages of data @@ -265,21 +274,24 @@ impl BasisCache { data_cache: PlaintextCache { data: None, tag: None }, } } + /// Returns a Vec which is a list of Bases to visit, in order of visitation, to create the union view. - pub(crate) fn access_list(&self) -> Vec:: { + pub(crate) fn access_list(&self) -> Vec { let mut al = Vec::::new(); for entry in self.cache.iter().rev() { al.push(entry.name.to_string()); } al } + pub(crate) fn rekey(&self, hw: &mut PddbOs, op: PddbRekeyOp) -> PddbRekeyOp { hw.pddb_rekey(op, &self.cache) } + fn select_basis(&self, basis_name: Option<&str>) -> Option { if self.cache.len() == 0 { log::error!("Can't select basis: PDDB is not mounted"); - return None + return None; } if let Some(n) = basis_name { self.cache.iter().position(|bc| bc.name == n) @@ -287,7 +299,8 @@ impl BasisCache { Some(self.cache.len() - 1) } } - pub(crate) fn basis_count(&self) -> usize {self.cache.len()} + + pub(crate) fn basis_count(&self) -> usize { self.cache.len() } /// Adds a dictionary with `name` to: /// - if `basis_name` is None, the most recently opened basis @@ -329,7 +342,7 @@ impl BasisCache { let mut init_flags = DictFlags(0); init_flags.set_valid(true); let mut free_keys = BinaryHeap::>::new(); - free_keys.push(Reverse(FreeKeyRange{start: 1, run: KEY_MAXCOUNT as u32 - 1})); + free_keys.push(Reverse(FreeKeyRange { start: 1, run: KEY_MAXCOUNT as u32 - 1 })); let dict_cache = DictCacheEntry { index: NonZeroU32::new(dict_index).unwrap(), keys: HashMap::::new(), @@ -363,7 +376,7 @@ impl BasisCache { /// Returns a list of all the known dictionaries, across all the basis. A HashSet is returned /// because you can have the same-named dictionary in multiple basis, and what we're asking for /// is the union of all the dictionary names, without duplicates. - pub(crate) fn dict_list(&mut self, hw: &mut PddbOs, basis_name: Option<&str>) -> HashSet:: { + pub(crate) fn dict_list(&mut self, hw: &mut PddbOs, basis_name: Option<&str>) -> HashSet { let mut dict_set = HashSet::::new(); if basis_name.is_some() { if let Some(basis_index) = self.select_basis(basis_name) { @@ -387,7 +400,13 @@ impl BasisCache { } dict_set } - pub(crate) fn key_list(&mut self, hw: &mut PddbOs, dict: &str, basis_name: Option<&str>) -> Result<(BTreeSet::, u32, u32)> { + + pub(crate) fn key_list( + &mut self, + hw: &mut PddbOs, + dict: &str, + basis_name: Option<&str>, + ) -> Result<(BTreeSet, u32, u32)> { let mut merge_list = BTreeSet::::new(); let mut found_dict = false; let mut key_count: u32 = 0; @@ -417,7 +436,7 @@ impl BasisCache { if found_dict { Ok((merge_list, key_count, found_key_count)) } else { - return Err(Error::new(ErrorKind::NotFound, "dictionary not found")) + return Err(Error::new(ErrorKind::NotFound, "dictionary not found")); } } @@ -425,8 +444,12 @@ impl BasisCache { /// Perhaps there also needs to be a `dict_remove_all` call which iterates through every basis /// makes sure the dictionary is removed from all the possible known basis. Anyways, that function /// would be a variant of this targeted version. - pub(crate) fn dict_remove(&mut self, - hw: &mut PddbOs, dict: &str, basis_name: Option<&str>, paranoid: bool + pub(crate) fn dict_remove( + &mut self, + hw: &mut PddbOs, + dict: &str, + basis_name: Option<&str>, + paranoid: bool, ) -> Result<()> { if let Some(basis_index) = self.select_basis(basis_name) { log::debug!("deleting dict {}", dict); @@ -443,8 +466,14 @@ impl BasisCache { } } - pub(crate) fn key_read(&mut self, hw: &mut PddbOs, dict: &str, key: &str, data: &mut [u8], - offset: Option, basis_name:Option<&str> + pub(crate) fn key_read( + &mut self, + hw: &mut PddbOs, + dict: &str, + key: &str, + data: &mut [u8], + offset: Option, + basis_name: Option<&str>, ) -> Result { if let Some(basis_index) = self.select_basis(basis_name) { let basis = &mut self.cache[basis_index]; @@ -454,14 +483,24 @@ impl BasisCache { if let Some(dict_entry) = basis.dicts.get_mut(dict) { if dict_entry.ensure_key_entry(hw, &mut basis.v2p_map, &basis.cipher, key) { let mut needs_fill = false; - loop { // this weird loop works around an interior mutability issue with filling key data. It's awful, like a `goto`. + loop { + // this weird loop works around an interior mutability issue with filling key data. + // It's awful, like a `goto`. if needs_fill { // fetch the data from disk log::debug!("Small key evicted: filling {}:{}", dict, key); - // this call does a mutable borrow of dict_entry, which interferes with getting kcache below. - dict_entry.refill_small_key(hw, &basis.v2p_map, &basis.cipher, &mut self.data_cache, key); + // this call does a mutable borrow of dict_entry, which interferes with getting + // kcache below. + dict_entry.refill_small_key( + hw, + &basis.v2p_map, + &basis.cipher, + &mut self.data_cache, + key, + ); } - let kcache = dict_entry.keys.get_mut(key).expect("Entry was assured, but then not there!"); + let kcache = + dict_entry.keys.get_mut(key).expect("Entry was assured, but then not there!"); kcache.set_atime(self.tt.elapsed_ms()); // the key exists, *and* there's sufficient space for the data if kcache.start < SMALL_POOL_END { @@ -490,43 +529,71 @@ impl BasisCache { } } else { if offset.unwrap_or(0) as u64 > kcache.len { - return Err(Error::new(ErrorKind::UnexpectedEof, "offest requested is beyond the key length")); + return Err(Error::new( + ErrorKind::UnexpectedEof, + "offest requested is beyond the key length", + )); } - if data.len() == 0 { // mostly because i don't want to have to think about this case in the later logic. - return Ok(0) + if data.len() == 0 { + // mostly because i don't want to have to think about this case in the later + // logic. + return Ok(0); } // large pool fetch let mut abs_cursor = offset.unwrap_or(0) as u64; let mut blocks_read = 0; let mut bytes_read = 0; loop { - let start_vpage_addr = ((kcache.start + abs_cursor) / VPAGE_SIZE as u64) * VPAGE_SIZE as u64; + let start_vpage_addr = + ((kcache.start + abs_cursor) / VPAGE_SIZE as u64) * VPAGE_SIZE as u64; - if let Some(pp) = basis.v2p_map.get(&VirtAddr::new(start_vpage_addr).unwrap()) { + if let Some(pp) = basis.v2p_map.get(&VirtAddr::new(start_vpage_addr).unwrap()) + { let block_start_pos = (abs_cursor % VPAGE_SIZE as u64) as usize; assert!(pp.valid(), "v2p returned an invalid page"); - let pt_data = hw.data_decrypt_page(&basis.cipher, &basis.aad, pp).expect("Decryption auth error"); + let pt_data = hw + .data_decrypt_page(&basis.cipher, &basis.aad, pp) + .expect("Decryption auth error"); if blocks_read != 0 { - assert!(block_start_pos == 0, "algorithm error in handling offset data"); + assert!( + block_start_pos == 0, + "algorithm error in handling offset data" + ); } if blocks_read == 0 { - log::debug!("reading {} abs: {}, block_start: {}, block: {}, data.len:{} kcache.len:{}", - key, abs_cursor, block_start_pos, blocks_read, data.len(), kcache.len); + log::debug!( + "reading {} abs: {}, block_start: {}, block: {}, data.len:{} kcache.len:{}", + key, + abs_cursor, + block_start_pos, + blocks_read, + data.len(), + kcache.len + ); } else { - log::debug!(" reading {} abs: {}, block_start: {}, block: {}, remaining.data:{} kcache.len:{}", - key, abs_cursor, block_start_pos, blocks_read, data.len() - bytes_read, kcache.len); + log::debug!( + " reading {} abs: {}, block_start: {}, block: {}, remaining.data:{} kcache.len:{}", + key, + abs_cursor, + block_start_pos, + blocks_read, + data.len() - bytes_read, + kcache.len + ); } let data_offset = bytes_read; for (&src, dst) in - pt_data[ - size_of::() // always this fixed offset per block - + block_start_pos - .. - ].iter().zip(data[data_offset..].iter_mut()) { + pt_data[size_of::() // always this fixed offset per block + + block_start_pos..] + .iter() + .zip(data[data_offset..].iter_mut()) + { *dst = src; - // it'd be computationally more efficient to figure out what this should be going into - // every copy loop, but it's logically easier to think about in this form. Without this - // check, a user could read past the allocated space for a block... + // it'd be computationally more efficient to figure out what this + // should be going into + // every copy loop, but it's logically easier to think about in this + // form. Without this check, a + // user could read past the allocated space for a block... if abs_cursor >= kcache.len { break; } @@ -535,8 +602,14 @@ impl BasisCache { } blocks_read += 1; } else { - log::warn!("Not enough bytes available to read for key {}:{} ({}/{})", dict, key, abs_cursor, data.len()); - return Ok(bytes_read as usize) + log::warn!( + "Not enough bytes available to read for key {}:{} ({}/{})", + dict, + key, + abs_cursor, + data.len() + ); + return Ok(bytes_read as usize); } if abs_cursor >= kcache.len { break; @@ -545,10 +618,11 @@ impl BasisCache { break; } } - return Ok(bytes_read as usize) + return Ok(bytes_read as usize); } // note that from this point forward, either the "if" or the "else" branch returns; - // the end of this loop is never seen, unless a `continue` statement is hit within the loop. + // the end of this loop is never seen, unless a `continue` statement is hit within the + // loop. } } else { return Err(Error::new(ErrorKind::NotFound, "key not found")); @@ -561,8 +635,13 @@ impl BasisCache { } } - pub(crate) fn key_remove(&mut self, - hw: &mut PddbOs, dict: &str, key: &str, basis_name: Option<&str>, paranoid: bool + pub(crate) fn key_remove( + &mut self, + hw: &mut PddbOs, + dict: &str, + key: &str, + basis_name: Option<&str>, + paranoid: bool, ) -> Result<()> { if let Some(basis_index) = self.select_basis(basis_name) { let basis = &mut self.cache[basis_index]; @@ -583,7 +662,10 @@ impl BasisCache { // sync the key pools to disk if !dict_entry.sync_small_pool(hw, &mut basis.v2p_map, &basis.cipher) { - return Err(Error::new(ErrorKind::OutOfMemory, "Ran out of memory syncing small pool")); + return Err(Error::new( + ErrorKind::OutOfMemory, + "Ran out of memory syncing small pool", + )); } dict_entry.sync_large_pool(); // encrypt and write the dict entry to disk @@ -592,7 +674,7 @@ impl BasisCache { basis.basis_sync(hw); // finally, sync the page tables. basis.pt_sync(hw); - return Ok(()) + return Ok(()); } else { return Err(Error::new(ErrorKind::NotFound, "key not found")); } @@ -604,8 +686,12 @@ impl BasisCache { } } - pub(crate) fn key_list_remove(&mut self, - hw: &mut PddbOs, dict: &str, key_list: Vec::, basis_name: Option<&str> + pub(crate) fn key_list_remove( + &mut self, + hw: &mut PddbOs, + dict: &str, + key_list: Vec, + basis_name: Option<&str>, ) -> Result<()> { if let Some(basis_index) = self.select_basis(basis_name) { let basis = &mut self.cache[basis_index]; @@ -618,7 +704,10 @@ impl BasisCache { for key in key_list { if dict_entry.ensure_key_entry(hw, &mut basis.v2p_map, &basis.cipher, &key) { dict_entry.key_remove(hw, &mut basis.v2p_map, &basis.cipher, &key, false); - assert!(dict_entry.clean == false, "dictionary entry should have been marked unclean"); + assert!( + dict_entry.clean == false, + "dictionary entry should have been marked unclean" + ); } } // sync the key pools to disk @@ -643,10 +732,17 @@ impl BasisCache { /// Updates a key in a dictionary; if it doesn't exist, creates it. User can specify a basis, /// or rely upon the auto-basis select algorithm. - pub(crate) fn key_update(&mut self, - hw: &mut PddbOs, dict: &str, key: &str, data: &[u8], offset: Option, - alloc_hint: Option, basis_name: Option<&str>, truncate: bool) -> Result<()> { - + pub(crate) fn key_update( + &mut self, + hw: &mut PddbOs, + dict: &str, + key: &str, + data: &[u8], + offset: Option, + alloc_hint: Option, + basis_name: Option<&str>, + truncate: bool, + ) -> Result<()> { // we have to estimate how many pages are needed *before* we do anything, because we can't // mutate the page table to allocate data while we're accessing the page table. This huge gob of code // computes the pages needed. :-/ @@ -654,17 +750,15 @@ impl BasisCache { let reserved = if data.len() + offset.unwrap_or(0) > alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) { data.len() + offset.unwrap_or(0) } else { - if alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) == 0 { // disallow 0-sized alloc hints, round up to the default size if someone tries 0 + if alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) == 0 { + // disallow 0-sized alloc hints, round up to the default size if someone tries 0 DEFAULT_ALLOC_HINT } else { alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) } }; - let reserved_pages = if reserved % VPAGE_SIZE == 0 { - reserved / VPAGE_SIZE - } else { - (reserved / VPAGE_SIZE) + 1 - }; + let reserved_pages = + if reserved % VPAGE_SIZE == 0 { reserved / VPAGE_SIZE } else { (reserved / VPAGE_SIZE) + 1 }; if let Some(basis_index) = self.select_basis(basis_name) { let basis = &mut self.cache[basis_index]; if !basis.ensure_dict_in_cache(hw, dict) { @@ -679,14 +773,31 @@ impl BasisCache { if let Some(key_index) = small_storage_index_from_key(&kcache, dict_entry.index) { // it's probably going in the small pool. // index exists, see if the page exists - if reserved == 0 { // something went wrong here + if reserved == 0 { + // something went wrong here log::debug!("reserved {}", reserved); - log::debug!("{}:{} - [{}]{:x}+{}->{}", dict, key, kcache.descriptor_index, kcache.start, kcache.len, kcache.reserved); + log::debug!( + "{}:{} - [{}]{:x}+{}->{}", + dict, + key, + kcache.descriptor_index, + kcache.start, + kcache.len, + kcache.reserved + ); } if dict_entry.small_pool.len() > key_index { - log::trace!("resolved key index {}, small pool len: {}", key_index, dict_entry.small_pool.len()); + log::trace!( + "resolved key index {}, small pool len: {}", + key_index, + dict_entry.small_pool.len() + ); // see if the pool's address exists in the page table - let pool_vaddr = VirtAddr::new(small_storage_base_vaddr_from_indices(dict_entry.index, key_index)).unwrap(); + let pool_vaddr = VirtAddr::new(small_storage_base_vaddr_from_indices( + dict_entry.index, + key_index, + )) + .unwrap(); if !basis.v2p_map.contains_key(&pool_vaddr) { pages_needed += 1; } @@ -707,21 +818,24 @@ impl BasisCache { pages_needed += reserved_pages; } } else { - // there's no key cache entry. For simplicity, let's just assume this means none of the data - // has been allocated, and ask for it to be available. + // there's no key cache entry. For simplicity, let's just assume this means none of the + // data has been allocated, and ask for it to be available. // // At least in v1 of the code, this is the case. Later on maybe you could - // evict a key key cache entry to trim memory usage; if we do this, we'll have to implement a - // routine that is like ensure_key_entry() but doesn't do any allocations - it just does the search - // for the key record and if it doesn't exist reports that we'll need one, but if the key does exist + // evict a key key cache entry to trim memory usage; if we do this, we'll have to + // implement a routine that is like ensure_key_entry() but doesn't do + // any allocations - it just does the search for the key record and if + // it doesn't exist reports that we'll need one, but if the key does exist // and simply wasn't loaded into cache, then don't ask for the reservation. // - // But this should be a "last resort" move: preferably, if we have memory pressure, we should take - // the data entries in the key cache and turn them into None to reduce data pressure, rather than - // evicting the key indices themselves (because it causes bookkeeping problems like this). I guess - // if someone wanted to allocate thousand and thousands of keys, we'd eventually consume a megabyte of - // RAM, which is relatively precious, but....anyways. Maybe the answer is "don't do that" on a small memory - // machine and expect it to work? + // But this should be a "last resort" move: preferably, if we have memory pressure, we + // should take the data entries in the key cache and turn them into + // None to reduce data pressure, rather than evicting the key indices + // themselves (because it causes bookkeeping problems like this). I guess + // if someone wanted to allocate thousand and thousands of keys, we'd eventually consume a + // megabyte of RAM, which is relatively precious, but....anyways. + // Maybe the answer is "don't do that" on a small memory machine and + // expect it to work? pages_needed += reserved_pages; } } @@ -736,39 +850,46 @@ impl BasisCache { // now actually do the update if let Some(basis_index) = self.select_basis(basis_name) { let dict_found = (&mut self.cache[basis_index]).ensure_dict_in_cache(hw, dict); - if !dict_found { // now that we're clear of the deep search, mutate the basis if we are sure it's not there + if !dict_found { + // now that we're clear of the deep search, mutate the basis if we are sure it's not there self.dict_add(hw, dict, basis_name).expect("couldn't add dictionary"); } // at this point, the dictionary should definitely be in cache // pre-flight & allocatefree space requirements if let Some(dict_entry) = self.cache[basis_index].dicts.get(dict) { hw.ensure_fast_space_alloc(dict_entry.alloc_estimate_small(), &self.cache); - // large pools don't have caching implemented, so we don't have to check for free space for them + // large pools don't have caching implemented, so we don't have to check for free space for + // them } - // refetch the basis here to avoid the re-borrow problem, now that all the potential dict cache mutations are done + // refetch the basis here to avoid the re-borrow problem, now that all the potential dict cache + // mutations are done let basis = &mut self.cache[basis_index]; // bumping this every key update affects performance *a lot* -- don't think this is worth it. - // the bases should only "age" when dicts or keys are modified, not when any data in it is updated for any reason. - // basis.age = basis.age.saturating_add(1); + // the bases should only "age" when dicts or keys are modified, not when any data in it is updated + // for any reason. basis.age = basis.age.saturating_add(1); // basis.clean = false; // now do the sync if let Some(dict_entry) = basis.dicts.get_mut(dict) { - let updated_ptr = dict_entry.key_update(hw, &mut basis.v2p_map, - &basis.cipher, key, data, + let updated_ptr = dict_entry.key_update( + hw, + &mut basis.v2p_map, + &basis.cipher, + key, + data, offset.unwrap_or(0), alloc_hint, truncate, - basis.large_alloc_ptr.unwrap_or(PageAlignedVa::from(LARGE_POOL_START)) + basis.large_alloc_ptr.unwrap_or(PageAlignedVa::from(LARGE_POOL_START)), )?; basis.large_alloc_ptr = Some(updated_ptr); if !dict_entry.sync_small_pool(hw, &mut basis.v2p_map, &basis.cipher) { return Err(Error::new(ErrorKind::OutOfMemory, "Ran out of memory syncing small pool")); } - // we don't have large pool caches yet, but this is a placeholder to remember to do "something" at this point, - // once we do have them. + // we don't have large pool caches yet, but this is a placeholder to remember to do + // "something" at this point, once we do have them. dict_entry.sync_large_pool(); // encrypt and write the dict entry to disk @@ -778,7 +899,10 @@ impl BasisCache { // finally, sync the page tables. basis.pt_sync(hw); } else { - return Err(Error::new(ErrorKind::NotFound, "Requested dictionary not found, or could not be allocated.")); + return Err(Error::new( + ErrorKind::NotFound, + "Requested dictionary not found, or could not be allocated.", + )); } Ok(()) @@ -787,7 +911,13 @@ impl BasisCache { } } - pub(crate) fn key_attributes(&mut self, hw: &mut PddbOs, dict: &str, key: &str, basis_name: Option<&str>) -> Result { + pub(crate) fn key_attributes( + &mut self, + hw: &mut PddbOs, + dict: &str, + key: &str, + basis_name: Option<&str>, + ) -> Result { if basis_name.is_none() { for basis in self.cache.iter_mut().rev() { if !basis.ensure_dict_in_cache(hw, dict) { @@ -807,7 +937,7 @@ impl BasisCache { basis: (&basis.name).to_string(), flags: kcache.flags, index: kcache.descriptor_index, - }) + }); } else { // this is not a hard error, it just means that the key wasn't in this basis. // that's alright, it could be in one of the other ones! @@ -824,7 +954,8 @@ impl BasisCache { } else { let dict_entry = basis.dicts.get_mut(dict).expect("Entry was assured, but not there!"); if dict_entry.ensure_key_entry(hw, &mut basis.v2p_map, &basis.cipher, key) { - let kcache = dict_entry.keys.get_mut(key).expect("Entry was assured, but then not there!"); + let kcache = + dict_entry.keys.get_mut(key).expect("Entry was assured, but then not there!"); Ok(KeyAttributes { len: kcache.len as usize, reserved: kcache.reserved as usize, @@ -844,7 +975,12 @@ impl BasisCache { } } - pub(crate) fn dict_attributes(&mut self, hw: &mut PddbOs, dict: &str, basis_name: Option<&str>) -> Result { + pub(crate) fn dict_attributes( + &mut self, + hw: &mut PddbOs, + dict: &str, + basis_name: Option<&str>, + ) -> Result { if basis_name.is_none() { for basis in self.cache.iter_mut().rev() { if !basis.ensure_dict_in_cache(hw, dict) { @@ -870,23 +1006,33 @@ impl BasisCache { } } - /// this largely copies code from the pddb_mount() routine. Perhaps this should be modified a little bit to - /// re-use that code. However, there are material differences in how the passwords are handled between - /// these two methods, so the API calls are different. pddb_mount mounts the system basis with the intention - /// of making it persistent, and assuming you're coming up from a blank slate. This routine makes no such - /// assumptions and allows one to specify a persistence. - pub(crate) fn basis_unlock(&mut self, hw: &mut PddbOs, name: &str, password: &str, - policy: BasisRetentionPolicy) -> Option { - let basis_key = hw.basis_derive_key(name, password); + /// this largely copies code from the pddb_mount() routine. Perhaps this should be modified a little bit + /// to re-use that code. However, there are material differences in how the passwords are handled + /// between these two methods, so the API calls are different. pddb_mount mounts the system basis with + /// the intention of making it persistent, and assuming you're coming up from a blank slate. This + /// routine makes no such assumptions and allows one to specify a persistence. + pub(crate) fn basis_unlock( + &mut self, + hw: &mut PddbOs, + name: &str, + password: &str, + policy: BasisRetentionPolicy, + ) -> Option { + let basis_key = hw.basis_derive_key(name, password); if let Some(basis_map) = hw.pt_scan_key(&basis_key.pt, &basis_key.data, name) { let aad = hw.data_aad(name); if let Some(root_page) = basis_map.get(&VirtAddr::new(VPAGE_SIZE as u64).unwrap()) { let vpage = match hw.data_decrypt_page_with_commit(&basis_key.data, &aad, root_page) { Some(data) => data, - None => {log::error!("Could not find basis {} root", name); return None;}, + None => { + log::error!("Could not find basis {} root", name); + return None; + } }; let mut basis_root = BasisRoot::default(); - for (&src, dst) in vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) { + for (&src, dst) in + vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) + { *dst = src; } if basis_root.magic != PDDB_MAGIC { @@ -897,7 +1043,8 @@ impl BasisCache { log::error!("PDDB version mismatch in system basis root. Unrecoverable error."); return None; } - let basis_name = std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]).expect("basis name is not valid utf-8"); + let basis_name = std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]) + .expect("basis name is not valid utf-8"); if basis_name != name { log::error!("PDDB mount requested {}, but got {}; aborting.", name, basis_name); return None; @@ -922,7 +1069,7 @@ impl BasisCache { return Err(Error::new(ErrorKind::OutOfMemory, "No free space to create basis")); }; - let basis_key = hw.basis_derive_key(name, password); + let basis_key = hw.basis_derive_key(name, password); if let Some(_basis_map) = hw.pt_scan_key(&basis_key.pt, &basis_key.data, name) { return Err(Error::new(ErrorKind::AlreadyExists, "Basis already exists")); @@ -934,7 +1081,7 @@ impl BasisCache { version: PDDB_VERSION, name: BasisRootName::try_from_str(name).unwrap(), age: 0, - num_dictionaries: 0 + num_dictionaries: 0, }; // allocate one page for the basis root if let Some(alloc) = hw.try_fast_space_alloc() { @@ -951,8 +1098,7 @@ impl BasisCache { let pp = basis_v2p_map.get(&VirtAddr::new(1 * VPAGE_SIZE as u64).unwrap()).unwrap(); assert!(pp.valid(), "v2p returned an invalid page"); let journal_bytes = (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes(); - let slice_iter = - journal_bytes.iter() // journal rev + let slice_iter = journal_bytes.iter() // journal rev .chain(basis_root.as_ref().iter()); let mut block = [0 as u8; KCOM_CT_LEN]; for (&src, dst) in slice_iter.zip(block.iter_mut()) { @@ -960,7 +1106,7 @@ impl BasisCache { } hw.data_encrypt_and_patch_page_with_commit(&basis_key.data, &aad, &mut block, &pp); - let cipher = Aes256::new(GenericArray::from_slice(&basis_key.pt)); + let cipher = Aes256::new(GenericArray::from_slice(&basis_key.pt)); for (&virt, phys) in basis_v2p_map.iter_mut() { hw.pt_patch_mapping(virt, phys.page_number(), &cipher); // mark the entry as clean, as it has been sync'd to disk @@ -977,6 +1123,7 @@ impl BasisCache { } ret } + pub(crate) fn basis_latest(&self) -> Option<&str> { if let Some(basis_index) = self.select_basis(None) { let basis = &self.cache[basis_index]; @@ -986,9 +1133,7 @@ impl BasisCache { } } - pub(crate) fn basis_add(&mut self, basis: BasisCacheEntry) { - self.cache.push(basis); - } + pub(crate) fn basis_add(&mut self, basis: BasisCacheEntry) { self.cache.push(basis); } pub(crate) fn basis_unmount(&mut self, hw: &mut PddbOs, basis_name: &str) -> Result<()> { if let Some(basis_index) = self.select_basis(Some(basis_name)) { @@ -1004,15 +1149,15 @@ impl BasisCache { pub(crate) fn basis_contains(&self, by_name: &str) -> bool { for bcache in &self.cache { if bcache.name == by_name { - return true + return true; } } false } /// note: you can "delete" a basis simply by forgetting its password, but this is more thorough. - /// there might also need to be a variant to make which is a "change my password" function, but that is actually - /// surprisingly hard. + /// there might also need to be a variant to make which is a "change my password" function, but that is + /// actually surprisingly hard. pub(crate) fn basis_delete(&mut self, hw: &mut PddbOs, basis_name: &str) -> Result<()> { if let Some(basis_index) = self.select_basis(Some(basis_name)) { let basis = &mut self.cache[basis_index]; @@ -1058,13 +1203,12 @@ impl BasisCache { if basis.policy_state >= sleeps { lock_list.push(basis.name.clone()); } - } - /* - BasisRetentionPolicy::TimeOutSecs(secs) => { - if secs < basis.policy_state { - lock_list.push(basis.name.clone()); - } - }*/ + } /* + BasisRetentionPolicy::TimeOutSecs(secs) => { + if secs < basis.policy_state { + lock_list.push(basis.name.clone()); + } + }*/ } } for basis in lock_list { @@ -1086,13 +1230,15 @@ impl BasisCache { } total_size } + /// attempts to prune `target_bytes` out of the cached data set pub(crate) fn cache_prune(&mut self, hw: &mut PddbOs, target_bytes: usize) -> usize { let mut pruned = 0; // this does it a "dumb" way, but at least it's sort of obvious how it works - // 0. sync the basis and dictionaries to disk, so that removing cache entries are guaranteed not to be problematic. - // 1. iterate through all the known keys, creating a sorted heap of fully-specified keys (basis/dict/key) - // sorted by access time + // 0. sync the basis and dictionaries to disk, so that removing cache entries are guaranteed not to be + // problematic. + // 1. iterate through all the known keys, creating a sorted heap of fully-specified keys + // (basis/dict/key) sorted by access time // 2. evict keys with the oldest access time, until target_bytes is hit. self.sync(hw, None, false).unwrap(); @@ -1102,15 +1248,13 @@ impl BasisCache { if dict.flags.valid() { for (key_name, key) in dict.keys.iter() { if key.flags.valid() { - candidates.push(Reverse( - KeyAge { - atime: key.atime(), - _size: key.size(), - key: key_name.to_string(), - dict: dict_name.to_string(), - basis: basis.name.to_string(), - } - )); + candidates.push(Reverse(KeyAge { + atime: key.atime(), + _size: key.size(), + key: key_name.to_string(), + dict: dict_name.to_string(), + basis: basis.name.to_string(), + })); } } } @@ -1124,7 +1268,13 @@ impl BasisCache { match basis.dicts.get_mut(&ka.dict) { Some(dentry) => { let freed = dentry.evict_keycache_entry(&ka.key); - log::trace!("pruned {} bytes from atime {} / total {} key {}", freed, ka.atime, pruned, ka.key); + log::trace!( + "pruned {} bytes from atime {} / total {} key {}", + freed, + ka.atime, + pruned, + ka.key + ); pruned += freed; if pruned >= target_bytes { return pruned; @@ -1152,19 +1302,13 @@ struct KeyAge { basis: String, } impl Ord for KeyAge { - fn cmp(&self, other: &Self) -> Ordering { - self.atime.cmp(&other.atime) - } + fn cmp(&self, other: &Self) -> Ordering { self.atime.cmp(&other.atime) } } impl PartialOrd for KeyAge { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl PartialEq for KeyAge { - fn eq(&self, other: &Self) -> bool { - self.atime == other.atime - } + fn eq(&self, other: &Self) -> bool { self.atime == other.atime } } impl Eq for KeyAge {} @@ -1177,25 +1321,29 @@ pub(crate) struct BasisCacheEntry { /// last sync time, in systicks, if any. Updated by any sync of basis, dict, or pt. pub last_sync: Option, /// Number of dictionaries. This should be greater than or equal to the number of elements in dicts. - /// If dicts has less elements than num_dicts, it means there are still dicts on-disk that haven't been cached. + /// If dicts has less elements than num_dicts, it means there are still dicts on-disk that haven't been + /// cached. pub num_dicts: u32, /// dictionary array. - pub dicts: HashMap::, + pub dicts: HashMap, /// A cached copy of the absolute offset of the next free dictionary slot, /// expressed as a number that needs to be multiplied by DICT_VSIZE to arrive at a virtual address pub free_dict_offset: Option, /// the cipher for the basis pub cipher: Aes256GcmSiv, - /// derived cipher for encrypting PTEs -- cache it, so we can save the time cost of constructing the cipher key schedule + /// derived cipher for encrypting PTEs -- cache it, so we can save the time cost of constructing the + /// cipher key schedule pub cipher_ecb: Aes256, - /// raw AES page table key -- needed because we have to do a low-level PT scan to generate FSCB, and sometimes the key comes from - /// a copy cached here, or from one derived solely for the FSCB scan. There is no way to copy an Aes256 record, so, we include - /// the raw key because we can copy that. :P so much for semantics. + /// raw AES page table key -- needed because we have to do a low-level PT scan to generate FSCB, and + /// sometimes the key comes from a copy cached here, or from one derived solely for the FSCB scan. + /// There is no way to copy an Aes256 record, so, we include the raw key because we can copy that. :P + /// so much for semantics. pub pt_key: GenericArray, - /// raw AES data key -- needed because we have to use this to derive commitment keys for the basis root record, to work around AES-GCM-SIV salamanders + /// raw AES data key -- needed because we have to use this to derive commitment keys for the basis root + /// record, to work around AES-GCM-SIV salamanders pub key: GenericArray, /// the AAD associated with this Basis - pub aad: Vec::, + pub aad: Vec, /// modification count pub age: u32, /// virtual to physical page map. It's the reverse mapping of the physical page table on disk. @@ -1214,7 +1362,13 @@ impl BasisCacheEntry { /// the basis. If `lazy` is true, it stops with the minimal amount of effort to respond to a query. /// If it `lazy` is false, it will populate the dictionary cache and key cache entries, as well as /// discover the location of the `large_alloc_ptr`. - pub(crate) fn mount(hw: &mut PddbOs, name: &str, key: &BasisKeys, lazy: bool, policy: BasisRetentionPolicy) -> Option { + pub(crate) fn mount( + hw: &mut PddbOs, + name: &str, + key: &BasisKeys, + lazy: bool, + policy: BasisRetentionPolicy, + ) -> Option { if let Some(basis_map) = hw.pt_scan_key(&key.pt, &key.data, name) { let cipher = Aes256GcmSiv::new(&key.data.into()); let aad = hw.data_aad(name); @@ -1222,12 +1376,21 @@ impl BasisCacheEntry { if let Some(root_page) = basis_map.get(&VirtAddr::new(VPAGE_SIZE as u64).unwrap()) { let vpage = match hw.data_decrypt_page_with_commit(&key.data, &aad, root_page) { Some(data) => data, - None => {log::error!("System basis decryption did not authenticate. Unrecoverable error."); return None;}, + None => { + log::error!("System basis decryption did not authenticate. Unrecoverable error."); + return None; + } }; - // if the below assertion fails, you will need to re-code this to decrypt more than one VPAGE and stripe into a basis root struct - assert!(size_of::() <= VPAGE_SIZE, "BasisRoot has grown past a single VPAGE, this routine needs to be re-coded to accommodate the extra bulk"); + // if the below assertion fails, you will need to re-code this to decrypt more than one VPAGE + // and stripe into a basis root struct + assert!( + size_of::() <= VPAGE_SIZE, + "BasisRoot has grown past a single VPAGE, this routine needs to be re-coded to accommodate the extra bulk" + ); let mut basis_root = BasisRoot::default(); - for (&src, dst) in vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) { + for (&src, dst) in + vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) + { *dst = src; } if basis_root.magic != PDDB_MAGIC { @@ -1238,9 +1401,13 @@ impl BasisCacheEntry { log::error!("PDDB version mismatch in system basis root. Unrecoverable error."); return None; } - let basis_name = std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]).expect("basis name is not valid utf-8"); + let basis_name = std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]) + .expect("basis name is not valid utf-8"); if basis_name != String::from(name) { - log::error!("Discovered basis name does not match the requested name: {}; aborting mount operation.", basis_name); + log::error!( + "Discovered basis name does not match the requested name: {}; aborting mount operation.", + basis_name + ); return None; } let mut bcache = BasisCacheEntry { @@ -1268,7 +1435,8 @@ impl BasisCacheEntry { log::info!("Basis {} found and reconstructed", name); return Some(bcache); } else { - // i guess technically we could try a brute-force search for the page if it went missing, but meh. + // i guess technically we could try a brute-force search for the page if it went missing, but + // meh. log::error!("Basis {} did not contain a root page -- unrecoverable error.", name); return None; } @@ -1277,6 +1445,7 @@ impl BasisCacheEntry { None } } + /// called during the initial basis scan to track where the large allocation pointer end should be. /// basically try to find the maximal extent of already allocated data, and start allocating from there. pub(crate) fn large_pool_update(&mut self, maybe_end: u64) { @@ -1294,13 +1463,15 @@ impl BasisCacheEntry { num_valid += 1; } } - // note to self: this should be true, because even if we didn't read all the dictionaries in from the basis, - // and then say, deleted a bunch of dictionaries and then added a bunch more, the num_dicts field should - // move up and down with the additions/deletions, and thus should relatively be larger than or equal to the num_valid - // at all times. In other words, the difference of self.num_dicts vs num_valid should be precisely the number - // of dictionary entries on disk that we haven't loaded into the cache. + // note to self: this should be true, because even if we didn't read all the dictionaries in from the + // basis, and then say, deleted a bunch of dictionaries and then added a bunch more, the + // num_dicts field should move up and down with the additions/deletions, and thus should + // relatively be larger than or equal to the num_valid at all times. In other words, the + // difference of self.num_dicts vs num_valid should be precisely the number of dictionary + // entries on disk that we haven't loaded into the cache. assert!(num_valid <= self.num_dicts, "Inconsistency in number of dictionaries in the basis"); - if num_valid == self.num_dicts { // no need to scan the index, just refresh the dictionaries themselves + if num_valid == self.num_dicts { + // no need to scan the index, just refresh the dictionaries themselves let mut largest_extent = 0; for dict in self.dicts.values_mut() { if dict.flags.valid() { @@ -1311,7 +1482,8 @@ impl BasisCacheEntry { } } self.large_pool_update(largest_extent); - } else { // scan the full index + } else { + // scan the full index let mut try_entry = 1; let mut dict_count = 0; while try_entry <= DICT_MAXCOUNT && dict_count < self.num_dicts { @@ -1320,7 +1492,9 @@ impl BasisCacheEntry { assert!(pp.valid(), "v2p returned an invalid page"); if let Some(dict) = self.dict_decrypt(hw, &pp) { if dict.flags.valid() { - let dict_name = std::str::from_utf8(&dict.name.data[..dict.name.len as usize]).expect("dict name is not valid utf-8").to_string(); + let dict_name = std::str::from_utf8(&dict.name.data[..dict.name.len as usize]) + .expect("dict name is not valid utf-8") + .to_string(); let dict_present_and_valid = if let Some(d) = self.dicts.get(&dict_name) { d.flags.valid() } else { @@ -1332,25 +1506,37 @@ impl BasisCacheEntry { self.dicts.insert(dict_name.to_string(), dcache); self.large_pool_update(max_large_alloc.get()); } else { - let dcache = self.dicts.get_mut(&dict_name).expect("dict should be present, as we checked for it already..."); + let dcache = self + .dicts + .get_mut(&dict_name) + .expect("dict should be present, as we checked for it already..."); let extent = dcache.fill(hw, &mut self.v2p_map, &self.cipher, false).get(); self.large_pool_update(extent); } dict_count += 1; } else { - // this is an empty dictionary entry. we could stick a dictionary in here later on, take note if we haven't already computed that - if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } + // this is an empty dictionary entry. we could stick a dictionary in here later + // on, take note if we haven't already computed that + if self.free_dict_offset.is_none() { + self.free_dict_offset = Some(try_entry as u32); + } } } else { - if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } + if self.free_dict_offset.is_none() { + self.free_dict_offset = Some(try_entry as u32); + } } } else { - if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } + if self.free_dict_offset.is_none() { + self.free_dict_offset = Some(try_entry as u32); + } } try_entry += 1; } if try_entry <= DICT_MAXCOUNT { - if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } + if self.free_dict_offset.is_none() { + self.free_dict_offset = Some(try_entry as u32); + } } } } @@ -1368,7 +1554,8 @@ impl BasisCacheEntry { // ensure all the keys are in RAM dcache.fill(hw, &self.v2p_map, &self.cipher, false); - // allocate a copy of the key list, to avoid interior mutability problems with the next remove step + // allocate a copy of the key list, to avoid interior mutability problems with the next remove + // step let mut key_list = Vec::::new(); for (key, entry) in dcache.keys.iter() { if entry.flags.valid() { @@ -1382,10 +1569,12 @@ impl BasisCacheEntry { } // wipe & de-allocate any small pages for index in 0..dcache.small_pool.len() { - let pool_vaddr = VirtAddr::new(small_storage_base_vaddr_from_indices(dcache.index, index)).unwrap(); + let pool_vaddr = + VirtAddr::new(small_storage_base_vaddr_from_indices(dcache.index, index)).unwrap(); if let Some(pp) = self.v2p_map.get_mut(&pool_vaddr) { assert!(pp.valid(), "v2p returned an invalid page"); - { // always nuke old data + { + // always nuke old data let mut random = [0u8; PAGE_SIZE]; hw.trng_slice(&mut random); hw.patch_data(&random, pp.page_number() * PAGE_SIZE as u32); @@ -1398,16 +1587,25 @@ impl BasisCacheEntry { /* for pp in self.v2p_map.values() { log::info!("v2p entry: {:x?}", pp); } */ - // erase the entire dictionary + key allocation area by writing over with random data. It's important to - // do a comprehensive erase, because if the dictionary slot is re-used, the previously allocated key entries - // decrypt correctly, are interpreted as valid keys, and thus cause consistency errors. + // erase the entire dictionary + key allocation area by writing over with random data. It's + // important to do a comprehensive erase, because if the dictionary slot is re-used, + // the previously allocated key entries decrypt correctly, are interpreted as valid + // keys, and thus cause consistency errors. let key_pages = 1 + (dcache.last_disk_key_index + 1) as usize / DK_PER_VPAGE; for page in 0..key_pages { - // note: check this code against dict_indices_to_vaddr() -- it's recoded here because we go by page, not by index, but this needs to be consistent with that function - let dk_vaddr = VirtAddr::new(dcache.index.get() as u64 * DICT_VSIZE as u64 + page as u64 * VPAGE_SIZE as u64).unwrap(); + // note: check this code against dict_indices_to_vaddr() -- it's recoded here because we go by + // page, not by index, but this needs to be consistent with that function + let dk_vaddr = VirtAddr::new( + dcache.index.get() as u64 * DICT_VSIZE as u64 + page as u64 * VPAGE_SIZE as u64, + ) + .unwrap(); if let Some(pp) = self.v2p_map.get_mut(&dk_vaddr) { assert!(pp.valid(), "v2p returned an invalid page"); - log::info!("erasing dk page 0x{:x}/0x{:x}", dk_vaddr, pp.page_number() as usize * PAGE_SIZE); + log::info!( + "erasing dk page 0x{:x}/0x{:x}", + dk_vaddr, + pp.page_number() as usize * PAGE_SIZE + ); let mut random = [0u8; PAGE_SIZE]; hw.trng_slice(&mut random); hw.patch_data(&random, pp.page_number() * PAGE_SIZE as u32); @@ -1415,7 +1613,9 @@ impl BasisCacheEntry { hw.fast_space_free(pp); assert!(pp.valid() == false, "pp is still marked as valid!"); } else { - log::warn!("Inconsistent internal state: requested dictionary didn't have a mapping in the page table."); + log::warn!( + "Inconsistent internal state: requested dictionary didn't have a mapping in the page table." + ); } } @@ -1433,7 +1633,8 @@ impl BasisCacheEntry { } /// Called to compute the next free index. Normally this will get filled during a disk search for - /// cache entries, but it could be None if a new dict entry was allocated and no other entries were loaded. + /// cache entries, but it could be None if a new dict entry was allocated and no other entries were + /// loaded. pub(crate) fn dict_get_free_offset(&mut self, hw: &mut PddbOs) -> u32 { if let Some(offset) = self.free_dict_offset.take() { return offset; @@ -1445,22 +1646,23 @@ impl BasisCacheEntry { assert!(pp.valid(), "v2p returned an invalid page"); if self.dict_decrypt(hw, &pp).is_none() { // mapping exists, but the data is invalid. It's a free entry. - return try_entry as u32 + return try_entry as u32; } } else { // mapping doesn't exist yet, that's a free entry - return try_entry as u32 + return try_entry as u32; } try_entry += 1; } } - // maybe we should handle this better? but, I think this should be super-rare, as we allow 16384 dictionaries per Basis, - // so if we got here I'm guessing it's more likely due to a coding error than an actually full Basis. + // maybe we should handle this better? but, I think this should be super-rare, as we allow 16384 + // dictionaries per Basis, so if we got here I'm guessing it's more likely due to a coding + // error than an actually full Basis. panic!("Basis full, can't allocate dictionary"); } - /// Returns a tuple of "dictionary offset" , "dictionary"; the "dictionary offset" needs to be multiplied by DICT_VSIZE to arrive - /// at a fully expanded virtual address. + /// Returns a tuple of "dictionary offset" , "dictionary"; the "dictionary offset" needs to be multiplied + /// by DICT_VSIZE to arrive at a fully expanded virtual address. pub(crate) fn dict_deep_search(&mut self, hw: &mut PddbOs, name: &str) -> Option<(u32, Dictionary)> { let mut try_entry = 1; let mut dict_count = 0; @@ -1469,19 +1671,22 @@ impl BasisCacheEntry { if let Some(pp) = self.v2p_map.get(&dict_vaddr) { assert!(pp.valid(), "v2p returned an invalid page"); if let Some(dict) = self.dict_decrypt(hw, &pp) { - let dict_name = std::str::from_utf8(&dict.name.data[..dict.name.len as usize]).expect("dict name is not valid utf-8"); + let dict_name = std::str::from_utf8(&dict.name.data[..dict.name.len as usize]) + .expect("dict name is not valid utf-8"); if (dict_name == name) && dict.flags.valid() { - return Some((try_entry as u32, dict)) + return Some((try_entry as u32, dict)); } dict_count += 1; } else { - // this is an empty dictionary entry. we could stick a dictionary in here later on, take note if we haven't already computed that + // this is an empty dictionary entry. we could stick a dictionary in here later on, take + // note if we haven't already computed that if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } } } else { - // this is an empty dictionary entry. we could stick a dictionary in here later on, take note if we haven't already computed that + // this is an empty dictionary entry. we could stick a dictionary in here later on, take note + // if we haven't already computed that if self.free_dict_offset.is_none() { self.free_dict_offset = Some(try_entry as u32); } @@ -1518,7 +1723,8 @@ impl BasisCacheEntry { hw.pt_erase(phys.page_number()); } } - // have to do this in a second phase due to interior mutability problems doing it inside the first iterator + // have to do this in a second phase due to interior mutability problems doing it inside the first + // iterator for kill in kill_list { if self.v2p_map.remove(&kill).is_none() { log::warn!("went to remove PTE from v2p map but it wasn't there: {:x}", kill); @@ -1552,11 +1758,18 @@ impl BasisCacheEntry { if let Some(dict) = self.dicts.get_mut(&String::from(name)) { let dict_offset = VirtAddr::new(dict.index.get() as u64 * DICT_VSIZE).unwrap(); if cleanup && (dict.key_count != dict.found_key_count) { - log::info!("Cleaning up key_count inconsistency: {} -> {}", dict.key_count, dict.found_key_count); + log::info!( + "Cleaning up key_count inconsistency: {} -> {}", + dict.key_count, + dict.found_key_count + ); dict.key_count = dict.found_key_count; } if !dict.clean { - let dict_name = DictName::try_from_str(name).or(Err(Error::new(ErrorKind::InvalidInput, "dictionary name invalid: invalid utf-8 or length")))?; + let dict_name = DictName::try_from_str(name).or(Err(Error::new( + ErrorKind::InvalidInput, + "dictionary name invalid: invalid utf-8 or length", + )))?; let dict_disk = Dictionary { flags: dict.flags, age: dict.age, @@ -1566,13 +1779,15 @@ impl BasisCacheEntry { }; log::debug!("syncing dict {} with {} keys", name, dict.key_count); // log::info!("raw: {:x?}", dict_disk.deref()); - // observation: all keys to be flushed to disk will be in the KeyCacheEntry. Some may be clean, - // but definitely all the dirty ones are in there (if they aren't, where else would they be??) + // observation: all keys to be flushed to disk will be in the KeyCacheEntry. Some may be + // clean, but definitely all the dirty ones are in there (if they aren't, + // where else would they be??) // this is the virtual page within the dictionary region that we're currently serializing let mut vpage_num = 0; // because the root record is dirty, we always need to regenerate page 0. - // note: other pages are only generated on-demand, based on if there is a dirty key in that page or not. - let mut loopcheck= 0; + // note: other pages are only generated on-demand, based on if there is a dirty key in that + // page or not. + let mut loopcheck = 0; let mut sync_count = 0; loop { loopcheck += 1; @@ -1580,7 +1795,8 @@ impl BasisCacheEntry { log::warn!("potential infinite loop detected sync dict {}", name); } // 1. resolve the virtual address to a target page - let cur_vpage = VirtAddr::new(dict_offset.get() + (vpage_num as u64 * VPAGE_SIZE as u64)).unwrap(); + let cur_vpage = + VirtAddr::new(dict_offset.get() + (vpage_num as u64 * VPAGE_SIZE as u64)).unwrap(); let pp = self.v2p_map.entry(cur_vpage).or_insert_with(|| { let mut ap = hw.try_fast_space_alloc().expect("FastSpace empty"); ap.set_valid(true); @@ -1603,12 +1819,14 @@ impl BasisCacheEntry { } // 2(b). fill in the target vpage with data: general key case - // Scan the DictCacheEntry.keys record for valid keys within the current target vpage's range - // this is not a terribly efficient operation right now, because the DictCacheEntry is designed to - // be searched by name, but in this case, we want to check for an index range. There's a lot - // of things we could do to optimize this, depending on the memory/time trade-off we want to - // make, but for now, let's do it with a dumb O(N) scan through the KeyCacheEntry, running under - // the assumption that the KeyCacheEntry doesn't ever get to a very large N. + // Scan the DictCacheEntry.keys record for valid keys within the current target vpage's + // range this is not a terribly efficient operation right now, because + // the DictCacheEntry is designed to be searched by name, but in this + // case, we want to check for an index range. There's a lot + // of things we could do to optimize this, depending on the memory/time trade-off we want + // to make, but for now, let's do it with a dumb O(N) scan through the + // KeyCacheEntry, running under the assumption that the KeyCacheEntry + // doesn't ever get to a very large N. let next_vpage = VirtAddr::new(cur_vpage.get() + VPAGE_SIZE as u64).unwrap(); let mut invalidate = Vec::::new(); // remember which keys to invalidate for (key_name, key) in dict.keys.iter_mut() { @@ -1619,12 +1837,16 @@ impl BasisCacheEntry { ); }*/ if key.flags.valid() { - if key.descriptor_vaddr(dict_offset) >= cur_vpage && - key.descriptor_vaddr(dict_offset) < next_vpage { + if key.descriptor_vaddr(dict_offset) >= cur_vpage + && key.descriptor_vaddr(dict_offset) < next_vpage + { log::trace!("merging in key {}", key_name); // key is within the current page, add it to the target list let mut dk_entry = DictKeyEntry::default(); - let kn = KeyName::try_from_str(key_name).or(Err(Error::new(ErrorKind::InvalidInput, "key name invalid: invalid utf-8 or length")))?; + let kn = KeyName::try_from_str(key_name).or(Err(Error::new( + ErrorKind::InvalidInput, + "key name invalid: invalid utf-8 or length", + )))?; let key_desc = KeyDescriptor { start: key.start, len: key.len, @@ -1636,14 +1858,21 @@ impl BasisCacheEntry { for (&src, dst) in key_desc.deref().iter().zip(dk_entry.data.iter_mut()) { *dst = src; } - dk_vpage.elements[key.descriptor_index.get() as usize % DK_PER_VPAGE] = Some(dk_entry); + dk_vpage.elements[key.descriptor_index.get() as usize % DK_PER_VPAGE] = + Some(dk_entry); key.clean = true; } else { - log::debug!("proposed key fell outside of our vpage: {} vpage{:x}/vaddr{:x}", key_name, cur_vpage.get(), key.descriptor_vaddr(dict_offset)); + log::debug!( + "proposed key fell outside of our vpage: {} vpage{:x}/vaddr{:x}", + key_name, + cur_vpage.get(), + key.descriptor_vaddr(dict_offset) + ); } } else { - if key.descriptor_vaddr(dict_offset) >= cur_vpage && - key.descriptor_vaddr(dict_offset) < next_vpage { + if key.descriptor_vaddr(dict_offset) >= cur_vpage + && key.descriptor_vaddr(dict_offset) < next_vpage + { dk_vpage.elements[key.descriptor_index.get() as usize % DK_PER_VPAGE] = None; invalidate.push(key_name.to_string()); } @@ -1661,18 +1890,25 @@ impl BasisCacheEntry { data } else { log::trace!("existing data invalid, creating a new page"); - // the existing data was invalid (this happens e.g. on the first time a dict is created). Just overwrite the whole page. + // the existing data was invalid (this happens e.g. on the first time a dict is + // created). Just overwrite the whole page. let mut d = vec![0u8; VPAGE_SIZE + size_of::()]; - for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes().iter().zip(d[..size_of::()].iter_mut()) { + for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE) + .to_le_bytes() + .iter() + .zip(d[..size_of::()].iter_mut()) + { *dst = src; } d }; - for (index, stride) in page[size_of::()..].chunks_mut(DK_STRIDE).enumerate() { + for (index, stride) in page[size_of::()..].chunks_mut(DK_STRIDE).enumerate() + { if let Some(elem) = dk_vpage.elements[index] { stride.copy_from_slice(&elem.data); } else { - // actively zero-ize fields that are `None` so they don't decrypt to valid records later on. + // actively zero-ize fields that are `None` so they don't decrypt to valid records + // later on. stride.copy_from_slice(&[0u8; DK_STRIDE]); } } @@ -1680,15 +1916,22 @@ impl BasisCacheEntry { log::debug!("patching pp {:x?} with aad {:x?}, data {:x?}", pp, self.aad, &page[..256]); hw.data_encrypt_and_patch_page(&self.cipher, &self.aad, &mut page, &pp); - // 4. Check for dirty keys, if there are still some, update vpage_num to target them; otherwise + // 4. Check for dirty keys, if there are still some, update vpage_num to target them; + // otherwise // exit the loop let mut found_next = false; for (name, key) in dict.keys.iter() { if !key.clean && key.flags.valid() || !key.flags.valid() { found_next = true; - // note: we don't care *which* vpage we do next -- so we just break after finding the first one + // note: we don't care *which* vpage we do next -- so we just break after finding + // the first one vpage_num = key.descriptor_vpage_num(); - log::debug!("sync searching again because key {} was valid {} clean {}", name, key.clean, key.flags.valid()); + log::debug!( + "sync searching again because key {} was valid {} clean {}", + name, + key.clean, + key.flags.valid() + ); sync_count += 1; break; } @@ -1730,13 +1973,14 @@ impl BasisCacheEntry { age: self.age, num_dictionaries: self.num_dicts, }; - let pp = self.v2p_map.get(&VirtAddr::new(1 * VPAGE_SIZE as u64).unwrap()) + let pp = self + .v2p_map + .get(&VirtAddr::new(1 * VPAGE_SIZE as u64).unwrap()) .expect("Internal consistency error: Basis exists, but its root map was not allocated!"); assert!(pp.valid(), "basis page was invalid"); log::debug!("{} before-sync journal: {}", self.name, self.journal); let journal_bytes = self.journal.to_le_bytes(); // journal gets bumped by the patching function now - let slice_iter = - journal_bytes.iter() // journal rev + let slice_iter = journal_bytes.iter() // journal rev .chain(basis_root.as_ref().iter()); let mut block = [0 as u8; KCOM_CT_LEN]; for (&src, dst) in slice_iter.zip(block.iter_mut()) { @@ -1754,17 +1998,15 @@ impl BasisCacheEntry { pub(crate) fn ensure_dict_in_cache(&mut self, hw: &mut PddbOs, name: &str) -> bool { let mut dict_found = false; let dict_in_cache_and_valid = - if let Some(dict) = self.dicts.get(name) { - dict.flags.valid() - } else { - false - }; + if let Some(dict) = self.dicts.get(name) { dict.flags.valid() } else { false }; if !dict_in_cache_and_valid { log::debug!("dict: key not in cache {}", name); // if the dictionary doesn't exist in our cache it doesn't necessarily mean it // doesn't exist. Do a comprehensive search if our cache isn't complete. if let Some((index, dict_record)) = self.dict_deep_search(hw, name) { - let dict_name = std::str::from_utf8(&dict_record.name.data[..dict_record.name.len as usize]).expect("dict name is not valid utf-8").to_string(); + let dict_name = std::str::from_utf8(&dict_record.name.data[..dict_record.name.len as usize]) + .expect("dict name is not valid utf-8") + .to_string(); let dcache = DictCacheEntry::new(dict_record, index as usize, &self.aad); self.dicts.insert(dict_name, dcache); dict_found = true; @@ -1800,7 +2042,7 @@ impl BasisCacheEntry { } match self.dict_sync(hw, &dict, cleanup) { - Ok(_) => {}, + Ok(_) => {} Err(e) => { log::error!("Error encountered syncing dict {}: {:?}", dict, e); return Err(Error::new(ErrorKind::Other, e.to_string())); @@ -1824,7 +2066,6 @@ impl BasisCacheEntry { self.large_alloc_ptr = Some(self.large_alloc_ptr.unwrap_or(PageAlignedVa::from(LARGE_POOL_START)) + PageAlignedVa::from(amount)); return alloc_ptr.as_u64() }*/ - } // **** @@ -1855,10 +2096,5 @@ impl BasisRootName { } } impl Default for BasisRootName { - fn default() -> BasisRootName { - BasisRootName{ - len: 0, - data: [0; BASIS_NAME_LEN - 1] - } - } + fn default() -> BasisRootName { BasisRootName { len: 0, data: [0; BASIS_NAME_LEN - 1] } } } diff --git a/services/pddb/src/backend/bcrypt.rs b/services/pddb/src/backend/bcrypt.rs index 082675163..19caff024 100644 --- a/services/pddb/src/backend/bcrypt.rs +++ b/services/pddb/src/backend/bcrypt.rs @@ -1,4 +1,3 @@ - // this code is based on https://github.com/Keats/rust-bcrypt // the original crate is not used directly because the base64 wrappers cause the crate // to be incompatible with a no-std environment. We are storing binary data directly @@ -30,7 +29,11 @@ pub fn bcrypt(cost: u32, salt: &[u8], pw: &str, output: &mut [u8]) { assert!(output.len() == 24); let pw_len = if pw.len() > crate::api::PASSWORD_LEN { - log::warn!("password of length {} is truncated to {} bytes [reason: bcrypt limitation]", pw.len(), crate::api::PASSWORD_LEN); + log::warn!( + "password of length {} is truncated to {} bytes [reason: bcrypt limitation]", + pw.len(), + crate::api::PASSWORD_LEN + ); crate::api::PASSWORD_LEN } else { pw.len() + 1 @@ -47,19 +50,19 @@ pub fn bcrypt(cost: u32, salt: &[u8], pw: &str, output: &mut [u8]) { let state = setup(cost, salt, &plaintext_copy[..pw_len]); // erase the plaintext copy as soon as we're done with it - // an unsafe method is used because the compiler will correctly reason that plaintext_copy goes out of scope - // and these writes are never read, and therefore they may be optimized out. + // an unsafe method is used because the compiler will correctly reason that plaintext_copy goes out of + // scope and these writes are never read, and therefore they may be optimized out. let pt_ptr = plaintext_copy.as_mut_ptr(); for i in 0..plaintext_copy.len() { - unsafe{pt_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + pt_ptr.add(i).write_volatile(core::mem::zeroed()); + } } core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); // OrpheanBeholderScryDoubt #[allow(clippy::unreadable_literal)] - let mut ctext = [ - 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274, - ]; + let mut ctext = [0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274]; for i in 0..3 { let i: usize = i * 2; for _ in 0..64 { diff --git a/services/pddb/src/backend/dictionary.rs b/services/pddb/src/backend/dictionary.rs index e3f7b2705..90ff467d8 100644 --- a/services/pddb/src/backend/dictionary.rs +++ b/services/pddb/src/backend/dictionary.rs @@ -1,18 +1,19 @@ -use crate::api::*; -use super::*; - -use std::num::NonZeroU32; -use core::ops::{Deref, DerefMut}; use core::mem::size_of; -use aes_gcm_siv::Aes256GcmSiv; -#[cfg(feature="perfcounter")] -use perflib::{PERFMETA_STARTBLOCK, PERFMETA_NONE, PERFMETA_ENDBLOCK}; -#[cfg(feature="perfcounter")] -use crate::FILE_ID_SERVICES_PDDB_SRC_DICTIONARY; -use std::collections::{HashMap, BinaryHeap, BTreeSet}; -use std::io::{Result, Error, ErrorKind}; -use bitfield::bitfield; +use core::ops::{Deref, DerefMut}; use std::cmp::{Ordering, Reverse}; +use std::collections::{BTreeSet, BinaryHeap, HashMap}; +use std::io::{Error, ErrorKind, Result}; +use std::num::NonZeroU32; + +use aes_gcm_siv::Aes256GcmSiv; +use bitfield::bitfield; +#[cfg(feature = "perfcounter")] +use perflib::{PERFMETA_ENDBLOCK, PERFMETA_NONE, PERFMETA_STARTBLOCK}; + +use super::*; +use crate::api::*; +#[cfg(feature = "perfcounter")] +use crate::FILE_ID_SERVICES_PDDB_SRC_DICTIONARY; bitfield! { #[derive(Copy, Clone, PartialEq, Eq)] @@ -31,39 +32,46 @@ pub(crate) struct DictCacheEntry { pub(crate) index: NonZeroU32, /// A cache of the keys within the dictionary. If the key does not exist in /// the cache, one should consult the on-disk copy, assuming the record is clean. - pub(crate) keys: HashMap::, - /// count of total keys in the dictionary -- may be equal to or larger than the number of elements in `keys` + pub(crate) keys: HashMap, + /// count of total keys in the dictionary -- may be equal to or larger than the number of elements in + /// `keys` pub(crate) key_count: u32, - /// actual count of keys found -- this drifts from the actual key count due to write errors and ungraceful powerdowns + /// actual count of keys found -- this drifts from the actual key count due to write errors and + /// ungraceful powerdowns pub(crate) found_key_count: u32, - /// track the pool of free key indices. Wrapped in a refcell so we can work the index mechanism while updating the keys HashMap - pub(crate) free_keys: BinaryHeap::>, + /// track the pool of free key indices. Wrapped in a refcell so we can work the index mechanism while + /// updating the keys HashMap + pub(crate) free_keys: BinaryHeap>, /// hint for when to stop doing a brute-force search for the existence of a key in the disk records. - /// This field is set to the max count on a new, naive record; and set only upon a sync() or a fill() call. + /// This field is set to the max count on a new, naive record; and set only upon a sync() or a fill() + /// call. pub(crate) last_disk_key_index: u32, - /// set if synced to disk. should be cleared if the dict is modified, and/or if a subordinate key descriptor is modified. + /// set if synced to disk. should be cleared if the dict is modified, and/or if a subordinate key + /// descriptor is modified. pub(crate) clean: bool, /// track modification count pub(crate) age: u32, /// copy of the flags entry on the Dict on-disk pub(crate) flags: DictFlags, - /// small pool data. index corresponds to portion on disk. This structure is built up as the dictionary is - /// read in, and is the "master" for tracking purposes. We always fill this from index 0 and go up; if a KeySmallPool - /// goes completely empty, the entry should still exist but indicate that it's got space. Thus if a key was found allocated - /// to the Nth index position, but the previous N-1 positions are empty, the only way we could have gotten there was if we - /// had allocated lots of small data, filled up the pool to the Nth position, and then deleted all of that prior data. - /// This situation could create pathologies in the memory usage overhead of the small_pool, which until we have a "defrag" - /// operation for the small pool, we may just have to live with. + /// small pool data. index corresponds to portion on disk. This structure is built up as the dictionary + /// is read in, and is the "master" for tracking purposes. We always fill this from index 0 and go + /// up; if a KeySmallPool goes completely empty, the entry should still exist but indicate that it's + /// got space. Thus if a key was found allocated to the Nth index position, but the previous N-1 + /// positions are empty, the only way we could have gotten there was if we had allocated lots of + /// small data, filled up the pool to the Nth position, and then deleted all of that prior data. This + /// situation could create pathologies in the memory usage overhead of the small_pool, which until we + /// have a "defrag" operation for the small pool, we may just have to live with. pub(crate) small_pool: Vec, - /// free space of each small pool element. It's a collection of free space along with the Vec index of the small_pool. - /// We don't keep the KeySmallPool itself in the small_pool_free directly because it's presumed to be more common - /// that we want to index the data, than it is to need to ask the question of who has the most space free. - /// This stays in lock-step with the small_pool data because we do a .pop() to get the target vector from the small_pool_free, - /// then we modify the pool item, and then we .push() it back into the heap (or if it doesn't fit at all we allocate a new + /// free space of each small pool element. It's a collection of free space along with the Vec index of + /// the small_pool. We don't keep the KeySmallPool itself in the small_pool_free directly because + /// it's presumed to be more common that we want to index the data, than it is to need to ask the + /// question of who has the most space free. This stays in lock-step with the small_pool data because + /// we do a .pop() to get the target vector from the small_pool_free, then we modify the pool item, + /// and then we .push() it back into the heap (or if it doesn't fit at all we allocate a new /// entry and return the original item plus the new one to the heap). pub(crate) small_pool_free: BinaryHeap, /// copy of our AAD, for convenience - pub(crate) aad: Vec::, + pub(crate) aad: Vec, /// ticktimer reference, for managing atimes pub(crate) created: std::time::Instant, } @@ -74,7 +82,10 @@ impl DictCacheEntry { my_aad.push(b); } let mut free_keys = BinaryHeap::>::new(); - free_keys.push(Reverse(FreeKeyRange{start: dict.free_key_index, run: KEY_MAXCOUNT as u32 - 1 - dict.free_key_index})); + free_keys.push(Reverse(FreeKeyRange { + start: dict.free_key_index, + run: KEY_MAXCOUNT as u32 - 1 - dict.free_key_index, + })); DictCacheEntry { index: NonZeroU32::new(index as u32).unwrap(), keys: HashMap::::new(), @@ -91,13 +102,20 @@ impl DictCacheEntry { created: std::time::Instant::now(), } } + /// Populates cache entries, reporting the maximum extent of large alloc data seen so far. /// Requires a descriptor for the hardware, and our virtual to physical page mapping. - /// Does not overwrite existing cache entries, if they already exist -- only loads in ones that are missing. - /// Todo: condense routines in common with ensure_key_entry() to make it easier to maintain. + /// Does not overwrite existing cache entries, if they already exist -- only loads in ones that are + /// missing. Todo: condense routines in common with ensure_key_entry() to make it easier to maintain. /// /// ASSUMES: all small/large pools have been sync'd prior to calling this - pub fn fill(&mut self, hw: &mut PddbOs, v2p_map: &HashMap::, cipher: &Aes256GcmSiv, cleanup: bool) -> VirtAddr { + pub fn fill( + &mut self, + hw: &mut PddbOs, + v2p_map: &HashMap, + cipher: &Aes256GcmSiv, + cleanup: bool, + ) -> VirtAddr { let mut try_entry = 1; let mut key_count = 0; let mut alloc_top = VirtAddr::new(LARGE_POOL_START).unwrap(); @@ -106,16 +124,21 @@ impl DictCacheEntry { self.keys.retain(|_name, entry| entry.flags.valid()); let valid_keys = self.keys.len(); - assert!(valid_keys <= self.key_count as usize, "Inconsistency in key count. See note on basis/dict_count for logic on why this assert should be true..."); + assert!( + valid_keys <= self.key_count as usize, + "Inconsistency in key count. See note on basis/dict_count for logic on why this assert should be true..." + ); if valid_keys == self.key_count as usize { // we've got an entry for every key, so we can safely skip the deep index search let mut smallkeys_to_fill = Vec::::new(); // work around inner mutability problem by copying the names of keys to fill for (name, key) in self.keys.iter() { // #[cfg(feature="perfcounter")] - // hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, std::line!()); + // hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, + // std::line!()); if key.flags.valid() { if key.start + key.reserved > alloc_top.get() { - // if the key is within the large pool space, note its allocation for the basis overall + // if the key is within the large pool space, note its allocation for the basis + // overall alloc_top = VirtAddr::new(key.start + key.reserved).unwrap(); // nothing else needs to be done -- we don't pre-cache large key data. } else { @@ -125,10 +148,11 @@ impl DictCacheEntry { } } } - // the cache may not be very effectively used because the key order is random, but let's try anyways. + // the cache may not be very effectively used because the key order is random, but let's try + // anyways. let mut data_cache = PlaintextCache { data: None, tag: None }; for key_to_fill in smallkeys_to_fill.iter() { - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, std::line!()); self.try_fill_small_key(hw, v2p_map, cipher, &mut data_cache, key_to_fill); } @@ -137,29 +161,43 @@ impl DictCacheEntry { let mut data_cache = PlaintextCache { data: None, tag: None }; let mut errcnt = 0; while try_entry < KEY_MAXCOUNT && (cleanup || (key_count < self.key_count)) { - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, std::line!()); - // cache our decryption data -- there's about 32 entries per page, and the scan is largely linear/sequential, so this should - // be a substantial savings in effort. + // cache our decryption data -- there's about 32 entries per page, and the scan is largely + // linear/sequential, so this should be a substantial savings in effort. let req_vaddr = dict_indices_to_vaddr(self.index, try_entry); index_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(req_vaddr).unwrap()); if index_cache.data.is_none() || index_cache.tag.is_none() { - // somehow we hit a page where nothing was allocated (perhaps it was previously deleted?), or less likely, the data was corrupted. Note the isuse, skip past it. + // somehow we hit a page where nothing was allocated (perhaps it was previously deleted?), + // or less likely, the data was corrupted. Note the isuse, skip past it. if (errcnt < 4) || (errcnt % 8192 == 0) { - log::warn!("Dictionary fill: encountered unallocated page at {} in the dictionary map. {}/{}", try_entry, key_count, self.key_count); + log::warn!( + "Dictionary fill: encountered unallocated page at {} in the dictionary map. {}/{}", + try_entry, + key_count, + self.key_count + ); } errcnt += 1; try_entry += DK_PER_VPAGE; } else { - let cache_pp = index_cache.tag.as_ref().expect("PP should be in existence, it was already checked..."); - let pp = v2p_map.get(&VirtAddr::new(req_vaddr).unwrap()).expect("dictionary PP should be in existence"); + let cache_pp = index_cache + .tag + .as_ref() + .expect("PP should be in existence, it was already checked..."); + let pp = v2p_map + .get(&VirtAddr::new(req_vaddr).unwrap()) + .expect("dictionary PP should be in existence"); assert!(pp.valid(), "v2p returned an invalid page"); assert!(cache_pp.page_number() == pp.page_number(), "cache inconsistency error"); - let cache = index_cache.data.as_ref().expect("Cache should be full, it was already checked..."); + let cache = + index_cache.data.as_ref().expect("Cache should be full, it was already checked..."); let mut keydesc = KeyDescriptor::default(); let start = size_of::() + (try_entry % DK_PER_VPAGE) * DK_STRIDE; - for (&src, dst) in cache[start..start + DK_STRIDE].iter().zip(keydesc.deref_mut().iter_mut()) { + for (&src, dst) in + cache[start..start + DK_STRIDE].iter().zip(keydesc.deref_mut().iter_mut()) + { *dst = src; } if keydesc.flags.valid() { @@ -174,17 +212,18 @@ impl DictCacheEntry { data: None, atime: self.created.elapsed().as_millis() as u64, }; - let kname = std::str::from_utf8(&keydesc.name.data[..keydesc.name.len as usize]).expect("key is not valid utf-8"); - let key_exists_and_valid = - if let Some(kcache) = self.keys.get(kname) { - kcache.flags.valid() - } else { - false - }; + let kname = std::str::from_utf8(&keydesc.name.data[..keydesc.name.len as usize]) + .expect("key is not valid utf-8"); + let key_exists_and_valid = if let Some(kcache) = self.keys.get(kname) { + kcache.flags.valid() + } else { + false + }; if !key_exists_and_valid { self.keys.insert(kname.to_string(), kcache); if keydesc.start + keydesc.reserved > alloc_top.get() { - // if the key is within the large pool space, note its allocation for the basis overall + // if the key is within the large pool space, note its allocation for the + // basis overall alloc_top = VirtAddr::new(keydesc.start + keydesc.reserved).unwrap(); // nothing else needs to be done -- we don't pre-cache large key data. } else { @@ -205,40 +244,53 @@ impl DictCacheEntry { self.found_key_count = key_count; // now build the small_pool_free binary heap structure - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, std::line!()); self.rebuild_free_pool(); - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, key_count, std::line!()); } alloc_top } + /// merges the list of keys in this dict cache entry into a merge_list. /// The `merge_list` is used because keys are presented as a union across all open basis. - pub(crate) fn key_list(&mut self, hw: &mut PddbOs, v2p_map: &HashMap::, cipher: &Aes256GcmSiv, merge_list: &mut BTreeSet) { - #[cfg(feature="perfcounter")] + pub(crate) fn key_list( + &mut self, + hw: &mut PddbOs, + v2p_map: &HashMap, + cipher: &Aes256GcmSiv, + merge_list: &mut BTreeSet, + ) { + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_STARTBLOCK, 0, std::line!()); // ensure that the key cache is filled if self.keys.len() < self.key_count as usize { self.fill(hw, v2p_map, cipher, false); } - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_NONE, 0, std::line!()); for (key, kcache) in self.keys.iter() { if kcache.flags.valid() { merge_list.insert(key.to_string()); } } - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] hw.perf_entry(FILE_ID_SERVICES_PDDB_SRC_DICTIONARY, PERFMETA_ENDBLOCK, 0, std::line!()); } + /// Simply ensures we have the description of a key in cache. Only tries to load small key data. /// Required by meta-operations on the keys that operate only out of the cache. /// This shares a lot of code with the fill() routine -- we should condense the common routines /// to make this easier to maintain. Returns false if the disk was searched and no key was found; true /// if cache is hot or key was found on search. - pub(crate) fn ensure_key_entry(&mut self, hw: &mut PddbOs, v2p_map: &mut HashMap::, cipher: &Aes256GcmSiv, - name_str: &str) -> bool { + pub(crate) fn ensure_key_entry( + &mut self, + hw: &mut PddbOs, + v2p_map: &mut HashMap, + cipher: &Aes256GcmSiv, + name_str: &str, + ) -> bool { let mut data_cache = PlaintextCache { data: None, tag: None }; // only fill if the key isn't in the cache, or if the data section has been evicted let needs_fill = if let Some(entry) = self.keys.get(name_str) { @@ -265,34 +317,47 @@ impl DictCacheEntry { let mut key_count = 0; let mut index_cache = PlaintextCache { data: None, tag: None }; let mut warn_count = 0; - while try_entry < KEY_MAXCOUNT && key_count < self.key_count && try_entry <= self.last_disk_key_index as usize { - // cache our decryption data -- there's about 32 entries per page, and the scan is largely linear/sequential, so this should - // be a substantial savings in effort. - // Determine the absolute virtual address of the requested entry. It's written a little weird because - // DK_PER_VPAGE is 32, which optimizes cleanly and removes an expensive division step + while try_entry < KEY_MAXCOUNT + && key_count < self.key_count + && try_entry <= self.last_disk_key_index as usize + { + // cache our decryption data -- there's about 32 entries per page, and the scan is largely + // linear/sequential, so this should be a substantial savings in effort. + // Determine the absolute virtual address of the requested entry. It's written a little weird + // because DK_PER_VPAGE is 32, which optimizes cleanly and removes an + // expensive division step let req_vaddr = dict_indices_to_vaddr(self.index, try_entry); index_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(req_vaddr).unwrap()); if index_cache.data.is_none() || index_cache.tag.is_none() { - // this case "should not happen" in practice, because the last_disk_key_index would either be correctly set as - // short by a dict_add(), or a mount() operation would have limited the extent of the search. - // if we are hitting this, that means the last_disk_key_index operator was not managed correctly. + // this case "should not happen" in practice, because the last_disk_key_index would either + // be correctly set as short by a dict_add(), or a mount() operation + // would have limited the extent of the search. if we are hitting + // this, that means the last_disk_key_index operator was not managed correctly. if warn_count < 4 || (warn_count % 8192 == 0) { log::warn!("expensive search op"); } warn_count += 1; try_entry += DK_PER_VPAGE; } else { - let cache = index_cache.data.as_ref().expect("Cache should be full, it was already checked..."); - let cache_pp = index_cache.tag.as_ref().expect("PP should be in existence, it was already checked..."); - let pp = v2p_map.get(&VirtAddr::new(req_vaddr).unwrap()).expect("dictionary PP should be in existence"); + let cache = + index_cache.data.as_ref().expect("Cache should be full, it was already checked..."); + let cache_pp = index_cache + .tag + .as_ref() + .expect("PP should be in existence, it was already checked..."); + let pp = v2p_map + .get(&VirtAddr::new(req_vaddr).unwrap()) + .expect("dictionary PP should be in existence"); assert!(pp.valid(), "v2p returned an invalid page"); assert!(cache_pp.page_number() == pp.page_number(), "cache inconsistency error"); let mut keydesc = KeyDescriptor::default(); let start = size_of::() + (try_entry % DK_PER_VPAGE) * DK_STRIDE; - // there is 1 byte of extra padding that causes the slices of keydesc.deref_mut() to be 128 bytes long instead of 127 bytes... + // there is 1 byte of extra padding that causes the slices of keydesc.deref_mut() to be + // 128 bytes long instead of 127 bytes... keydesc.deref_mut()[..DK_STRIDE].copy_from_slice(&cache[start..start + DK_STRIDE]); - let kname = std::str::from_utf8(&keydesc.name.data[..keydesc.name.len as usize]).expect("key is not valid utf-8"); + let kname = std::str::from_utf8(&keydesc.name.data[..keydesc.name.len as usize]) + .expect("key is not valid utf-8"); if keydesc.flags.valid() { if kname == name_str { log::debug!("found {} at entry {}/{}", name_str, try_entry, start); @@ -322,13 +387,21 @@ impl DictCacheEntry { if self.keys.get(name_str).expect("inconsistent state").flags.valid() { true } else { - // not valid -- it's an erased key, but waiting to be synced to disk. Return that the key wasn't found. + // not valid -- it's an erased key, but waiting to be synced to disk. Return that the key + // wasn't found. false } } } - fn try_fill_small_key(&mut self, hw: &mut PddbOs, v2p_map: &HashMap::, cipher: &Aes256GcmSiv, - data_cache: &mut PlaintextCache, key_name: &str) { + + fn try_fill_small_key( + &mut self, + hw: &mut PddbOs, + v2p_map: &HashMap, + cipher: &Aes256GcmSiv, + data_cache: &mut PlaintextCache, + key_name: &str, + ) { let mut filled_index: Option = None; if let Some(kcache) = self.keys.get_mut(key_name) { if !kcache.flags.valid() { @@ -336,13 +409,16 @@ impl DictCacheEntry { return; } if let Some(pool_index) = small_storage_index_from_key(&kcache, self.index) { - // if the key is within the small pool space, create a bookkeeping record for it, and pre-cache its data. - // generate the index within the small pool based on the address + // if the key is within the small pool space, create a bookkeeping record for it, and + // pre-cache its data. generate the index within the small pool based on the + // address while self.small_pool.len() < pool_index + 1 { - // fill in the pool with blank entries. In general, we should have a low amount of blank entries, but - // one situation where we could get a leak is if we allocate a large amount of small data, and then delete - // all but the most recently allocated one, leaving an orphan at a high index, which is then subsequently - // treated as read-only so none of the subsequent write/update ops would have occassion to move it. This would + // fill in the pool with blank entries. In general, we should have a low amount of blank + // entries, but one situation where we could get a leak is if we + // allocate a large amount of small data, and then delete all but the + // most recently allocated one, leaving an orphan at a high index, which is then + // subsequently treated as read-only so none of the subsequent + // write/update ops would have occassion to move it. This would // need to be remedied with a "defrag" operation, but for now, we don't have that. let ksp = KeySmallPool::new(); self.small_pool.push(ksp); @@ -352,48 +428,63 @@ impl DictCacheEntry { if !ksp.contents.contains(&key_name.to_string()) { log::trace!("creating ksp entry for {}", key_name); ksp.contents.push(key_name.to_string()); - assert!(kcache.reserved >= kcache.len, "Reserved amount is less than length, this is an error!"); - assert!(kcache.reserved <= VPAGE_SIZE as u64, "Reserved amount is not appropriate for the small pool. Logic error in prior PDDB operation!"); + assert!( + kcache.reserved >= kcache.len, + "Reserved amount is less than length, this is an error!" + ); + assert!( + kcache.reserved <= VPAGE_SIZE as u64, + "Reserved amount is not appropriate for the small pool. Logic error in prior PDDB operation!" + ); log::trace!("avail: {} reserved: {}", ksp.avail, kcache.reserved); - assert!((ksp.avail as u64) >= kcache.reserved, "Total amount allocated to a small pool chunk is incorrect; suspect logic error in prior PDDB operation!"); + assert!( + (ksp.avail as u64) >= kcache.reserved, + "Total amount allocated to a small pool chunk is incorrect; suspect logic error in prior PDDB operation!" + ); ksp.avail -= kcache.reserved as u16; // note: small_pool_free is updated only after all the entries have been read in } else { // if the entry was previously created, don't update the metadata. // This might not be the best place for this note, but, as a reminder to myself, // the small pool code is written assuming you scan through all the keys and fill it - // into RAM at least once. Once you've done that, you can evict cached data to free up some - // space, but it's sort of dumb that you have to do that in the first place. This is going to - // lead to a crisis once the small pool data exceeds the available RAM -- which isn't too far from now. + // into RAM at least once. Once you've done that, you can evict cached data to free up + // some space, but it's sort of dumb that you have to do that + // in the first place. This is going to lead to a crisis once + // the small pool data exceeds the available RAM -- which isn't too far from now. // This will be tracked in issue #109. log::trace!("refilling data only for {}", key_name); } - // now grab the *data* referred to by this key. Maybe this is a "bad" idea -- this can really eat up RAM fast to hold - // all the small pool data right away, but let's give it a try and see how it works. Later on we can always skip this. - // manage a separate small cache for data blocks, under the theory that small keys tend to be packed together + // now grab the *data* referred to by this key. Maybe this is a "bad" idea -- this can + // really eat up RAM fast to hold all the small pool data right away, + // but let's give it a try and see how it works. Later on we can always skip this. + // manage a separate small cache for data blocks, under the theory that small keys tend to + // be packed together let data_vaddr = small_storage_base_vaddr_from_indices(self.index, pool_index); data_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(data_vaddr).unwrap()); if let Some(page) = data_cache.data.as_ref() { - let start_offset = size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; + let start_offset = + size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; let mut data = page[start_offset..start_offset + kcache.len as usize].to_vec(); data.reserve_exact((kcache.reserved - kcache.len) as usize); - kcache.data = Some(KeyCacheData::Small( - KeySmallData { - clean: true, - data - } - )); + kcache.data = Some(KeyCacheData::Small(KeySmallData { clean: true, data })); filled_index = Some(pool_index); } else { - log::error!("Key {}'s data region at pp: {:x?} va: {:x} is unreadable", key_name, data_cache.tag, kcache.start); + log::error!( + "Key {}'s data region at pp: {:x?} va: {:x} is unreadable", + key_name, + data_cache.tag, + kcache.start + ); } } else { log::debug!("pool is clean for key {} / index {}, skipping fill", key_name, pool_index); } } } else { - log::error!("try_fill_small_key() can only be called after the key cache entry has been allocated"); + log::error!( + "try_fill_small_key() can only be called after the key cache entry has been allocated" + ); panic!("consistency error"); } if let Some(index) = filled_index { @@ -415,31 +506,45 @@ impl DictCacheEntry { } } } + /// To be called only to re-fill key entries whose data have been evicted by a prune. - pub (crate) fn refill_small_key(&mut self, hw: &mut PddbOs, v2p_map: &HashMap::, cipher: &Aes256GcmSiv, - data_cache: &mut PlaintextCache, key_name: &str) { + pub(crate) fn refill_small_key( + &mut self, + hw: &mut PddbOs, + v2p_map: &HashMap, + cipher: &Aes256GcmSiv, + data_cache: &mut PlaintextCache, + key_name: &str, + ) { if let Some(kcache) = self.keys.get_mut(key_name) { - if kcache.flags.valid() { // invalid keys were previously deleted, and have nothing to fill + if kcache.flags.valid() { + // invalid keys were previously deleted, and have nothing to fill if let Some(pool_index) = small_storage_index_from_key(&kcache, self.index) { let ksp = &mut self.small_pool[pool_index]; - assert!(ksp.contents.contains(&key_name.to_string()), "refill called on a non-existent key. This is an illegal state."); - // now grab the *data* referred to by this key. Maybe this is a "bad" idea -- this can really eat up RAM fast to hold - // all the small pool data right away, but let's give it a try and see how it works. Later on we can always skip this. - // manage a separate small cache for data blocks, under the theory that small keys tend to be packed together + assert!( + ksp.contents.contains(&key_name.to_string()), + "refill called on a non-existent key. This is an illegal state." + ); + // now grab the *data* referred to by this key. Maybe this is a "bad" idea -- this can + // really eat up RAM fast to hold all the small pool data right away, + // but let's give it a try and see how it works. Later on we can always skip this. + // manage a separate small cache for data blocks, under the theory that small keys tend to + // be packed together let data_vaddr = small_storage_base_vaddr_from_indices(self.index, pool_index); data_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(data_vaddr).unwrap()); if let Some(page) = data_cache.data.as_ref() { - let start_offset = size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; + let start_offset = + size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; let mut data = page[start_offset..start_offset + kcache.len as usize].to_vec(); data.reserve_exact((kcache.reserved - kcache.len) as usize); - kcache.data = Some(KeyCacheData::Small( - KeySmallData { - clean: true, - data - } - )); + kcache.data = Some(KeyCacheData::Small(KeySmallData { clean: true, data })); } else { - log::error!("Key {}'s data region at pp: {:x?} va: {:x} is unreadable", key_name, data_cache.tag, kcache.start); + log::error!( + "Key {}'s data region at pp: {:x?} va: {:x} is unreadable", + key_name, + data_cache.tag, + kcache.start + ); } } } @@ -448,9 +553,11 @@ impl DictCacheEntry { panic!("consistency error"); } } + /// Update a key entry. If the key does not already exist, it will create a new one. /// - /// Assume: the caller has called ensure_fast_space_alloc() to make sure there is sufficient space for the inserted key before calling. + /// Assume: the caller has called ensure_fast_space_alloc() to make sure there is sufficient space for the + /// inserted key before calling. /// /// `key_update` will write `data` starting at `offset`, and will grow the record if data /// is larger than the current allocation. If `truncate` is false, the existing data past the end of @@ -462,24 +569,36 @@ impl DictCacheEntry { /// `truncate` records are particularly useful for updating very large file streams, which can't be /// held entirely in RAM. /// - /// Note: it is up to the higher level Basis disambiguation logic to decide the cross-basis update policy: it - /// could either be to update only the dictionary in the latest open basis, update all dictionaries, or update a - /// specific dictionary in a named basis. In all of these cases, the Basis resolver will have had to find the - /// correct DictCacheEntry and issue the `key_update` to it; for multiple updates, then multiple calls to - /// multiple DictCacheEntry are required. - pub fn key_update(&mut self, hw: &mut PddbOs, v2p_map: &mut HashMap::, cipher: &Aes256GcmSiv, - name: &str, data: &[u8], offset: usize, alloc_hint:Option, truncate: bool, large_alloc_ptr: PageAlignedVa) -> Result { + /// Note: it is up to the higher level Basis disambiguation logic to decide the cross-basis update policy: + /// it could either be to update only the dictionary in the latest open basis, update all + /// dictionaries, or update a specific dictionary in a named basis. In all of these cases, the Basis + /// resolver will have had to find the correct DictCacheEntry and issue the `key_update` to it; for + /// multiple updates, then multiple calls to multiple DictCacheEntry are required. + pub fn key_update( + &mut self, + hw: &mut PddbOs, + v2p_map: &mut HashMap, + cipher: &Aes256GcmSiv, + name: &str, + data: &[u8], + offset: usize, + alloc_hint: Option, + truncate: bool, + large_alloc_ptr: PageAlignedVa, + ) -> Result { self.age = self.age.saturating_add(1); self.clean = false; if self.ensure_key_entry(hw, v2p_map, cipher, name) { let kcache = self.keys.get_mut(name).expect("Entry was assured, but then not there!"); kcache.set_atime(self.created.elapsed().as_millis() as u64); kcache.clean = false; - // the update isn't going to fit in the reserved space, remove it, and re-insert it with an entirely new entry. + // the update isn't going to fit in the reserved space, remove it, and re-insert it with an + // entirely new entry. if kcache.reserved < (data.len() + offset) as u64 { if kcache.start < SMALL_POOL_END { // this started life as a small key. the algorithm is to remove and retry upon extend. - // allocate a new vector that contains the *entire* data contents (not just the updated portion), and re-insert it + // allocate a new vector that contains the *entire* data contents (not just the updated + // portion), and re-insert it let mut update_data = Vec::::with_capacity(data.len() + offset); if let Some(KeyCacheData::Small(old_data)) = &kcache.data { for &b in &old_data.data { @@ -489,35 +608,67 @@ impl DictCacheEntry { log::error!("expected a small key, but the data type was incorrect or not in cache"); panic!("expected a small key, but the data type was incorrect or not in cache"); } - // extend the vector out to the expected length, so the indexed slice in the next step doesn't panic + // extend the vector out to the expected length, so the indexed slice in the next step + // doesn't panic while update_data.len() < offset + data.len() { update_data.push(0); } - for (&src, dst) in data.iter().zip(update_data[offset..].iter_mut()) { *dst = src }; + for (&src, dst) in data.iter().zip(update_data[offset..].iter_mut()) { + *dst = src + } log::debug!("update/extend: removing {}", name); // now remove the old key entirely self.key_remove(hw, v2p_map, cipher, name, false); self.sync_small_pool(hw, v2p_map, cipher); - if update_data.len() > 4 { // just make sure that this log call doesn't fail on an index violation... - log::debug!("update/extend: re-adding {} with data len {}: {:x?}...", name, update_data.len(), &update_data[..4]); + if update_data.len() > 4 { + // just make sure that this log call doesn't fail on an index violation... + log::debug!( + "update/extend: re-adding {} with data len {}: {:x?}...", + name, + update_data.len(), + &update_data[..4] + ); } - // and re-add it with the extended data; if it's no longer a small key after this, it'll be handled inside this call. - return self.key_update(hw, v2p_map, cipher, name, &update_data, 0, alloc_hint, truncate, large_alloc_ptr); + // and re-add it with the extended data; if it's no longer a small key after this, it'll + // be handled inside this call. + return self.key_update( + hw, + v2p_map, + cipher, + name, + &update_data, + 0, + alloc_hint, + truncate, + large_alloc_ptr, + ); } else { - // large data sets will need more physical pages to be allocated for the new file length. It's a hard error - // if the requested size goes beyond the pre-allocated virtual memory space limit. + // large data sets will need more physical pages to be allocated for the new file length. + // It's a hard error if the requested size goes beyond the + // pre-allocated virtual memory space limit. if (offset + data.len()) as u64 > LARGE_FILE_MAX_SIZE { - log::error!("Requested file update would exceed our file size limit. Asked: {}, limit: {}", offset + data.len(), LARGE_FILE_MAX_SIZE); + log::error!( + "Requested file update would exceed our file size limit. Asked: {}, limit: {}", + offset + data.len(), + LARGE_FILE_MAX_SIZE + ); panic!("Updated file size is greater than the large file size limit."); } - assert!((kcache.start + kcache.reserved) % VPAGE_SIZE as u64 == 0, "large space allocation rules were violated"); - let new_reservation_abs_addr = PageAlignedVa::from(kcache.start + offset as u64 + data.len() as u64).as_u64(); - for vpage_addr in ( - (kcache.start + kcache.reserved)..new_reservation_abs_addr - ).step_by(VPAGE_SIZE) { - // ensures that a physical page entry exists for every new virtual address required by the extended key + assert!( + (kcache.start + kcache.reserved) % VPAGE_SIZE as u64 == 0, + "large space allocation rules were violated" + ); + let new_reservation_abs_addr = + PageAlignedVa::from(kcache.start + offset as u64 + data.len() as u64).as_u64(); + for vpage_addr in + ((kcache.start + kcache.reserved)..new_reservation_abs_addr).step_by(VPAGE_SIZE) + { + // ensures that a physical page entry exists for every new virtual address required by + // the extended key v2p_map.entry(VirtAddr::new(vpage_addr).unwrap()).or_insert_with(|| { - let mut ap = hw.try_fast_space_alloc().expect("No free space to allocate additional large key storage"); + let mut ap = hw + .try_fast_space_alloc() + .expect("No free space to allocate additional large key storage"); ap.set_valid(true); ap }); @@ -525,7 +676,17 @@ impl DictCacheEntry { kcache.reserved = new_reservation_abs_addr - kcache.start; // convert absolute address to an actual length kcache.clean = false; // now retry the call, with the new physical page reservations in place - return self.key_update(hw, v2p_map, cipher, name, data, offset, alloc_hint, truncate, large_alloc_ptr); + return self.key_update( + hw, + v2p_map, + cipher, + name, + data, + offset, + alloc_hint, + truncate, + large_alloc_ptr, + ); } } // the key exists, *and* there's sufficient space for the data @@ -533,12 +694,14 @@ impl DictCacheEntry { log::debug!("doing data update of {}", name); kcache.age = kcache.age.saturating_add(1); kcache.clean = false; - // it's a small key; note that we didn't consult the *size* of the key to determine its pool type: - // small-sized keys can still end up in the "large" space if the small pool allocation is exhausted. + // it's a small key; note that we didn't consult the *size* of the key to determine its pool + // type: small-sized keys can still end up in the "large" space if the small + // pool allocation is exhausted. match kcache.data.as_mut() { Some(KeyCacheData::Small(cache_data)) => { cache_data.clean = false; - // grow the data cache to accommodate the necessary length; this should be efficient because we reserved space when the vector was allocated + // grow the data cache to accommodate the necessary length; this should be efficient + // because we reserved space when the vector was allocated while cache_data.data.len() < data.len() + offset { cache_data.data.push(0); } @@ -552,28 +715,31 @@ impl DictCacheEntry { None => { // cache data was evicted, fill it assert!(kcache.flags.valid(), "Entry to be filled should be valid"); - let pool_index = small_storage_index_from_key(&kcache, self.index).expect("pool should be valid"); + let pool_index = + small_storage_index_from_key(&kcache, self.index).expect("pool should be valid"); let ksp = &mut self.small_pool[pool_index]; if !ksp.contents.contains(&name.to_string()) { - log::warn!("illegal state: key_update fill key pool contents: {:?}, missing {}", ksp.contents, name); + log::warn!( + "illegal state: key_update fill key pool contents: {:?}, missing {}", + ksp.contents, + name + ); panic!("Entry to fill isn't in the expected pool"); } let data_vaddr = small_storage_base_vaddr_from_indices(self.index, pool_index); let mut data_cache = PlaintextCache { data: None, tag: None }; data_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(data_vaddr).unwrap()); if let Some(page) = data_cache.data.as_ref() { - let start_offset = size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; + let start_offset = + size_of::() + (kcache.start % VPAGE_SIZE as u64) as usize; let mut data = page[start_offset..start_offset + kcache.len as usize].to_vec(); data.reserve_exact((kcache.reserved - kcache.len) as usize); - kcache.data = Some(KeyCacheData::Small( - KeySmallData { - clean: true, - data - } - )); + kcache.data = Some(KeyCacheData::Small(KeySmallData { clean: true, data })); } else { - let error = - format!("Key {}'s data region at pp: {:x?} va: {:x} is unreadable", name, data_cache.tag, kcache.start); + let error = format!( + "Key {}'s data region at pp: {:x?} va: {:x} is unreadable", + name, data_cache.tag, kcache.start + ); panic!("{}", error); } } @@ -603,50 +769,87 @@ impl DictCacheEntry { // 1. handle unaligned start offsets let mut written: usize = 0; if ((kcache.start + offset as u64 + written as u64) % VPAGE_SIZE as u64) != 0 { - let start_vpage_addr = ((kcache.start + offset as u64) / VPAGE_SIZE as u64) * VPAGE_SIZE as u64; - let pp = v2p_map.get(&VirtAddr::new(start_vpage_addr).unwrap()).expect("large key data allocation missing"); + let start_vpage_addr = + ((kcache.start + offset as u64) / VPAGE_SIZE as u64) * VPAGE_SIZE as u64; + let pp = v2p_map + .get(&VirtAddr::new(start_vpage_addr).unwrap()) + .expect("large key data allocation missing"); assert!(pp.valid(), "v2p returned an invalid page"); let mut pt_data = match hw.data_decrypt_page(&cipher, &self.aad, pp) { Some(data) => data, None => { // this case is triggered by the following circumstance: // - we reserved data that includes this current page - // - up until now, we've only written data into the previous page (so this page is not initialized -- it's garbage) - // - we just issued an update that causes the data to touch this page for the first time + // - up until now, we've only written data into the previous page (so this + // page is not initialized -- it's garbage) + // - we just issued an update that causes the data to touch this page for the + // first time // in response to this, we allocate a fresh page of 0's. - log::debug!("Reserved and uninitialized page encountered updating large block: {} {:x}..{}->{}; update @{}..{}", - name, kcache.start, kcache.len, kcache.reserved, offset, data.len()); + log::debug!( + "Reserved and uninitialized page encountered updating large block: {} {:x}..{}->{}; update @{}..{}", + name, + kcache.start, + kcache.len, + kcache.reserved, + offset, + data.len() + ); let mut d = vec![0u8; VPAGE_SIZE + size_of::()]; - for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes().iter().zip(d[..size_of::()].iter_mut()) { + for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE) + .to_le_bytes() + .iter() + .zip(d[..size_of::()].iter_mut()) + { *dst = src; } d } }; if offset > 0 { - log::trace!("patching offset {}, total length {}, data length {}", offset % VPAGE_SIZE, kcache.len, data.len()); + log::trace!( + "patching offset {}, total length {}, data length {}", + offset % VPAGE_SIZE, + kcache.len, + data.len() + ); } - for (&src, dst) in data[written..].iter().zip(pt_data[size_of::() + (offset % VPAGE_SIZE)..].iter_mut()) { + for (&src, dst) in data[written..] + .iter() + .zip(pt_data[size_of::() + (offset % VPAGE_SIZE)..].iter_mut()) + { *dst = src; written += 1; } if written < data.len() { - assert!((kcache.start + offset as u64 + written as u64) % VPAGE_SIZE as u64 == 0, "alignment algorithm failed"); + assert!( + (kcache.start + offset as u64 + written as u64) % VPAGE_SIZE as u64 == 0, + "alignment algorithm failed" + ); } hw.data_encrypt_and_patch_page(cipher, &self.aad, &mut pt_data, &pp); } // 2. do the rest while written < data.len() { - let vpage_addr = ((kcache.start + written as u64 + offset as u64) / VPAGE_SIZE as u64) * VPAGE_SIZE as u64; - let pp = v2p_map.get(&VirtAddr::new(vpage_addr).unwrap()).expect("large key data allocation missing"); + let vpage_addr = ((kcache.start + written as u64 + offset as u64) + / VPAGE_SIZE as u64) + * VPAGE_SIZE as u64; + let pp = v2p_map + .get(&VirtAddr::new(vpage_addr).unwrap()) + .expect("large key data allocation missing"); assert!(pp.valid(), "v2p returned an invalid page"); if data.len() - written >= VPAGE_SIZE { // overwrite whole pages without decryption let mut block = [0u8; VPAGE_SIZE + size_of::()]; - for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes().iter().zip(block[..size_of::()].iter_mut()) { + for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE) + .to_le_bytes() + .iter() + .zip(block[..size_of::()].iter_mut()) + { *dst = src; } - for (&src, dst) in data[written..].iter().zip(block[size_of::()..].iter_mut()) { + for (&src, dst) in + data[written..].iter().zip(block[size_of::()..].iter_mut()) + { *dst = src; written += 1; } @@ -654,7 +857,9 @@ impl DictCacheEntry { } else { // handle partial trailing pages if let Some(pt_data) = hw.data_decrypt_page(&cipher, &self.aad, pp).as_mut() { - for (&src, dst) in data[written..].iter().zip(pt_data[size_of::()..].iter_mut()) { + for (&src, dst) in + data[written..].iter().zip(pt_data[size_of::()..].iter_mut()) + { *dst = src; written += 1; } @@ -662,10 +867,16 @@ impl DictCacheEntry { } else { // page didn't exist, initialize it with 0's and merge the tail end. let mut pt_data = [0u8; VPAGE_SIZE + size_of::()]; - for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes().iter().zip(pt_data[..size_of::()].iter_mut()) { + for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE) + .to_le_bytes() + .iter() + .zip(pt_data[..size_of::()].iter_mut()) + { *dst = src; } - for (&src, dst) in data[written..].iter().zip(pt_data[size_of::()..].iter_mut()) { + for (&src, dst) in + data[written..].iter().zip(pt_data[size_of::()..].iter_mut()) + { *dst = src; written += 1; } @@ -674,17 +885,23 @@ impl DictCacheEntry { } } log::trace!("data written: {}, data requested to write: {}", written, data.len()); - assert!(written == data.len(), "algorithm problem -- didn't write all the data we thought we would"); + assert!( + written == data.len(), + "algorithm problem -- didn't write all the data we thought we would" + ); // 3. truncate or extend // check if we grew the length; extend the length by exactly enough if so. if kcache.len < (data.len() + offset) as u64 { kcache.len = (data.len() + offset) as u64; } else if truncate { - // discard all whole pages after written+offset, and reset the reserved field to the smaller size. + // discard all whole pages after written+offset, and reset the reserved field to the + // smaller size. log::trace!("PageAligned VA components: {}, {}", written, offset); let vpage_end_offset = PageAlignedVa::from((written + offset) as u64); if (vpage_end_offset.as_u64() - kcache.start) > kcache.reserved { - for vpage in (vpage_end_offset.as_u64()..kcache.start + kcache.reserved).step_by(VPAGE_SIZE) { + for vpage in (vpage_end_offset.as_u64()..kcache.start + kcache.reserved) + .step_by(VPAGE_SIZE) + { if let Some(pp) = v2p_map.get_mut(&VirtAddr::new(vpage).unwrap()) { assert!(pp.valid(), "v2p returned an invalid page"); log::trace!("fast_space_free key_update {} before", pp.journal()); @@ -702,20 +919,26 @@ impl DictCacheEntry { } else { // key does not exist (or was previously erased) -- create one or replace the erased one. // try to fit the key in the small pool first - if ((data.len() + offset) < SMALL_CAPACITY) && (alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) < SMALL_CAPACITY) { + if ((data.len() + offset) < SMALL_CAPACITY) + && (alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) < SMALL_CAPACITY) + { log::debug!("creating small key {}", name); - // handle the case that we're a brand new dictionary and no small keys have ever been stored before. + // handle the case that we're a brand new dictionary and no small keys have ever been stored + // before. if self.small_pool.len() == 0 { self.small_pool.push(KeySmallPool::new()); self.rebuild_free_pool(); } - let pool_candidate = self.small_pool_free.pop().expect("Free pool was allocated & rebuilt, but still empty."); + let pool_candidate = + self.small_pool_free.pop().expect("Free pool was allocated & rebuilt, but still empty."); let mut reservation = if alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) > data.len() + offset { alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) } else { data.len() + offset }; - if reservation == 0 { // this case happens if someone tries to just create an empty key entry without giving an alloc hint + if reservation == 0 { + // this case happens if someone tries to just create an empty key entry without giving an + // alloc hint reservation = 1; // at least make a reservation for 1 byte of data } let index = if pool_candidate.avail as usize >= reservation { @@ -725,7 +948,8 @@ impl DictCacheEntry { ksp.avail -= reservation as u16; ksp.clean = false; log::debug!("ksp.clean = false {}", name); - self.small_pool_free.push(KeySmallPoolOrd { avail: ksp.avail, index: pool_candidate.index }); + self.small_pool_free + .push(KeySmallPoolOrd { avail: ksp.avail, index: pool_candidate.index }); pool_candidate.index } else { self.small_pool_free.push(pool_candidate); @@ -736,8 +960,10 @@ impl DictCacheEntry { ksp.clean = false; log::debug!("ksp.clean = false {}", name); // update the free pool with the current candidate - // we don't subtract 1 from len because we're about to push the ksp onto the end of the small_pool, consuming it - self.small_pool_free.push(KeySmallPoolOrd { avail: ksp.avail, index: self.small_pool.len() }); + // we don't subtract 1 from len because we're about to push the ksp onto the end of the + // small_pool, consuming it + self.small_pool_free + .push(KeySmallPoolOrd { avail: ksp.avail, index: self.small_pool.len() }); self.small_pool.push(ksp); // the actual location is at len-1 now because we have done the push self.small_pool.len() - 1 @@ -769,10 +995,7 @@ impl DictCacheEntry { age: 0, descriptor_index, clean: false, - data: Some(KeyCacheData::Small(KeySmallData{ - clean: false, - data: alloc_data - })), + data: Some(KeyCacheData::Small(KeySmallData { clean: false, data: alloc_data })), atime: self.created.elapsed().as_millis() as u64, }; self.keys.insert(name.to_string(), kcache); @@ -780,8 +1003,8 @@ impl DictCacheEntry { } else { log::debug!("creating large key: {}", data.len()); // it didn't fit in the small pool, stick it in the big pool. - let reservation = PageAlignedVa::from( - if alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) > data.len() + offset { + let reservation = + PageAlignedVa::from(if alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) > data.len() + offset { alloc_hint.unwrap_or(DEFAULT_ALLOC_HINT) } else { data.len() + offset @@ -807,40 +1030,64 @@ impl DictCacheEntry { self.keys.insert(name.to_string(), kcache); self.key_count += 1; // 1. allocate all the pages up to the reservation limit - for vpage_addr in (large_alloc_ptr.as_u64()..large_alloc_ptr.as_u64() + reservation.as_u64()).step_by(VPAGE_SIZE) { - let pp = hw.try_fast_space_alloc().ok_or(Error::new(ErrorKind::OutOfMemory, "couldn't allocate memory for large key"))?; + for vpage_addr in (large_alloc_ptr.as_u64()..large_alloc_ptr.as_u64() + reservation.as_u64()) + .step_by(VPAGE_SIZE) + { + let pp = hw.try_fast_space_alloc().ok_or(Error::new( + ErrorKind::OutOfMemory, + "couldn't allocate memory for large key", + ))?; assert!(pp.valid(), "didn't receive a valid page in large space alloc"); log::debug!("pp alloc v{:x}->p{:x?}", vpage_addr, pp); v2p_map.insert(VirtAddr::new(vpage_addr).unwrap(), pp); } - // 2. Recurse. Now, the key should exist, and it should go through the "write the data out" section of the algorithm. - // note that the alloc pointer at this point sets the ultimate limit for the large file size (32GiB as of writing). - return self.key_update(hw, v2p_map, cipher, name, data, offset, alloc_hint, truncate, - large_alloc_ptr + PageAlignedVa::from(LARGE_FILE_MAX_SIZE)); + // 2. Recurse. Now, the key should exist, and it should go through the "write the data out" + // section of the algorithm. + // note that the alloc pointer at this point sets the ultimate limit for the large file size + // (32GiB as of writing). + return self.key_update( + hw, + v2p_map, + cipher, + name, + data, + offset, + alloc_hint, + truncate, + large_alloc_ptr + PageAlignedVa::from(LARGE_FILE_MAX_SIZE), + ); } } Ok(large_alloc_ptr) } + #[allow(dead_code)] - pub fn key_contains(&self, name: &str) -> bool { - self.keys.contains_key(&String::from(name)) - } + pub fn key_contains(&self, name: &str) -> bool { self.keys.contains_key(&String::from(name)) } fn rebuild_free_pool(&mut self) { self.small_pool_free.clear(); for (index, ksp) in self.small_pool.iter().enumerate() { - self.small_pool_free.push(KeySmallPoolOrd{index, avail: ksp.avail}) + self.small_pool_free.push(KeySmallPoolOrd { index, avail: ksp.avail }) } } + /// Used to remove a key from the dictionary. If you call it with a non-existent key, /// the routine has no effect, and does not report an error. Small keys are not immediately /// overwritten in paranoid mode, but large keys are. - pub fn key_remove(&mut self, hw: &mut PddbOs, v2p_map: &mut HashMap::, cipher: &Aes256GcmSiv, - name_str: &str, paranoid: bool) { + pub fn key_remove( + &mut self, + hw: &mut PddbOs, + v2p_map: &mut HashMap, + cipher: &Aes256GcmSiv, + name_str: &str, + paranoid: bool, + ) { log::debug!("removing key {}", name_str); if paranoid { // large records are paranoid-erased, by default, because of the pool-reuse problem. - unimplemented!("Paranoid erase for small records not yet implemented. Calling sync after an update, however, effectively does a paranoid erase."); + unimplemented!( + "Paranoid erase for small records not yet implemented. Calling sync after an update, however, effectively does a paranoid erase." + ); } // this call will check the disk to see if there's key data that's not in cache. if self.ensure_key_entry(hw, v2p_map, cipher, name_str) { @@ -864,25 +1111,31 @@ impl DictCacheEntry { if let Some(small_index) = small_storage_index_from_key(kcache, self.index) { // handle the small pool case let ksp = &mut self.small_pool[small_index]; - let err_static = format!("Small pool did not contain the element we expected: {}, len: {}", &name, kcache.len); + let err_static = format!( + "Small pool did not contain the element we expected: {}, len: {}", + &name, kcache.len + ); log::debug!("ksp swap_remove({}) flags: {:?}", name, kcache.flags); - ksp.contents.swap_remove(ksp.contents.iter().position(|s| *s == name) - .expect(&err_static)); + ksp.contents + .swap_remove(ksp.contents.iter().position(|s| *s == name).expect(&err_static)); assert!(kcache.reserved <= SMALL_CAPACITY as u64, "error in small key entry size"); ksp.avail += kcache.reserved as u16; assert!(ksp.avail <= SMALL_CAPACITY as u16, "bookkeeping error in small pool capacity"); ksp.clean = false; // this will also effectively cause the record to be deleted on disk once the small pool data is synchronized need_rebuild = true; - } else { // handle the large pool case // mark the entry as invalid and dirty; virtual space is one huge memory leak... - // ...but we remove the virtual pages from the page pool, effectively reclaiming the physical space. + // ...but we remove the virtual pages from the page pool, effectively reclaiming the + // physical space. for vpage in kcache.large_pool_vpages() { if let Some(pp) = v2p_map.get_mut(&vpage) { assert!(pp.valid(), "v2p returned an invalid page"); - { // this slows things down but it prevents data from leaking back into other basis data structures when the sector is re-allocated - // in other words, i think it's probably very unsafe to not always secure-erase large data as it's de-allocated. + { + // this slows things down but it prevents data from leaking back into other + // basis data structures when the sector is re-allocated + // in other words, i think it's probably very unsafe to not always + // secure-erase large data as it's de-allocated. let mut noise = [0u8; PAGE_SIZE]; hw.trng_slice(&mut noise); hw.patch_data(&noise, pp.page_number() * PAGE_SIZE as u32); @@ -911,21 +1164,24 @@ impl DictCacheEntry { // at this point: // - in-memory representation will return an entry, but with its valid flag set to false. // - disk still contains a key entry that claims we have a valid key - // a call to sync is necessary to completely flush things, but, we don't sync every time we remove because it's inefficient. + // a call to sync is necessary to completely flush things, but, we don't sync every time we remove + // because it's inefficient. } else { log::debug!("key_remove() key does not exist: {}", name_str); } // if there's no key....we're done! } + /// used to remove a key from the dictionary, syncing 0's to the disk in the key's place - /// sort of less relevant now that the large keys have a paranoid mode; probably this routine should actually - /// be a higher-level function that catches the paranoid request and does an "update" of 0's to the key - /// then does a disk sync and then calls remove + /// sort of less relevant now that the large keys have a paranoid mode; probably this routine should + /// actually be a higher-level function that catches the paranoid request and does an "update" of 0's + /// to the key then does a disk sync and then calls remove pub fn key_erase(&mut self, _name: &str) { unimplemented!(); } - /// estimates the amount of space needed to sync the dict cache. Pass this to ensure_fast_space_alloc() before calling a sync. - /// estimate can be inaccurate under pathological allocation conditions. + + /// estimates the amount of space needed to sync the dict cache. Pass this to ensure_fast_space_alloc() + /// before calling a sync. estimate can be inaccurate under pathological allocation conditions. pub(crate) fn alloc_estimate_small(&self) -> usize { let mut data_estimate = 0; let mut index_estimate = 0; @@ -948,50 +1204,75 @@ impl DictCacheEntry { }; (data_estimate / VPAGE_SIZE) + 1 + index_req } + /// Synchronize a given small pool key to disk. Make sure there is adequate space in the fastspace /// pool by using self.alloc_estimate_small + hw.ensure_fast_space_alloc. Following this call, /// one should call `dict_sync` and `pt_sync` as soon as possible to keep everything consistent. /// /// Observation: given the dictionary index and the small key pool index, we know exactly /// the virtual address of the data pool. - pub(crate) fn sync_small_pool(&mut self, hw: &mut PddbOs, v2p_map: &mut HashMap::, cipher: &Aes256GcmSiv) -> bool { + pub(crate) fn sync_small_pool( + &mut self, + hw: &mut PddbOs, + v2p_map: &mut HashMap, + cipher: &Aes256GcmSiv, + ) -> bool { let mut data_cache = PlaintextCache { data: None, tag: None }; for (index, entry) in self.small_pool.iter_mut().enumerate() { if !entry.clean { - let pool_vaddr = VirtAddr::new(small_storage_base_vaddr_from_indices(self.index, index)).unwrap(); + let pool_vaddr = + VirtAddr::new(small_storage_base_vaddr_from_indices(self.index, index)).unwrap(); if !hw.fast_space_has_pages(1) { - return false + return false; } - let pp = v2p_map.entry(pool_vaddr).or_insert_with(|| { - let mut ap = hw.try_fast_space_alloc().expect("No free space to allocate small key storage"); - ap.set_valid(true); - ap - }).clone(); + let pp = v2p_map + .entry(pool_vaddr) + .or_insert_with(|| { + let mut ap = + hw.try_fast_space_alloc().expect("No free space to allocate small key storage"); + ap.set_valid(true); + ap + }) + .clone(); assert!(pp.valid(), "v2p returned an invalid page"); // WARNING - we don't read back the journal number before loading data into the page! - // we /could/ do that, but it incurs an expensive full-page decryption when we plan to nuke all the data. - // I'm a little worried the implementation as-is is going to be too slow, so let's try the abbreviated method - // and see how it fares. This incurs a risk that we lose data if we have a power outage or panic just after + // we /could/ do that, but it incurs an expensive full-page decryption when we plan to nuke + // all the data. I'm a little worried the implementation as-is is going to be + // too slow, so let's try the abbreviated method and see how it fares. This + // incurs a risk that we lose data if we have a power outage or panic just after // the page is erased but before all the PTEs and pointers are synced. // // If it turns out this is an issue, here's how you'd fix it: // 1. decrypt the old page (if it exists) and extract the journal rev - // 2. de-allocate the old phys page, returning it to the fastspace pool; it'll likely not be returned on the next step + // 2. de-allocate the old phys page, returning it to the fastspace pool; it'll likely not be + // returned on the next step // 3. allocate a new page // 4. write data to the new page (which increments the old journal rev) // 5. sync the page tables // This implementation just skips to step 3. let mut page = [0u8; VPAGE_SIZE + size_of::()]; - for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes().iter().zip(page[..size_of::()].iter_mut()) { + for (&src, dst) in (hw.trng_u32() % JOURNAL_RAND_RANGE) + .to_le_bytes() + .iter() + .zip(page[..size_of::()].iter_mut()) + { *dst = src; } let mut pool_offset = 0; // visit the entries in arbitrary order, but back them in optimally tightly packed for key_name in &entry.contents { let kcache = self.keys.get_mut(key_name).expect("data record without index"); - if kcache.flags.valid() { // only sync valid keys, not ones that were marked for deletion but not yet synced. - log::debug!("sync {}/{}:0x{:x}..{}->{}", key_name, index, kcache.start, kcache.len, kcache.reserved); + if kcache.flags.valid() { + // only sync valid keys, not ones that were marked for deletion but not yet synced. + log::debug!( + "sync {}/{}:0x{:x}..{}->{}", + key_name, + index, + kcache.start, + kcache.len, + kcache.reserved + ); let old_start = kcache.start; kcache.start = pool_vaddr.get() + pool_offset as u64; kcache.age = kcache.age.saturating_add(1); @@ -1001,30 +1282,46 @@ impl DictCacheEntry { match kcache.data.as_mut() { Some(KeyCacheData::Small(data)) => { data.clean = true; - page[size_of::() + pool_offset .. - size_of::() + pool_offset + kcache.len as usize] + page[size_of::() + pool_offset + ..size_of::() + pool_offset + kcache.len as usize] .copy_from_slice(&data.data[..kcache.len as usize]); } None => { // the entry was pruned and must be loaded - // fill() will only do the expensive decryption operation once per page, and refer to the cached value on other calls + // fill() will only do the expensive decryption operation once per page, and + // refer to the cached value on other calls log::debug!("Small key sync filling {}", key_name); - data_cache.fill(hw, v2p_map, cipher, &self.aad, VirtAddr::new(pool_vaddr.get()).unwrap()); + data_cache.fill( + hw, + v2p_map, + cipher, + &self.aad, + VirtAddr::new(pool_vaddr.get()).unwrap(), + ); if let Some(old_page) = data_cache.data.as_ref() { - let start_offset = size_of::() + (old_start % VPAGE_SIZE as u64) as usize; - // the delta of len-reserved bytes is assumed zeroed by the original space allocator; we just copy that - // here. However, if this assumption is broken, we could have leakage between keys in the reserved area!!! - page[size_of::() + pool_offset .. - size_of::() + pool_offset + kcache.len as usize] - .copy_from_slice(&old_page[start_offset..start_offset + kcache.len as usize]); + let start_offset = + size_of::() + (old_start % VPAGE_SIZE as u64) as usize; + // the delta of len-reserved bytes is assumed zeroed by the original space + // allocator; we just copy that + // here. However, if this assumption is broken, we could have leakage + // between keys in the reserved area!!! + page[size_of::() + pool_offset + ..size_of::() + pool_offset + kcache.len as usize] + .copy_from_slice( + &old_page[start_offset..start_offset + kcache.len as usize], + ); // now take the previous data and shove it back in our cache - let data = old_page[start_offset..start_offset + kcache.len as usize].to_vec(); - kcache.data = Some(KeyCacheData::Small(KeySmallData { - clean: true, - data, - })); + let data = + old_page[start_offset..start_offset + kcache.len as usize].to_vec(); + kcache.data = + Some(KeyCacheData::Small(KeySmallData { clean: true, data })); } else { - log::error!("Key {}'s data region at pp: {:x?} va: {:x} is unreadable", key_name, data_cache.tag, kcache.start); + log::error!( + "Key {}'s data region at pp: {:x?} va: {:x} is unreadable", + key_name, + data_cache.tag, + kcache.start + ); } } _ => { @@ -1044,13 +1341,13 @@ impl DictCacheEntry { log::debug!("Key pool[{}] was clean, no need to sync: {:?}", index, entry.contents); } } - // we now have a bunch of dirty kcache entries. You should call `dict_sync` shortly after this to synchronize those entries to disk. + // we now have a bunch of dirty kcache entries. You should call `dict_sync` shortly after this to + // synchronize those entries to disk. true } /// No data cache to flush yet...large pool caches not implemented! - pub(crate) fn sync_large_pool(&self) { - } + pub(crate) fn sync_large_pool(&self) {} /// Finds the next available slot to store the key metadata (not the data itself). It also /// does bookkeeping to bound brute-force searches for keys within the dictionary's index space. @@ -1058,15 +1355,11 @@ impl DictCacheEntry { if let Some(free_key) = self.free_keys.pop() { let index = free_key.0.start; if free_key.0.run > 0 { - self.free_keys.push( - Reverse(FreeKeyRange { - start: index + 1, - run: free_key.0.run - 1, - }) - ) + self.free_keys.push(Reverse(FreeKeyRange { start: index + 1, run: free_key.0.run - 1 })) } if index >= self.last_disk_key_index { - // if the new index is outside the currently known set, raise the search extent for the brute-force search + // if the new index is outside the currently known set, raise the search extent for the + // brute-force search self.last_disk_key_index = index + 1; } NonZeroU32::new(index as u32) @@ -1075,66 +1368,84 @@ impl DictCacheEntry { None } } + /// Returns a key's metadata storage to the index pool. pub(crate) fn put_free_key_index(&mut self, index: u32) { let free_keys = std::mem::replace(&mut self.free_keys, BinaryHeap::>::new()); let mut free_key_vec = free_keys.into_sorted_vec(); // I know what you're going to say! It'd be more efficient to have a custom Ord implementation! - // I tried! It got really confusing! Some calls were coming out sorted and others weren't. Something subtle was wrong in the Ord implementation. - // While this is less efficient, it at least works, and I can reason through it. - // You're welcome to fix it. Just be sure to pass the unit tests. + // I tried! It got really confusing! Some calls were coming out sorted and others weren't. Something + // subtle was wrong in the Ord implementation. While this is less efficient, it at least + // works, and I can reason through it. You're welcome to fix it. Just be sure to pass the unit + // tests. free_key_vec.reverse(); // this is a bit weird because we have three cases: - // - the new key is more than 1 away from any element, in which case we insert it as a singleton (start = index, run = 0) - // - the new key is adjacent to exactly once element, in which case we put it either on the top or bottom (merge into existing record) - // - the new key is adjacent to exactly two elements, in which case we merge the new key and other two elements together, add its length to the new overall run + // - the new key is more than 1 away from any element, in which case we insert it as a singleton + // (start = index, run = 0) + // - the new key is adjacent to exactly once element, in which case we put it either on the top or + // bottom (merge into existing record) + // - the new key is adjacent to exactly two elements, in which case we merge the new key and other two + // elements together, add its length to the new overall run let mut skip = false; let mut placed = false; log::trace!("inserting free key {}", index); for i in 0..free_key_vec.len() { if skip { - // this happens when we merged into the /next/ record, and we reduced the total number of items by one + // this happens when we merged into the /next/ record, and we reduced the total number of + // items by one skip = false; - continue + continue; } if !placed { match free_key_vec[i].0.arg_compared_to_self(index) { FreeKeyCases::LessThan => { - self.free_keys.push(Reverse(FreeKeyRange{start: index as u32, run: 0})); + self.free_keys.push(Reverse(FreeKeyRange { start: index as u32, run: 0 })); placed = true; self.free_keys.push(free_key_vec[i]); } FreeKeyCases::LeftAdjacent => { - self.free_keys.push(Reverse(FreeKeyRange{start: index as u32, run: free_key_vec[i].0.run + 1})); + self.free_keys.push(Reverse(FreeKeyRange { + start: index as u32, + run: free_key_vec[i].0.run + 1, + })); placed = true; } FreeKeyCases::Within => { log::error!("Double-free error in free_keys()"); log::info!("free_key_vec[i].0: {:?}", free_key_vec[i].0); log::info!("index: {}", index); - panic!("Double-free error in free_keys(). free_key_vec[i].0: {:?}, index: {}, free_key_vec.len(): {}", - free_key_vec[i].0, index, free_key_vec.len()); + panic!( + "Double-free error in free_keys(). free_key_vec[i].0: {:?}, index: {}, free_key_vec.len(): {}", + free_key_vec[i].0, + index, + free_key_vec.len() + ); } FreeKeyCases::RightAdjacent => { // see if we should merge to the right if i + 1 < free_key_vec.len() { - log::trace!("middle insert {}: {:?}, {:?}", index, free_key_vec[i], free_key_vec[i+1]); - if free_key_vec[i+1].0.arg_compared_to_self(index as u32) == FreeKeyCases::LeftAdjacent { - self.free_keys.push( - Reverse(FreeKeyRange{ - start: free_key_vec[i].0.start, - run: free_key_vec[i].0.run + free_key_vec[i+1].0.run + 2 - })); + log::trace!( + "middle insert {}: {:?}, {:?}", + index, + free_key_vec[i], + free_key_vec[i + 1] + ); + if free_key_vec[i + 1].0.arg_compared_to_self(index as u32) + == FreeKeyCases::LeftAdjacent + { + self.free_keys.push(Reverse(FreeKeyRange { + start: free_key_vec[i].0.start, + run: free_key_vec[i].0.run + free_key_vec[i + 1].0.run + 2, + })); skip = true; placed = true; continue; } } - self.free_keys.push( - Reverse(FreeKeyRange { - start: free_key_vec[i].0.start, - run: free_key_vec[i].0.run + 1 - })); + self.free_keys.push(Reverse(FreeKeyRange { + start: free_key_vec[i].0.start, + run: free_key_vec[i].0.run + 1, + })); placed = true; } FreeKeyCases::GreaterThan => { @@ -1148,7 +1459,8 @@ impl DictCacheEntry { } /// a cache entry doesn't actually know it's own name -- it's the key associated with the cache entry - /// so you must provide it to create the full record. It also doesn't know the name of its containing basis. + /// so you must provide it to create the full record. It also doesn't know the name of its containing + /// basis. pub(crate) fn to_dict_attributes(&self, name: &str, basis_name: &str) -> DictAttributes { DictAttributes { flags: self.flags, @@ -1171,7 +1483,8 @@ impl DictCacheEntry { if kcache.flags.valid() && !kcache.clean { return 0; } - if let Some(KeyCacheData::Small(_)) = kcache.data { // can only prune small records + if let Some(KeyCacheData::Small(_)) = kcache.data { + // can only prune small records let pruned = kcache.size(); kcache.data.take(); // this effectively frees up the key cache data // mark the key's pool as unclean, so it is processed for filling @@ -1201,7 +1514,7 @@ fn dict_indices_to_vaddr(dict_index: NonZeroU32, key_meta_index: usize) -> u64 { /// The index maps into the small_pool array, which itself maps 1:1 onto blocks inside the small pool /// memory space. pub(crate) fn small_storage_index_from_key(kcache: &KeyCacheEntry, dict_index: NonZeroU32) -> Option { - let storage_base = (dict_index.get()-1) as u64 * SMALL_POOL_STRIDE + SMALL_POOL_START; + let storage_base = (dict_index.get() - 1) as u64 * SMALL_POOL_STRIDE + SMALL_POOL_START; let storage_end = storage_base + SMALL_POOL_STRIDE; if (kcache.start >= storage_base) && ((kcache.start + kcache.reserved) < storage_end) { let index_base = kcache.start - storage_base; @@ -1210,9 +1523,10 @@ pub(crate) fn small_storage_index_from_key(kcache: &KeyCacheEntry, dict_index: N None } } -/// derive the virtual address of a small storage block from the dictionary and the index of the small storage pool +/// derive the virtual address of a small storage block from the dictionary and the index of the small storage +/// pool pub(crate) fn small_storage_base_vaddr_from_indices(dict_index: NonZeroU32, base_index: usize) -> u64 { - SMALL_POOL_START + (dict_index.get()-1) as u64 * DICT_VSIZE + base_index as u64 * SMALL_CAPACITY as u64 + SMALL_POOL_START + (dict_index.get() - 1) as u64 * DICT_VSIZE + base_index as u64 * SMALL_CAPACITY as u64 } #[derive(PartialEq, Eq, Copy, Clone, Debug)] @@ -1239,12 +1553,7 @@ impl DictName { } } impl Default for DictName { - fn default() -> DictName { - DictName { - len: 0, - data: [0; DICT_NAME_LEN - 1] - } - } + fn default() -> DictName { DictName { len: 0, data: [0; DICT_NAME_LEN - 1] } } } #[derive(Debug)] @@ -1259,8 +1568,8 @@ pub(crate) struct Dictionary { /// Number of keys in the dictionary pub(crate) num_keys: u32, /// Free index starting space. While this is a derived parameter, its value is recorded to avoid - /// an expensive, long search operation during the creation of a dictionary cache record. 0 is an invalid index, - /// as this is where the header goes. Maybe this should be a NonZeroU32. + /// an expensive, long search operation during the creation of a dictionary cache record. 0 is an invalid + /// index, as this is where the header goes. Maybe this should be a NonZeroU32. pub(crate) free_key_index: u32, /// Name. Length should pad out the record to exactly 127 bytes. pub(crate) name: DictName, @@ -1274,18 +1583,23 @@ impl Default for Dictionary { } impl Deref for Dictionary { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const Dictionary as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const Dictionary as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } impl DerefMut for Dictionary { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut Dictionary as *mut u8, core::mem::size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut Dictionary as *mut u8, + core::mem::size_of::(), + ) as &mut [u8] } } } @@ -1309,11 +1623,7 @@ pub(crate) struct DictKeyEntry { pub(crate) data: [u8; DK_STRIDE], } impl Default for DictKeyEntry { - fn default() -> DictKeyEntry { - DictKeyEntry { - data: [0; DK_STRIDE] - } - } + fn default() -> DictKeyEntry { DictKeyEntry { data: [0; DK_STRIDE] } } } /// This structure helps to bookkeep which slices within a DictKey virtual page need to be updated @@ -1321,14 +1631,9 @@ pub(crate) struct DictKeyVpage { pub(crate) elements: [Option; VPAGE_SIZE / DK_STRIDE], } impl<'a> Default for DictKeyVpage { - fn default() -> DictKeyVpage { - DictKeyVpage { - elements: [None; VPAGE_SIZE / DK_STRIDE], - } - } + fn default() -> DictKeyVpage { DictKeyVpage { elements: [None; VPAGE_SIZE / DK_STRIDE] } } } - #[derive(PartialEq, Eq, Debug)] pub(crate) enum FreeKeyCases { LeftAdjacent, @@ -1339,7 +1644,8 @@ pub(crate) enum FreeKeyCases { } #[derive(Eq, Copy, Clone, Debug)] pub(crate) struct FreeKeyRange { - /// This index should be free. Smallest value is 1; 0-index is for the dict header. Maybe this should be a NonZeroU32? + /// This index should be free. Smallest value is 1; 0-index is for the dict header. Maybe this should be + /// a NonZeroU32? pub(crate) start: u32, /// Additional free keys after the start one. Run = 0 means just the start key is free, and the /// next one should be used. Run = 2 means {start, start+1} are free, etc. @@ -1363,19 +1669,13 @@ impl FreeKeyRange { } } impl Ord for FreeKeyRange { - fn cmp(&self, other: &Self) -> Ordering { - self.start.cmp(&other.start) - } + fn cmp(&self, other: &Self) -> Ordering { self.start.cmp(&other.start) } } impl PartialOrd for FreeKeyRange { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) } } impl PartialEq for FreeKeyRange { - fn eq(&self, other: &Self) -> bool { - self.start == other.start - } + fn eq(&self, other: &Self) -> bool { self.start == other.start } } /// stashed copy of a decrypted page. The copy here must always match @@ -1384,18 +1684,23 @@ impl PartialEq for FreeKeyRange { /// This is stored with the journal number on top. /// What the four possibilities of cache vs pp mean: /// Some(cache) & Some(cache_pp) -> valid cache and pp -/// None(cache) & Some(cache_pp) -> the page was allocated; but never used, or was erased (it's free for you to use it); alternately, it was corrupted -/// Some(cache) & None(cache_pp) -> invalid, internal error +/// None(cache) & Some(cache_pp) -> the page was allocated; but never used, or was erased (it's free for you +/// to use it); alternately, it was corrupted Some(cache) & None(cache_pp) -> invalid, internal error /// None(cache) & None(cache_pp) -> the basis mapping didn't exist: we've never requested this page before. pub(crate) struct PlaintextCache { /// a page of data, stored with the Journal rev on top - pub(crate) data: Option>, + pub(crate) data: Option>, /// the page the cache corresponds to pub(crate) tag: Option, } impl PlaintextCache { - pub fn fill(&mut self, hw: &mut PddbOs, v2p_map: &HashMap::, cipher: &Aes256GcmSiv, aad: &[u8], - req_vaddr: VirtAddr + pub fn fill( + &mut self, + hw: &mut PddbOs, + v2p_map: &HashMap, + cipher: &Aes256GcmSiv, + aad: &[u8], + req_vaddr: VirtAddr, ) { if let Some(pp) = v2p_map.get(&req_vaddr) { assert!(pp.valid(), "v2p returned an invalid page"); @@ -1434,11 +1739,7 @@ mod tests { } #[test] fn test_free_key_index() { - let mut d = DictCacheEntry::new( - Dictionary::default(), - 1, - &Vec::::new(), - ); + let mut d = DictCacheEntry::new(Dictionary::default(), 1, &Vec::::new()); let init_max = KEY_MAXCOUNT as u32 - 1 - 1; // 1..131070 let c = clone_bheap(&mut d.free_keys); @@ -1448,7 +1749,7 @@ mod tests { assert!(f[0].0.start == 1); assert!(f[0].0.run == init_max); for i in 0..10 { - assert!(d.get_free_key_index().unwrap().get() == i+1); + assert!(d.get_free_key_index().unwrap().get() == i + 1); } // 11...131060 let c = clone_bheap(&mut d.free_keys); @@ -1557,5 +1858,5 @@ mod tests { assert!(f.len() == 1); assert!(f[0].0.start == 8); assert!(f[0].0.run == init_max - 7); -} + } } diff --git a/services/pddb/src/backend/fastspace.rs b/services/pddb/src/backend/fastspace.rs index 7e4dadfeb..8cd055d1d 100644 --- a/services/pddb/src/backend/fastspace.rs +++ b/services/pddb/src/backend/fastspace.rs @@ -1,10 +1,11 @@ -use core::ops::{Deref, DerefMut}; use core::convert::TryInto; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; + +use aes_gcm_siv::{Nonce, Tag}; use super::PAGE_SIZE; use crate::*; -use core::mem::size_of; -use aes_gcm_siv::{Nonce, Tag}; /// Each free_pool entry takes about 4 bytes, so give-or-take we have about 1000 free_pool /// entries per page of storage for the free_pool, or 4k * 1000 ~ 4MiB per page, when PhysAddr is a u32 @@ -34,14 +35,12 @@ impl From for SpaceState { } } impl From for u8 { - fn from(arg: SpaceState) -> Self { - arg as u8 - } + fn from(arg: SpaceState) -> Self { arg as u8 } } -pub(crate) const FASTSPACE_FREE_POOL_LEN: usize = - ((PAGE_SIZE * FASTSPACE_PAGES) - (size_of::() + size_of::())) - / core::mem::size_of::(); +pub(crate) const FASTSPACE_FREE_POOL_LEN: usize = ((PAGE_SIZE * FASTSPACE_PAGES) + - (size_of::() + size_of::())) + / core::mem::size_of::(); /// FastSpace tracks a limited set of physical pages /// An optimal implementation of this would fill whole pages with the free_pool array. /// This record is meant to be updated rarely, and atomically, in a make-before-break fashion: @@ -60,17 +59,20 @@ pub(crate) const FASTSPACE_FREE_POOL_LEN: usize = /// The "rare" property is important especially as the disk size scales up; if we wanted to keep /// 100MiB of "fast space" on hand, this structure would span 25 pages. #[repr(C)] -pub (crate) struct FastSpace { +pub(crate) struct FastSpace { /// Not sure if there is a "better" way to compute things, but we want the number of entries in the /// free_pool array to "round out" the FastSpace record to be equal to exactly one page pub(crate) free_pool: [PhysPage; FASTSPACE_FREE_POOL_LEN], } impl Deref for FastSpace { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const FastSpace as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const FastSpace as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } @@ -84,10 +86,11 @@ pub(crate) struct FastSpaceInFlash { p_tag: [u8; size_of::()], } -/// a 128-bit record that stores an encrypted update to the FastSpace pool, facilitating the "rarely" update property of the structure. +/// a 128-bit record that stores an encrypted update to the FastSpace pool, facilitating the "rarely" update +/// property of the structure. #[repr(C, packed)] #[cfg(not(feature = "u64_pa"))] -pub (crate) struct SpaceUpdate { +pub(crate) struct SpaceUpdate { nonce: u64, page_number: PhysPage, // this checksum is "weak" but we are protecting against two scenarios: @@ -112,6 +115,7 @@ impl SpaceUpdate { None } } + pub fn new(nonce: u64, page_number: PhysPage) -> Self { let mut hashbuf: [u8; 12] = [0; 12]; for (&src, dst) in nonce.to_le_bytes().iter().zip(hashbuf[..8].iter_mut()) { @@ -121,16 +125,12 @@ impl SpaceUpdate { *dst = src; } let computed_sum = murmur3_32(&hashbuf[..12], u32::from_be_bytes(hashbuf[4..8].try_into().unwrap())); - SpaceUpdate { - nonce, - page_number, - checksum: computed_sum.to_le_bytes(), - } + SpaceUpdate { nonce, page_number, checksum: computed_sum.to_le_bytes() } } } #[cfg(feature = "u64_pa")] -pub (crate) struct SpaceUpdate { +pub(crate) struct SpaceUpdate { nonce: u64, page_number: PhysPage, // consider: using the top 12 bits of the PhysAddr as a checksum @@ -138,18 +138,23 @@ pub (crate) struct SpaceUpdate { impl Deref for SpaceUpdate { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const SpaceUpdate as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const SpaceUpdate as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } impl DerefMut for SpaceUpdate { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut SpaceUpdate as *mut u8, core::mem::size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut SpaceUpdate as *mut u8, + core::mem::size_of::(), + ) as &mut [u8] } } } @@ -159,6 +164,9 @@ mod tests { use super::*; #[test] fn test_fast_space_size() { - assert!(core::mem::size_of::() & (PAGE_SIZE - 1) == 0, "FastSpaceInFlash is not exactly a multiple of one page in size"); + assert!( + core::mem::size_of::() & (PAGE_SIZE - 1) == 0, + "FastSpaceInFlash is not exactly a multiple of one page in size" + ); } } diff --git a/services/pddb/src/backend/hosted.rs b/services/pddb/src/backend/hosted.rs index f3994b107..f142d3b5f 100644 --- a/services/pddb/src/backend/hosted.rs +++ b/services/pddb/src/backend/hosted.rs @@ -1,13 +1,12 @@ #![allow(dead_code)] -use crate::api::*; - -use std::sync::Once; -use std::mem::MaybeUninit; - use std::fs::File; use std::fs::OpenOptions; use std::io::prelude::*; use std::io::SeekFrom; +use std::mem::MaybeUninit; +use std::sync::Once; + +use crate::api::*; // This is considered bad practice for Rust to use a global singleton. // However, this hack puts the burden of emulation on the emulator, while @@ -17,7 +16,7 @@ use std::io::SeekFrom; // // Note that this is a concurrently accessed, unsafe, unchecked vector. struct FlashSingleton { - memory: Vec::, + memory: Vec, disk: File, } @@ -28,11 +27,11 @@ fn flashmem() -> &'static mut FlashSingleton { unsafe { ONCE.call_once(|| { let mut disk = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open("../tools/pddb-images/hosted.bin") - .expect("Can't open a PDDB image file for writing"); + .read(true) + .write(true) + .create(true) + .open("../tools/pddb-images/hosted.bin") + .expect("Can't open a PDDB image file for writing"); let mut memory = Vec::::with_capacity(PDDB_A_LEN); if disk.metadata().unwrap().len() == 0 { @@ -44,7 +43,11 @@ fn flashmem() -> &'static mut FlashSingleton { match disk.read_to_end(&mut memory) { Ok(bytes_read) => { if bytes_read != PDDB_A_LEN { - log::warn!("PDDB disk image is of an incorrect size: got {}, expected {}", bytes_read, PDDB_A_LEN); + log::warn!( + "PDDB disk image is of an incorrect size: got {}, expected {}", + bytes_read, + PDDB_A_LEN + ); } } _ => { @@ -53,10 +56,7 @@ fn flashmem() -> &'static mut FlashSingleton { } } - let flashmem = FlashSingleton { - memory, - disk, - }; + let flashmem = FlashSingleton { memory, disk }; SINGLETON.write(flashmem); }); SINGLETON.assume_init_mut() @@ -71,28 +71,26 @@ pub struct KeyExport { /// page table key pub pt_key: [u8; 32], } -pub struct EmuStorage { -} +pub struct EmuStorage {} impl EmuStorage { - pub fn new() -> Self { - EmuStorage { - } - } + pub fn new() -> Self { EmuStorage {} } + pub unsafe fn as_slice(&self) -> &[T] { - core::slice::from_raw_parts( - flashmem().memory.as_ptr() as *const T, - flashmem().memory.len() / core::mem::size_of::(), - ) - } - pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { - flashmem().memory.as_mut_slice() + core::slice::from_raw_parts( + flashmem().memory.as_ptr() as *const T, + flashmem().memory.len() / core::mem::size_of::(), + ) } + + pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { flashmem().memory.as_mut_slice() } + /// used to reset the storage for repeated test case generation pub fn reset(&mut self) { for b in flashmem().memory.as_mut_slice() { *b = 0xFF; } } + pub fn dump_fs(&self, name: &Option) { let defaultname = String::from("pddb"); let rootname = name.as_ref().unwrap_or(&defaultname); @@ -100,6 +98,7 @@ impl EmuStorage { f.write_all(flashmem().memory.as_slice()).unwrap(); f.flush().unwrap(); } + pub fn dump_keys(&self, known_keys: &[KeyExport], name: &Option) { let defaultname = String::from("pddb"); let rootname = name.as_ref().unwrap_or(&defaultname); @@ -114,26 +113,34 @@ impl EmuStorage { } } -pub struct HostedSpinor { -} +pub struct HostedSpinor {} impl HostedSpinor { - pub fn new() -> Self { - HostedSpinor { - } - } - pub fn patch(&self, _region: &[u8], _region_base: u32, data: &[u8], offset: u32) -> Result<(), xous::Error> { + pub fn new() -> Self { HostedSpinor {} } + + pub fn patch( + &self, + _region: &[u8], + _region_base: u32, + data: &[u8], + offset: u32, + ) -> Result<(), xous::Error> { // println!("patch at {:x}+{}", offset, data.len()); - for (&src, dst) in data.iter().zip( - flashmem().memory.as_mut_slice()[offset as usize..offset as usize + data.len()].iter_mut() - ) { + for (&src, dst) in data + .iter() + .zip(flashmem().memory.as_mut_slice()[offset as usize..offset as usize + data.len()].iter_mut()) + { *dst = src; } flashmem().disk.seek(SeekFrom::Start(offset as u64)).expect("couldn't seek PDDB"); flashmem().disk.write(data).expect("couldn't write PDDB"); Ok(()) } + pub fn bulk_erase(&self, start: u32, len: u32) -> Result<(), xous::Error> { - for b in flashmem().memory.as_mut_slice()[(start - xous::PDDB_LOC) as usize .. (start - xous::PDDB_LOC + len) as usize].iter_mut() { + for b in flashmem().memory.as_mut_slice() + [(start - xous::PDDB_LOC) as usize..(start - xous::PDDB_LOC + len) as usize] + .iter_mut() + { *b = 0xFF; } flashmem().disk.seek(SeekFrom::Start(start as u64)).expect("couldn't seek PDDB"); diff --git a/services/pddb/src/backend/hw.rs b/services/pddb/src/backend/hw.rs index d352b9b23..0a3eaf287 100644 --- a/services/pddb/src/backend/hw.rs +++ b/services/pddb/src/backend/hw.rs @@ -1,46 +1,44 @@ -use std::convert::TryInto; - -use crate::*; -use backend::bcrypt::*; -use aes_gcm_siv::{Nonce, Tag, Aes256GcmSiv}; -use aes_gcm_siv::aead::{Aead, Payload}; -use aes::{Aes256, Block, BLOCK_SIZE}; -use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray}; -use modals::Modals; -use root_keys::api::KeywrapError; -use root_keys::api::AesRootkeyType; -use spinor::SPINOR_BULK_ERASE_SIZE; -use subtle::ConstantTimeEq; -use core::ops::{Deref, DerefMut}; use core::mem::size_of; - -use std::collections::HashMap; -#[cfg(any(all(feature="pddbtest", feature="autobasis"), not(feature="deterministic")))] -use std::collections::HashSet; -#[cfg(feature="deterministic")] +use core::ops::{Deref, DerefMut}; +use std::cmp::Reverse; +#[cfg(feature = "deterministic")] use std::collections::BTreeSet; use std::collections::BinaryHeap; -use std::cmp::Reverse; -use std::io::{Result, Error, ErrorKind}; +use std::collections::HashMap; +#[cfg(any(all(feature = "pddbtest", feature = "autobasis"), not(feature = "deterministic")))] +use std::collections::HashSet; +use std::convert::TryInto; +use std::io::{Error, ErrorKind, Result}; -#[cfg(not(feature="deterministic"))] -type FspaceSet = HashSet::; -#[cfg(feature="deterministic")] -type FspaceSet = BTreeSet::; +use aes::cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit}; +use aes::{Aes256, Block, BLOCK_SIZE}; +use aes_gcm_siv::aead::{Aead, Payload}; +use aes_gcm_siv::{Aes256GcmSiv, Nonce, Tag}; +use backend::bcrypt::*; +use modals::Modals; +use root_keys::api::AesRootkeyType; +use root_keys::api::KeywrapError; +use spinor::SPINOR_BULK_ERASE_SIZE; +use subtle::ConstantTimeEq; +use crate::*; + +#[cfg(not(feature = "deterministic"))] +type FspaceSet = HashSet; +#[cfg(feature = "deterministic")] +type FspaceSet = BTreeSet; + +use digest::Digest; +#[cfg(feature = "perfcounter")] +use perflib::*; +use sha2::{FallbackStrategy, Sha512Trunc256}; +#[cfg(feature = "perfcounter")] +use utralib::AtomicCsr; use zeroize::Zeroize; -use sha2::{FallbackStrategy, Sha512Trunc256}; -use digest::Digest; - -#[cfg(feature="migration1")] +#[cfg(feature = "migration1")] use crate::backend::migration1to2::*; -#[cfg(feature="perfcounter")] -use perflib::*; -#[cfg(feature="perfcounter")] -use utralib::AtomicCsr; - /// Implementation-specific PDDB structures: for Precursor/Xous OS pair pub(crate) const MBBB_PAGES: usize = 10; pub(crate) const FSCB_PAGES: usize = 16; @@ -58,7 +56,7 @@ pub const KCOM_CT_LEN: usize = 4004; pub(crate) const WRAPPED_AES_KEYSIZE: usize = AES_KEYSIZE + 8; const SCD_VERSION: u32 = 2; -#[cfg(all(feature="pddbtest", feature="autobasis"))] +#[cfg(all(feature = "pddbtest", feature = "autobasis"))] pub const BASIS_TEST_ROOTNAME: &'static str = "test"; #[derive(Zeroize)] @@ -67,7 +65,8 @@ pub const BASIS_TEST_ROOTNAME: &'static str = "test"; pub(crate) struct StaticCryptoData { /// a version number for the block pub(crate) version: u32, - /// aes-256 key of the system basis page table, encrypted with the User0 root key, and wrapped using NIST SP800-38F + /// aes-256 key of the system basis page table, encrypted with the User0 root key, and wrapped using NIST + /// SP800-38F pub(crate) system_key_pt: [u8; WRAPPED_AES_KEYSIZE], /// aes-256 key of the system basis, encrypted with the User0 root key, and wrapped using NIST SP800-38F pub(crate) system_key: [u8; WRAPPED_AES_KEYSIZE], @@ -86,18 +85,23 @@ impl StaticCryptoData { } impl Deref for StaticCryptoData { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const StaticCryptoData as *const u8, size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const StaticCryptoData as *const u8, + size_of::(), + ) as &[u8] } } } impl DerefMut for StaticCryptoData { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut StaticCryptoData as *mut u8, size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut StaticCryptoData as *mut u8, + size_of::(), + ) as &mut [u8] } } } @@ -111,15 +115,15 @@ enum DnaMode { #[zeroize(drop)] pub(crate) struct BasisKeys { pub(crate) pt: [u8; AES_KEYSIZE], - pub(crate) data: [u8; AES_KEYSIZE] + pub(crate) data: [u8; AES_KEYSIZE], } struct MigrationCiphers { pt_ecb: Aes256, data_gcm_siv: Aes256GcmSiv, data_key: [u8; 32], - aad_incoming: Vec::, - aad_local: Vec::, + aad_incoming: Vec, + aad_local: Vec, } // emulated @@ -129,9 +133,9 @@ type EmuMemoryRange = EmuStorage; type EmuSpinor = HostedSpinor; // native hardware -#[cfg(any(feature="precursor", feature="renode"))] +#[cfg(any(feature = "precursor", feature = "renode"))] type EmuMemoryRange = xous::MemoryRange; -#[cfg(any(feature="precursor", feature="renode"))] +#[cfg(any(feature = "precursor", feature = "renode"))] type EmuSpinor = spinor::Spinor; pub(crate) struct PddbOs { @@ -142,24 +146,28 @@ pub(crate) struct PddbOs { /// page table base -- location in FLASH, offset from physical bottom of pddb_mr pt_phys_base: PageAlignedPa, /// local key store -- one page, to store exactly one key, used for the system basis. - /// the rest of the keys are generated on the fly entirely from the user password + a salt also stored in this page + /// the rest of the keys are generated on the fly entirely from the user password + a salt also stored in + /// this page key_phys_base: PageAlignedPa, /// make before break buffer base -- location in FLASH, offset from physical bottom of pddb_mr mbbb_phys_base: PageAlignedPa, /// free space circular buffer base -- location in FLASH, offset from physical bottom of pddb_mr fscb_phys_base: PageAlignedPa, data_phys_base: PageAlignedPa, - /// We keep a copy of the raw key around because we have to combine this with the AAD of a block to derive the AES-GCM-SIV cipher. + /// We keep a copy of the raw key around because we have to combine this with the AAD of a block to + /// derive the AES-GCM-SIV cipher. system_basis_key: Option, - /// derived cipher for handling fastspace -- cache it, so we can save the time cost of constructing the cipher key schedule + /// derived cipher for handling fastspace -- cache it, so we can save the time cost of constructing the + /// cipher key schedule cipher_ecb: Option, /// fast space cache fspace_cache: FspaceSet, /// memoize the location of the fscb log pages - fspace_log_addrs: Vec::, + fspace_log_addrs: Vec, /// memoize the current target offset for the next log entry fspace_log_next_addr: Option, - /// track roughly how big the log has gotten, so we can pre-emptively garbage collect it before we get too full. + /// track roughly how big the log has gotten, so we can pre-emptively garbage collect it before we get + /// too full. fspace_log_len: usize, /// a cached copy of the FPGA's DNA ID, used in the AAA records. dna: u64, @@ -173,14 +181,14 @@ pub(crate) struct PddbOs { pw_cid: xous::CID, /// Number of consecutive failed login attempts failed_logins: u64, - #[cfg(all(feature="pddbtest", feature="autobasis"))] - testnames: HashSet::, + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] + testnames: HashSet, /// Performance counter elements - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] perfclient: PerfClient, - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pc_id: u32, - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] /// used to toggle performance profiling on or off use_perf: bool, } @@ -188,7 +196,7 @@ pub(crate) struct PddbOs { impl PddbOs { pub fn new(trngpool: Rc>, pw_cid: xous::CID) -> PddbOs { let xns = xous_names::XousNames::new().unwrap(); - #[cfg(any(feature="precursor", feature="renode"))] + #[cfg(any(feature = "precursor", feature = "renode"))] let pddb = xous::syscall::map_memory( xous::MemoryAddress::new(xous::PDDB_LOC as usize + xous::FLASH_PHYS_BASE as usize), None, @@ -196,23 +204,29 @@ impl PddbOs { xous::MemoryFlags::R | xous::MemoryFlags::RESERVE, ) .expect("Couldn't map the PDDB memory range"); - #[cfg(any(feature="precursor", feature="renode"))] + #[cfg(any(feature = "precursor", feature = "renode"))] // Safety: all u8 values are valid - log::info!("pddb slice len: {}, PDDB_A_LEN: {}, raw len: {}", unsafe { pddb.as_slice::().len() }, PDDB_A_LEN, pddb.len()); // sanity check the PDDB size on init + log::info!( + "pddb slice len: {}, PDDB_A_LEN: {}, raw len: {}", + unsafe { pddb.as_slice::().len() }, + PDDB_A_LEN, + pddb.len() + ); // sanity check the PDDB size on init // the mbbb is located one page off from the Page Table let key_phys_base = PageAlignedPa::from(size_of::()); log::debug!("key_phys_base: {:x?}", key_phys_base); let mbbb_phys_base = key_phys_base + PageAlignedPa::from(PAGE_SIZE); log::debug!("mbbb_phys_base: {:x?}", mbbb_phys_base); - let fscb_phys_base = PageAlignedPa::from(mbbb_phys_base.as_u32() + MBBB_PAGES as u32 * PAGE_SIZE as u32); + let fscb_phys_base = + PageAlignedPa::from(mbbb_phys_base.as_u32() + MBBB_PAGES as u32 * PAGE_SIZE as u32); log::debug!("fscb_phys_base: {:x?}", fscb_phys_base); let llio = llio::Llio::new(&xns); let dna = llio.soc_dna().unwrap(); // performance counter infrastructure, if selected - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] let event2_csr = xous::syscall::map_memory( xous::MemoryAddress::new(utralib::generated::utra::event_source2::HW_EVENT_SOURCE2_BASE), None, @@ -220,23 +234,26 @@ impl PddbOs { xous::MemoryFlags::R | xous::MemoryFlags::W, ) .expect("couldn't map event2 CSR range"); - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] let perfclient = PerfClient::new(AtomicCsr::new(event2_csr.as_mut_ptr() as *mut u32)); - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] let pc_id = xous::process::id() as u32; // native hardware - #[cfg(any(feature="precursor", feature="renode"))] + #[cfg(any(feature = "precursor", feature = "renode"))] let ret = PddbOs { spinor: spinor::Spinor::new(&xns).unwrap(), - rootkeys: root_keys::RootKeys::new(&xns, Some(AesRootkeyType::User0)).expect("FATAL: couldn't access RootKeys!"), + rootkeys: root_keys::RootKeys::new(&xns, Some(AesRootkeyType::User0)) + .expect("FATAL: couldn't access RootKeys!"), tt: ticktimer_server::Ticktimer::new().unwrap(), pddb_mr: pddb, pt_phys_base: PageAlignedPa::from(0 as u32), key_phys_base, mbbb_phys_base, fscb_phys_base, - data_phys_base: PageAlignedPa::from(fscb_phys_base.as_u32() + FSCB_PAGES as u32 * PAGE_SIZE as u32), + data_phys_base: PageAlignedPa::from( + fscb_phys_base.as_u32() + FSCB_PAGES as u32 * PAGE_SIZE as u32, + ), system_basis_key: None, cipher_ecb: None, fspace_cache: FspaceSet::new(), @@ -250,13 +267,13 @@ impl PddbOs { entropy: trngpool, pw_cid, failed_logins: 0, - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] testnames: HashSet::new(), - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] perfclient, - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pc_id, - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] use_perf: true, }; // emulated @@ -264,14 +281,17 @@ impl PddbOs { let ret = { PddbOs { spinor: HostedSpinor::new(), - rootkeys: root_keys::RootKeys::new(&xns, Some(AesRootkeyType::User0)).expect("FATAL: couldn't access RootKeys!"), + rootkeys: root_keys::RootKeys::new(&xns, Some(AesRootkeyType::User0)) + .expect("FATAL: couldn't access RootKeys!"), tt: ticktimer_server::Ticktimer::new().unwrap(), pddb_mr: EmuStorage::new(), pt_phys_base: PageAlignedPa::from(0 as u32), key_phys_base, mbbb_phys_base, fscb_phys_base, - data_phys_base: PageAlignedPa::from(fscb_phys_base.as_u32() + FSCB_PAGES as u32 * PAGE_SIZE as u32), + data_phys_base: PageAlignedPa::from( + fscb_phys_base.as_u32() + FSCB_PAGES as u32 * PAGE_SIZE as u32, + ), system_basis_key: None, cipher_ecb: None, fspace_cache: FspaceSet::new(), @@ -285,7 +305,7 @@ impl PddbOs { entropy: trngpool, pw_cid, failed_logins: 0, - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] testnames: HashSet::new(), } }; @@ -293,7 +313,7 @@ impl PddbOs { } #[cfg(not(target_os = "xous"))] - pub fn dbg_dump(&self, name: Option, extra_keys: Option<&Vec::>) { + pub fn dbg_dump(&self, name: Option, extra_keys: Option<&Vec>) { self.pddb_mr.dump_fs(&name); let mut export = Vec::::new(); if let Some(key) = &self.system_basis_key { @@ -302,13 +322,7 @@ impl PddbOs { for (&src, dst) in PDDB_DEFAULT_SYSTEM_BASIS.as_bytes().iter().zip(name.iter_mut()) { *dst = src; } - export.push( - KeyExport { - basis_name: name, - key: key.data, - pt_key: key.pt, - } - ); + export.push(KeyExport { basis_name: name, key: key.data, pt_key: key.pt }); } if let Some(extra) = extra_keys { for key in extra { @@ -317,24 +331,26 @@ impl PddbOs { } self.pddb_mr.dump_keys(&export, &name); } + #[allow(dead_code)] - #[cfg(any(feature="precursor", feature="renode"))] + #[cfg(any(feature = "precursor", feature = "renode"))] pub fn dbg_dump(&self, _name: Option) { // placeholder } + #[allow(dead_code)] - #[cfg(feature="perfcounter")] - pub fn set_use_perf(&mut self, use_perf: bool) { - self.use_perf = use_perf; - } + #[cfg(feature = "perfcounter")] + pub fn set_use_perf(&mut self, use_perf: bool) { self.use_perf = use_perf; } + #[allow(dead_code)] - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pub fn perf_entry(&mut self, file_id: u32, meta: u32, index: u32, line: u32) { if self.use_perf { let entry = perf_entry!(self.pc_id, file_id, meta, index, line); self.perfclient.log_event_unchecked(entry); } } + #[allow(dead_code)] #[cfg(not(target_os = "xous"))] /// used to reset the hardware structure for repeated runs of testing within a single invocation @@ -346,6 +362,7 @@ impl PddbOs { self.fspace_log_next_addr = None; self.pddb_mr.reset(); } + pub(crate) fn is_efuse_secured(&self) -> bool { self.rootkeys.is_efuse_secured().expect("couldn't query efuse security state") == Some(true) } @@ -354,68 +371,88 @@ impl PddbOs { let nonce_array = self.entropy.borrow_mut().get_nonce(); *Nonce::from_slice(&nonce_array) } + #[allow(dead_code)] - pub(crate) fn dna(&self) -> u64 {self.dna} - pub(crate) fn trng_slice(&self, slice: &mut [u8]) { - self.entropy.borrow_mut().get_slice(slice); - } - pub(crate) fn trng_u32(&self) -> u32 { - self.entropy.borrow_mut().get_u32() - } - pub(crate) fn trng_u8(&self) -> u8 { - self.entropy.borrow_mut().get_u8() - } - pub(crate) fn timestamp_now(&self) -> u64 {self.tt.elapsed_ms()} + pub(crate) fn dna(&self) -> u64 { self.dna } + + pub(crate) fn trng_slice(&self, slice: &mut [u8]) { self.entropy.borrow_mut().get_slice(slice); } + + pub(crate) fn trng_u32(&self) -> u32 { self.entropy.borrow_mut().get_u32() } + + pub(crate) fn trng_u8(&self) -> u8 { self.entropy.borrow_mut().get_u8() } + + pub(crate) fn timestamp_now(&self) -> u64 { self.tt.elapsed_ms() } + /// checks if the root keys are initialized, which is a prerequisite to formatting and mounting pub(crate) fn rootkeys_initialized(&self) -> bool { self.rootkeys.is_initialized().expect("couldn't query initialization state of the rootkeys server") } + /// patches data at an offset starting from the data physical base address, which corresponds /// exactly to the first entry in the page table pub(crate) fn patch_data(&self, data: &[u8], offset: u32) { log::trace!("patch offset: {:x} len: {:x}", offset, data.len()); - // log::trace!("patch bef: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + self.data_phys_base.as_usize()..offset as usize + self.data_phys_base.as_usize() + 48]); - assert!(data.len() + offset as usize <= PDDB_A_LEN - self.data_phys_base.as_usize(), "attempt to store past disk boundary"); - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &data, - offset + self.data_phys_base.as_u32(), - ).expect("couldn't write to data region in the PDDB"); - //log::trace!("patch aft: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + self.data_phys_base.as_usize()..offset as usize + self.data_phys_base.as_usize() + 48]); - //log::trace!("patch end: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + self.data_phys_base.as_usize() + 4048..offset as usize + self.data_phys_base.as_usize() + 4096]) + // log::trace!("patch bef: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + + // self.data_phys_base.as_usize()..offset as usize + self.data_phys_base.as_usize() + 48]); + assert!( + data.len() + offset as usize <= PDDB_A_LEN - self.data_phys_base.as_usize(), + "attempt to store past disk boundary" + ); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + &data, + offset + self.data_phys_base.as_u32(), + ) + .expect("couldn't write to data region in the PDDB"); + //log::trace!("patch aft: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + + // self.data_phys_base.as_usize()..offset as usize + self.data_phys_base.as_usize() + 48]); + // log::trace!("patch end: {:x?}", &self.pddb_mr.as_slice::()[offset as usize + + // self.data_phys_base.as_usize() + 4048..offset as usize + self.data_phys_base.as_usize() + 4096]) } + fn patch_pagetable(&self, data: &[u8], offset: u32) { if cfg!(feature = "mbbb") { - assert!(data.len() + offset as usize <= size_of::(), "attempt to patch past page table end"); - // 1. Check if there is an MBBB structure existing. If so, copy it into the empty slot in the page table, then erase the mbbb. + assert!( + data.len() + offset as usize <= size_of::(), + "attempt to patch past page table end" + ); + // 1. Check if there is an MBBB structure existing. If so, copy it into the empty slot in the page + // table, then erase the mbbb. if let Some(mbbb) = self.mbbb_retrieve() { if let Some(erased_offset) = self.pt_find_erased_slot() { - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &mbbb, - self.pt_phys_base.as_u32() + erased_offset, - ).expect("couldn't write to page table"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + &mbbb, + self.pt_phys_base.as_u32() + erased_offset, + ) + .expect("couldn't write to page table"); } - // if there *isn't* an erased slot, we still want to get rid of the MBBB structure. A lack of an - // erased slot would indicate we lost power after we copied the previous MBBB structure but before we could erase - // it from storage, so this picks up where we left off. + // if there *isn't* an erased slot, we still want to get rid of the MBBB structure. A lack of + // an erased slot would indicate we lost power after we copied the previous + // MBBB structure but before we could erase it from storage, so this picks up + // where we left off. self.mbbb_erase(); } // 2. find the page we're patching let base_page = offset as usize & !(PAGE_SIZE - 1); // 3. copy the data to a local buffer let mut mbbb_page = [0u8; PAGE_SIZE]; - for (&src, dst) in - unsafe { self.pddb_mr.as_slice()[self.pt_phys_base.as_usize() + base_page..self.pt_phys_base.as_usize() + base_page + PAGE_SIZE].iter() } - .zip(mbbb_page.iter_mut()) { + for (&src, dst) in unsafe { + self.pddb_mr.as_slice()[self.pt_phys_base.as_usize() + base_page + ..self.pt_phys_base.as_usize() + base_page + PAGE_SIZE] + .iter() + } + .zip(mbbb_page.iter_mut()) + { *dst = src; } // 4. patch the local buffer copy let base_offset = offset as usize & (PAGE_SIZE - 1); - for (&src, dst) in data.iter() - .zip(mbbb_page[base_offset..base_offset + data.len()].iter_mut()) { + for (&src, dst) in data.iter().zip(mbbb_page[base_offset..base_offset + data.len()].iter_mut()) { *dst = src; } // 5. pick a random offset in the MBBB for the target patch, and write the data into the target @@ -423,55 +460,75 @@ impl PddbOs { self.patch_mbbb(&mbbb_page, offset); // 6. erase the original page area, thus making the MBBB the authorative location let blank = [0xffu8; PAGE_SIZE]; - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &blank, - self.pt_phys_base.as_u32() + base_page as u32, - ).expect("couldn't write to page table"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + &blank, + self.pt_phys_base.as_u32() + base_page as u32, + ) + .expect("couldn't write to page table"); } else { self.patch_pagetable_raw(data, offset); } } + /// Direct write to the page table, without MBBB buffering. fn patch_pagetable_raw(&self, data: &[u8], offset: u32) { - assert!(data.len() + offset as usize <= size_of::(), "attempt to patch past page table end"); - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &data, - self.pt_phys_base.as_u32() + offset, - ).expect("couldn't write to page table"); + assert!( + data.len() + offset as usize <= size_of::(), + "attempt to patch past page table end" + ); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + &data, + self.pt_phys_base.as_u32() + offset, + ) + .expect("couldn't write to page table"); } + fn patch_keys(&self, data: &[u8], offset: u32) { - assert!(data.len() + offset as usize <= PAGE_SIZE, "attempt to burn key data that is outside the key region"); + assert!( + data.len() + offset as usize <= PAGE_SIZE, + "attempt to burn key data that is outside the key region" + ); log::info!("patching keys area with {} bytes", data.len()); - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - data, - self.key_phys_base.as_u32() + offset - ).expect("couldn't burn keys"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + data, + self.key_phys_base.as_u32() + offset, + ) + .expect("couldn't burn keys"); } + fn patch_mbbb(&self, data: &[u8], offset: u32) { assert!(data.len() + offset as usize <= PAGE_SIZE * MBBB_PAGES, "mbbb patch would go out of bounds"); - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - data, - self.mbbb_phys_base.as_u32() + offset - ).expect("couldn't burn mbbb"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + data, + self.mbbb_phys_base.as_u32() + offset, + ) + .expect("couldn't burn mbbb"); } - /// raw patch is provided for 128-bit incremental updates to the FLASH. For FastSpace master record writes, - /// see fast_space_write() + + /// raw patch is provided for 128-bit incremental updates to the FLASH. For FastSpace master record + /// writes, see fast_space_write() fn patch_fscb(&self, data: &[u8], offset: u32) { assert!(data.len() + offset as usize <= PAGE_SIZE * FSCB_PAGES, "fscb patch would go out of bounds"); - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - data, - self.fscb_phys_base.as_u32() + offset - ).expect("couldn't burn fscb"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + data, + self.fscb_phys_base.as_u32() + offset, + ) + .expect("couldn't burn fscb"); } /// Public function that "does the right thing" for patching in a page table entry based on a virtual to @@ -493,51 +550,70 @@ impl PddbOs { self.trng_slice(&mut eraseblock); self.patch_pagetable(&eraseblock, phys_page_num * aes::BLOCK_SIZE as u32); } + /// Searches the page table for an MBBB slot. This is currently an O(N) search but /// in practice for Precursor there are only 8 pages, so it's quite fast on average. /// This would want to be optimized or cached for a much larger filesystem. /// Returns the physical address of the erased slot as an offset from the pt_phys_base() fn pt_find_erased_slot(&self) -> Option { - let pt: &[u8] = unsafe { &self.pddb_mr.as_slice()[self.pt_phys_base.as_usize()..self.pt_phys_base.as_usize() + size_of::()] }; + let pt: &[u8] = unsafe { + &self.pddb_mr.as_slice() + [self.pt_phys_base.as_usize()..self.pt_phys_base.as_usize() + size_of::()] + }; let blank = [0xffu8; aes::BLOCK_SIZE]; for (index, page) in pt.chunks(PAGE_SIZE).enumerate() { if page[..aes::BLOCK_SIZE] == blank { - return Some( (index * PAGE_SIZE) as PhysAddr ) + return Some((index * PAGE_SIZE) as PhysAddr); } } None } + fn pt_as_slice(&self) -> &[u8] { - unsafe { &self.pddb_mr.as_slice()[self.pt_phys_base.as_usize()..self.pt_phys_base.as_usize() + size_of::()] } + unsafe { + &self.pddb_mr.as_slice() + [self.pt_phys_base.as_usize()..self.pt_phys_base.as_usize() + size_of::()] + } } /// scans the page tables and returns all entries for a given basis /// basis_name is needed to decrypt pages in case of a journal conflict /// /// Here, we may encounter page table entries that are bogus for two reasons: - /// 1. PT entries that pass the checksum, but don't map to valid data due to random collisions from the short 32-bit checksum space + /// 1. PT entries that pass the checksum, but don't map to valid data due to random collisions from the + /// short 32-bit checksum space /// 2. Conflicting entries due to an unclean power-down /// /// This routine does a "lazy" check of improper data: - /// If the bad data happens to land on the same page mapping as valid data, a conflict resolution happens where - /// the block is decrypted and validated. - /// - If there is only one valid block, the valid block is picked to populate the page table (handles half of case 1) - /// - If two valid blocks are found, the one with the older journal entry is used (handles the easy version of case 2) - /// - If two valid blocks with identical journal versions happen (hard version of case 2), this is an internal consistency error. - /// It shouldn't happen; but, if it happens, it retains the first version encountered and prints an error to the log. + /// If the bad data happens to land on the same page mapping as valid data, a conflict resolution happens + /// where the block is decrypted and validated. + /// - If there is only one valid block, the valid block is picked to populate the page table (handles + /// half of case 1) + /// - If two valid blocks are found, the one with the older journal entry is used (handles the easy + /// version of case 2) + /// - If two valid blocks with identical journal versions happen (hard version of case 2), this is an + /// internal consistency error. It shouldn't happen; but, if it happens, it retains the first version + /// encountered and prints an error to the log. /// - /// This leaves the "hard version" of case 1 - there was a checksum collision, and it wasn't detected because no other valid - /// block happened to map to it. In this case, the "imposter" entry is left to stand, under the theory that typically this represents - /// this represents a "leak" of free space where this orphaned bock may never be allocated or used. However, because these are - /// rare (perhaps around 1 in a billion chance?) this leakage is fine. Note that if the FSCB is generated before this page gets - /// allocated, the imposter page is avoided in the scan, so this leaked memory is "forever". An orphaned node search could - /// try to chase this out, if it becomes a problem. + /// This leaves the "hard version" of case 1 - there was a checksum collision, and it wasn't detected + /// because no other valid block happened to map to it. In this case, the "imposter" entry is left to + /// stand, under the theory that typically this represents this represents a "leak" of free space + /// where this orphaned bock may never be allocated or used. However, because these are rare (perhaps + /// around 1 in a billion chance?) this leakage is fine. Note that if the FSCB is generated before this + /// page gets allocated, the imposter page is avoided in the scan, so this leaked memory is "forever". + /// An orphaned node search could try to chase this out, if it becomes a problem. /// - /// If the "imposter" entry also happens to be "allocated" down the road (that is, the FSCB happens to have an entry - /// that also maps to the imposter), the imposter is effectively de-allocated because the FSCB report is inserted directly - /// into the page table, overwriting the impostor entry in the paget able. On the next mount, this turns into the "trivial conflict" - /// case, where one page will validate and the other will not. - pub(crate) fn pt_scan_key(&self, pt_key: &[u8; AES_KEYSIZE], data_key: &[u8; AES_KEYSIZE], basis_name: &str) -> Option> { + /// If the "imposter" entry also happens to be "allocated" down the road (that is, the FSCB happens to + /// have an entry that also maps to the imposter), the imposter is effectively de-allocated because + /// the FSCB report is inserted directly into the page table, overwriting the impostor entry in the + /// paget able. On the next mount, this turns into the "trivial conflict" case, where one page will + /// validate and the other will not. + pub(crate) fn pt_scan_key( + &self, + pt_key: &[u8; AES_KEYSIZE], + data_key: &[u8; AES_KEYSIZE], + basis_name: &str, + ) -> Option> { let cipher = Aes256::new(&GenericArray::from_slice(pt_key)); let pt = self.pt_as_slice(); let mut map = HashMap::::new(); @@ -547,14 +623,17 @@ impl PddbOs { if let Some(page) = self.mbbb_retrieve() { page } else { - log::debug!("Blank page in PT found, but no MBBB entry exists. PT is either corrupted or not initialized!"); + log::debug!( + "Blank page in PT found, but no MBBB entry exists. PT is either corrupted or not initialized!" + ); pt_page } } else { pt_page }; for (index, candidate) in clean_page.chunks(aes::BLOCK_SIZE).enumerate() { - // encryption is in-place, but the candidates are read-only, so we have to copy them to a new location + // encryption is in-place, but the candidates are read-only, so we have to copy them to a new + // location let mut block = Block::clone_from_slice(candidate); cipher.decrypt_block(&mut block); if let Some(pte) = Pte::try_from_slice(block.as_slice()) { @@ -574,12 +653,18 @@ impl PddbOs { let new_data = self.data_decrypt_page(&cipher, &aad, &pp); if let Some(new_d) = new_data { if let Some(prev_d) = prev_data { - let prev_j = JournalType::from_le_bytes(prev_d[..size_of::()].try_into().unwrap()); - let new_j = JournalType::from_le_bytes(new_d[..size_of::()].try_into().unwrap()); + let prev_j = JournalType::from_le_bytes( + prev_d[..size_of::()].try_into().unwrap(), + ); + let new_j = JournalType::from_le_bytes( + new_d[..size_of::()].try_into().unwrap(), + ); if new_j > prev_j { map.insert(pte.vaddr(), pp); } else if new_j == prev_j { - log::error!("Found duplicate blocks with same journal age, picking arbitrary block and moving on..."); + log::error!( + "Found duplicate blocks with same journal age, picking arbitrary block and moving on..." + ); } } else { self.resolve_pp_journal(&mut pp); @@ -588,7 +673,11 @@ impl PddbOs { } } else { // new data is bogus, ignore it - log::warn!("conflicting PTE found, but data is bogus: v{:x?}:p{:x?}", pte.vaddr(), prev_page); + log::warn!( + "conflicting PTE found, but data is bogus: v{:x?}:p{:x?}", + pte.vaddr(), + prev_page + ); } } else { // handle case that there is a conflict in root structure @@ -596,12 +685,18 @@ impl PddbOs { let new_data = self.data_decrypt_page_with_commit(data_key, &aad, &pp); if let Some(new_d) = new_data { if let Some(prev_d) = prev_data { - let prev_j = JournalType::from_le_bytes(prev_d[..size_of::()].try_into().unwrap()); - let new_j = JournalType::from_le_bytes(new_d[..size_of::()].try_into().unwrap()); + let prev_j = JournalType::from_le_bytes( + prev_d[..size_of::()].try_into().unwrap(), + ); + let new_j = JournalType::from_le_bytes( + new_d[..size_of::()].try_into().unwrap(), + ); if new_j > prev_j { map.insert(pte.vaddr(), pp); } else if new_j == prev_j { - log::error!("Found duplicate blocks with same journal age, picking arbitrary block and moving on..."); + log::error!( + "Found duplicate blocks with same journal age, picking arbitrary block and moving on..." + ); } } else { self.resolve_pp_journal(&mut pp); @@ -611,7 +706,11 @@ impl PddbOs { } else { // new data is bogus, ignore it // new data is bogus, ignore it - log::warn!("conflicting basis root found, but data is bogus: v{:x?}:p{:x?}", pte.vaddr(), prev_page); + log::warn!( + "conflicting basis root found, but data is bogus: v{:x?}:p{:x?}", + pte.vaddr(), + prev_page + ); } } } else { @@ -621,14 +720,12 @@ impl PddbOs { } } } - if map.len() > 0 { - Some(map) - } else { - None - } + if map.len() > 0 { Some(map) } else { None } } + /// Pages drawn from disk might already have come from the FSCB. We need to make the journal number - /// of these consistent with those in the FSCB so later on when they are retired we don't have journal conflicts. + /// of these consistent with those in the FSCB so later on when they are retired we don't have journal + /// conflicts. fn resolve_pp_journal(&self, pp: &mut PhysPage) { if let Some(fs_pp) = self.fspace_cache.get(pp) { if pp.journal() < fs_pp.journal() { @@ -640,22 +737,31 @@ impl PddbOs { /// maps a StaticCryptoData structure into the key area of the PDDB. fn static_crypto_data_get(&self) -> &StaticCryptoData { - let scd_ptr = unsafe { self.pddb_mr.as_slice::()[self.key_phys_base.as_usize()..self.key_phys_base.as_usize() + PAGE_SIZE].as_ptr() as *const StaticCryptoData }; - let scd: &StaticCryptoData = unsafe{scd_ptr.as_ref().unwrap()}; + let scd_ptr = unsafe { + self.pddb_mr.as_slice::() + [self.key_phys_base.as_usize()..self.key_phys_base.as_usize() + PAGE_SIZE] + .as_ptr() as *const StaticCryptoData + }; + let scd: &StaticCryptoData = unsafe { scd_ptr.as_ref().unwrap() }; scd } - #[cfg(feature="migration1")] - /// needs to reside in this object because it accesses the key_phys_base registers, which have good reason to be private - /// this needs to return a full copy of the data, because the copy on disk is going away. + + #[cfg(feature = "migration1")] + /// needs to reside in this object because it accesses the key_phys_base registers, which have good reason + /// to be private this needs to return a full copy of the data, because the copy on disk is going + /// away. fn static_crypto_data_get_v1(&self) -> StaticCryptoDataV1 { let mut scd = StaticCryptoDataV1::default(); - let scd_ptr = self.pddb_mr.as_slice::()[self.key_phys_base.as_usize()..self.key_phys_base.as_usize() + PAGE_SIZE].as_ptr() as *const StaticCryptoDataV1; - let scd_flash: &StaticCryptoDataV1 = unsafe{scd_ptr.as_ref().unwrap()}; + let scd_ptr = self.pddb_mr.as_slice::() + [self.key_phys_base.as_usize()..self.key_phys_base.as_usize() + PAGE_SIZE] + .as_ptr() as *const StaticCryptoDataV1; + let scd_flash: &StaticCryptoDataV1 = unsafe { scd_ptr.as_ref().unwrap() }; scd.version = scd_flash.version; scd.system_key = scd_flash.system_key; scd.salt_base = scd_flash.salt_base; scd } + /// takes the key and writes it with zero, using hard pointer math and a compiler fence to ensure /// the wipe isn't optimized out. #[allow(dead_code)] @@ -664,14 +770,15 @@ impl PddbOs { self.system_basis_key = None; self.cipher_ecb = None; } - pub(crate) fn clear_password(&self) { - self.rootkeys.clear_password(AesRootkeyType::User0); - } - pub (crate) fn try_login(&mut self) -> PasswordState { + + pub(crate) fn clear_password(&self) { self.rootkeys.clear_password(AesRootkeyType::User0); } + + pub(crate) fn try_login(&mut self) -> PasswordState { if self.system_basis_key.is_none() || self.cipher_ecb.is_none() { let scd = self.static_crypto_data_get(); - if scd.version == 0xFFFF_FFFF { // system is in the blank state - return PasswordState::Uninit + if scd.version == 0xFFFF_FFFF { + // system is in the blank state + return PasswordState::Uninit; } if scd.version != SCD_VERSION { log::error!("Version mismatch for keystore, declaring database as uninitialized"); @@ -718,7 +825,7 @@ impl PddbOs { self.failed_logins = self.failed_logins.saturating_add(1); return PasswordState::Incorrect(self.failed_logins); } - } + }, } match self.rootkeys.unwrap_key(&scd.system_key, AES_KEYSIZE) { Ok(sk) => syskey.copy_from_slice(&sk), @@ -734,7 +841,7 @@ impl PddbOs { self.failed_logins = self.failed_logins.saturating_add(1); return PasswordState::Incorrect(self.failed_logins); } - } + }, } if keys_updated { log::warn!("Migration event from incorrectly wrapped key"); @@ -750,20 +857,15 @@ impl PddbOs { let mut system_key: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; system_key.copy_from_slice(&syskey); - self.system_basis_key = Some( - BasisKeys { - data: system_key, - pt: system_key_pt, - } - ); + self.system_basis_key = Some(BasisKeys { data: system_key, pt: system_key_pt }); // erase the plaintext keys let nuke = syskey.as_mut_ptr(); for i in 0..syskey.len() { - unsafe{nuke.add(i).write_volatile(0)}; + unsafe { nuke.add(i).write_volatile(0) }; } let nuke = syskey_pt.as_mut_ptr(); for i in 0..syskey_pt.len() { - unsafe{nuke.add(i).write_volatile(0)}; + unsafe { nuke.add(i).write_volatile(0) }; } self.failed_logins = 0; PasswordState::Correct @@ -772,18 +874,25 @@ impl PddbOs { PasswordState::Correct } } + fn syskey_ensure(&mut self) { while self.try_login() != PasswordState::Correct { self.clear_password(); // clear the bad password entry let xns = xous_names::XousNames::new().unwrap(); let modals = modals::Modals::new(&xns).expect("can't connect to Modals server"); - modals.show_notification(t!("pddb.badpass_infallible", locales::LANG), None).expect("notification failed"); + modals + .show_notification(t!("pddb.badpass_infallible", locales::LANG), None) + .expect("notification failed"); } } fn mbbb_as_slice(&self) -> &[u8] { - unsafe { &self.pddb_mr.as_slice()[self.mbbb_phys_base.as_usize()..self.mbbb_phys_base.as_usize() + MBBB_PAGES * PAGE_SIZE] } + unsafe { + &self.pddb_mr.as_slice() + [self.mbbb_phys_base.as_usize()..self.mbbb_phys_base.as_usize() + MBBB_PAGES * PAGE_SIZE] + } } + fn mbbb_retrieve(&self) -> Option<&[u8]> { // Invariant: MBBB pages should be blank, unless a page is stashed there. So, just check the first // AES key size region for "blankness" @@ -792,11 +901,12 @@ impl PddbOs { if page[..aes::BLOCK_SIZE] == blank { continue; } else { - return Some(page) + return Some(page); } } None } + fn mbbb_erase(&self) { let blank = [0xffu8; aes::BLOCK_SIZE]; for (index, page) in self.mbbb_as_slice().chunks(PAGE_SIZE).enumerate() { @@ -819,7 +929,7 @@ impl PddbOs { /// the desired target DNA to effectively bypass the check (you need to, of course, /// know what that DNA is in the first place, so hopefully you were able to extract /// it before you destroyed the FPGA). - fn fast_space_aad(&self, aad: &mut Vec::) { + fn fast_space_aad(&self, aad: &mut Vec) { aad.extend_from_slice(PDDB_FAST_SPACE_SYSTEM_BASIS.as_bytes()); aad.extend_from_slice(&PDDB_VERSION.to_le_bytes()); match self.dna_mode { @@ -827,6 +937,7 @@ impl PddbOs { DnaMode::Migration => aad.extend_from_slice(&self.migration_dna.to_le_bytes()), } } + /// Assumes you are writing a "most recent" version of FastSpace. Thus /// Anytime the fscb is updated, all the partial records are nuked, as well as any existing record. /// Then, a _random_ location is picked to place the structure to help with wear levelling. @@ -837,15 +948,14 @@ impl PddbOs { let nonce_array = self.entropy.borrow_mut().get_nonce(); let nonce = Nonce::from_slice(&nonce_array); let fs_ser: &[u8] = fs.deref(); - assert!( ((fs_ser.len() + size_of::() + size_of::()) & (PAGE_SIZE - 1)) == 0, - "FastSpace record is not page-aligned in size!"); + assert!( + ((fs_ser.len() + size_of::() + size_of::()) & (PAGE_SIZE - 1)) == 0, + "FastSpace record is not page-aligned in size!" + ); // AAD + data => Payload let mut aad = Vec::::new(); self.fast_space_aad(&mut aad); - let payload = Payload { - msg: fs_ser, - aad: &aad, - }; + let payload = Payload { msg: fs_ser, aad: &aad }; // these are useful for debug, but do a hard-comment on them because they leak secret info //log::info!("key: {:x?}", key); //log::info!("nonce: {:x?}", nonce); @@ -856,47 +966,76 @@ impl PddbOs { //log::info!("mac: {:x?}", &ciphertext[ciphertext.len()-16..]); let ct_to_flash = ciphertext.deref(); // determine which page we're going to write the ciphertext into - let page_search_limit = FSCB_PAGES - ((PageAlignedPa::from(ciphertext.len()).as_usize() / PAGE_SIZE) - 1); + let page_search_limit = + FSCB_PAGES - ((PageAlignedPa::from(ciphertext.len()).as_usize() / PAGE_SIZE) - 1); let dest_page_start = self.entropy.borrow_mut().get_u32() % page_search_limit as u32; // atomicity of the FreeSpace structure is a bit of a tough topic. It's a fairly hefty structure, // that runs a risk of corruption as it's being written, if power is lost or the system crashes. - // However, the guiding principle of this ordering is that it's better to have no FastSpace structure - // (and force a re-computation of it by scanning all the open Basis), than it is to have a broken - // FastSpace structure + stale SpaceUpdates. In particular a stale SpaceUpdate would lead the system - // to conclude that some pages are free when they aren't. Thus, we prefer to completely erase the - // FSCB region before committing the updated version. + // However, the guiding principle of this ordering is that it's better to have no FastSpace + // structure (and force a re-computation of it by scanning all the open Basis), than + // it is to have a broken FastSpace structure + stale SpaceUpdates. In particular a + // stale SpaceUpdate would lead the system to conclude that some pages are free when + // they aren't. Thus, we prefer to completely erase the FSCB region before committing + // the updated version. let patch_data = [&nonce_array, ct_to_flash].concat(); assert!(patch_data.len() % PAGE_SIZE == 0); // should be guaranteed by design, if not, throw an error during early testing. let dest_page_end = dest_page_start + (patch_data.len() / PAGE_SIZE) as u32; - log::info!("picking random pages [{}-{}) out of {} pages for fscb", dest_page_start, dest_page_end, FSCB_PAGES); - { // this is where we begin the "it would be bad if we lost power about now" code region + log::info!( + "picking random pages [{}-{}) out of {} pages for fscb", + dest_page_start, + dest_page_end, + FSCB_PAGES + ); + { + // this is where we begin the "it would be bad if we lost power about now" code region let blank_sector: [u8; PAGE_SIZE] = [0xff; PAGE_SIZE]; // prep a "blank" page for the loop for offset in 0..FSCB_PAGES as u32 { if offset >= dest_page_start && offset < dest_page_end { - // patch the FSCB data in when we find it, and skip on later iterations that are within the page range of the FSCB + // patch the FSCB data in when we find it, and skip on later iterations that are + // within the page range of the FSCB if offset == dest_page_start { self.patch_fscb(&patch_data, dest_page_start * PAGE_SIZE as u32); - // Catch cache coherence issues. For now, we do a "hard panic" so that we know there is a cache coherence problem. - // We could "paper over" this by re-reading the data, but really, this problem should be solved by the d-cache - // flush in the SPINOR primitive. + // Catch cache coherence issues. For now, we do a "hard panic" so that we know + // there is a cache coherence problem. + // We could "paper over" this by re-reading the data, but really, this problem + // should be solved by the d-cache flush in the SPINOR + // primitive. let page_start = dest_page_start as usize * PAGE_SIZE; - let fscb_slice = &unsafe { self.pddb_mr.as_slice() }[self.fscb_phys_base.as_usize()..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE]; + let fscb_slice = + &unsafe { self.pddb_mr.as_slice() }[self.fscb_phys_base.as_usize() + ..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE]; let mut fscb_buf = [0; FASTSPACE_PAGES * PAGE_SIZE - size_of::()]; // copy the encrypted data to the decryption buffer - fscb_buf.copy_from_slice(&fscb_slice[page_start + size_of::() .. page_start + FASTSPACE_PAGES * PAGE_SIZE]); + fscb_buf.copy_from_slice( + &fscb_slice[page_start + size_of::() + ..page_start + FASTSPACE_PAGES * PAGE_SIZE], + ); let mut errcount = 0; - for (index, (&buf, &flash)) in fscb_buf.iter().zip(&patch_data[size_of::()..]).enumerate() { + for (index, (&buf, &flash)) in + fscb_buf.iter().zip(&patch_data[size_of::()..]).enumerate() + { if buf != flash { if errcount < 128 { - log::warn!("Cache coherence issue at 0x{:x}: buf 0x{:x} -> rbk 0x{:x}", index, buf, flash); + log::warn!( + "Cache coherence issue at 0x{:x}: buf 0x{:x} -> rbk 0x{:x}", + index, + buf, + flash + ); } errcount += 1; } } if errcount != 0 { - log::warn!("Cache coherence issue detected: total buf<->rbk errors: {}", errcount); - panic!("FSCB write to pages [{}-{}) failed: cache coherence failure, with {} errors", dest_page_start, dest_page_end, errcount); + log::warn!( + "Cache coherence issue detected: total buf<->rbk errors: {}", + errcount + ); + panic!( + "FSCB write to pages [{}-{}) failed: cache coherence failure, with {} errors", + dest_page_start, dest_page_end, errcount + ); } /* let mut daad = Vec::::new(); @@ -967,29 +1106,19 @@ impl PddbOs { log::info!("patch data len: {}", patch_data.len()); } // end "it would be bad if we lost power now" region - // note: this function should be followed up by a fast_space_read() to regenerate the temporary - // bookkeeping variables that are not reset by this function. + // note: this function should be followed up by a fast_space_read() to regenerate the temporary + // bookkeeping variables that are not reset by this function. } else { panic!("invalid state!"); } } - #[cfg(feature="hwtest")] + #[cfg(feature = "hwtest")] pub fn stresstest_read(&mut self, pagecount: u32, iters: u32) -> u32 { - let pagecount = if pagecount == 0 { - 2 - } else { - pagecount - }; - let iters = if iters == 0 { - 16 - } else { - iters - }; - let test_slice = &self.pddb_mr.as_slice()[ - self.data_phys_base.as_usize().. - self.data_phys_base.as_usize() + pagecount as usize * PAGE_SIZE - ]; + let pagecount = if pagecount == 0 { 2 } else { pagecount }; + let iters = if iters == 0 { 16 } else { iters }; + let test_slice = &self.pddb_mr.as_slice() + [self.data_phys_base.as_usize()..self.data_phys_base.as_usize() + pagecount as usize * PAGE_SIZE]; let mut total_errs = 0; for attempt in 0..iters { log::info!("Attempt {} of {}", attempt + 1, iters); @@ -1000,7 +1129,13 @@ impl PddbOs { if flash != mem { errcount += 1; if errcount < 128 { - log::warn!("Attempt {}: Mismatch at 0x:{:x}: flash:{:x} <-> mem {:x}", attempt, index, flash, mem); + log::warn!( + "Attempt {}: Mismatch at 0x:{:x}: flash:{:x} <-> mem {:x}", + attempt, + index, + flash, + mem + ); } } } @@ -1009,6 +1144,7 @@ impl PddbOs { } total_errs } + /// Sweeps through the entire set of known data (as indicated in `page_heap`) and /// returns a subset of the total free space in a PhysPage vector that is a list of physical pages, /// in random order, that can be used by PDDB operations in the future without worry about @@ -1018,7 +1154,7 @@ impl PddbOs { /// needs to run in a fairly memory-constrained environment, keeping in mind that if the PDDB /// structures were to be extended to run on say, an external USB drive with gigabytes of space, /// we cannot afford to naively allocate vectors that count every single page. - fn fast_space_generate(&mut self, mut page_heap: BinaryHeap>) -> Vec:: { + fn fast_space_generate(&mut self, mut page_heap: BinaryHeap>) -> Vec { let mut free_pool = Vec::::new(); let max_entries = FASTSPACE_PAGES * PAGE_SIZE / size_of::(); free_pool.reserve_exact(max_entries); @@ -1066,12 +1202,13 @@ impl PddbOs { // https://github.com/rust-random/rand/blob/0f4fc6b4c303696bd5f8765a375162ac7142b1df/src/seq/mod.rs#L586-L592 for i in (1..free_pool.len()).rev() { // invariant: elements with index > i have been locked in place. - free_pool.swap(i, self.entropy.borrow_mut().get_u32() as usize % (i+1)); + free_pool.swap(i, self.entropy.borrow_mut().get_u32() as usize % (i + 1)); } log::info!("free_pool initial count: {}", free_pool.len()); // 4. ensure that the free pool stays within the defined deniability ratio + noise - let mut noise = (self.entropy.borrow_mut().get_u32() as f32 / u32::MAX as f32) * FSCB_FILL_UNCERTAINTY; + let mut noise = + (self.entropy.borrow_mut().get_u32() as f32 / u32::MAX as f32) * FSCB_FILL_UNCERTAINTY; if self.entropy.borrow_mut().get_u8() > 127 { noise = -noise; } @@ -1079,8 +1216,12 @@ impl PddbOs { // we're guaranteed to have at least one free page, because we errored out if the pages was 0 above. let deniable_free_pages = if deniable_free_pages == 0 { 1 } else { deniable_free_pages }; free_pool.truncate(deniable_free_pages); - log::warn!("total_free: {}; free_pool after PD trim: {}; max pages allowed: {}", - total_free_pages, free_pool.len(), deniable_free_pages); + log::warn!( + "total_free: {}; free_pool after PD trim: {}; max pages allowed: {}", + total_free_pages, + free_pool.len(), + deniable_free_pages + ); // 5. Take the free_pool and annotate it for writing to disk let mut page_pool = Vec::::new(); @@ -1096,15 +1237,20 @@ impl PddbOs { } fn fscb_deref(&self) -> &[u8] { - unsafe { &self.pddb_mr.as_slice()[self.fscb_phys_base.as_usize()..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE] } + unsafe { + &self.pddb_mr.as_slice() + [self.fscb_phys_base.as_usize()..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE] + } } + /// Reads the data structures in the FSCB, if any, and stores the results in the fspace_cache HashSet. - /// Note the following convention on the fscb: if the first 128 bits of a page are all 1's, then that sector - /// cannot contain the master FastSpace record. Also, if a sector is to contain *any* data, the first piece - /// of data must start at exactly 16 bytes into the page (at the 129th bit). Examples: + /// Note the following convention on the fscb: if the first 128 bits of a page are all 1's, then that + /// sector cannot contain the master FastSpace record. Also, if a sector is to contain *any* data, the + /// first piece of data must start at exactly 16 bytes into the page (at the 129th bit). Examples: /// /// FF = must be all 1's - /// xx/yy/zz = AES encrypted data. Techincally AES includes the all 1's ciphertext in its set, but it's extremely unlikely. + /// xx/yy/zz = AES encrypted data. Techincally AES includes the all 1's ciphertext in its set, but it's + /// extremely unlikely. /// /// Byte # /// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | 10 | 11 | 12 | 13 | 14 | ... # byte offset @@ -1112,7 +1258,6 @@ impl PddbOs { /// | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | ... # page must be blank /// | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | yy | yy | yy | yy | yy | ... # page must contain FastSpace record /// | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | FF | zz | zz | zz | zz | zz | ... # page contains an arbitrary number of SpaceUpdate records - /// fn fast_space_read(&mut self) { self.syskey_ensure(); if let Some(system_key) = &self.system_basis_key { @@ -1122,10 +1267,15 @@ impl PddbOs { self.fspace_log_next_addr = None; self.fspace_log_len = 0; - // let fscb_slice = self.fscb_deref(); // can't use this line because it causse self to be immutably borrowed, so we write out the equivalent below. - let fscb_slice = unsafe { &self.pddb_mr.as_slice()[self.fscb_phys_base.as_usize()..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE] }; + // let fscb_slice = self.fscb_deref(); // can't use this line because it causse self to be + // immutably borrowed, so we write out the equivalent below. + let fscb_slice = unsafe { + &self.pddb_mr.as_slice() + [self.fscb_phys_base.as_usize()..self.fscb_phys_base.as_usize() + FSCB_PAGES * PAGE_SIZE] + }; - // 1. scan through the entire space, and look for the FastSpace record. It can be identified by the + // 1. scan through the entire space, and look for the FastSpace record. It can be identified by + // the // first 16 (aes::BLOCK_SIZE) bytes not being all 1's. let blank: [u8; aes::BLOCK_SIZE] = [0xff; aes::BLOCK_SIZE]; let mut fscb_pages = 0; @@ -1190,9 +1340,11 @@ impl PddbOs { let mut block = Block::default(); log::info!("space_log_addrs len: {}", self.fspace_log_addrs.len()); for page in &self.fspace_log_addrs { - for (index, ct_block) in - fscb_slice[page.as_usize() + aes::BLOCK_SIZE .. page.as_usize() + PAGE_SIZE] - .chunks_exact(aes::BLOCK_SIZE).enumerate() { + for (index, ct_block) in fscb_slice + [page.as_usize() + aes::BLOCK_SIZE..page.as_usize() + PAGE_SIZE] + .chunks_exact(aes::BLOCK_SIZE) + .enumerate() + { let mut is_blank = true; for &ct in ct_block { if ct != 0xFF { @@ -1201,7 +1353,8 @@ impl PddbOs { } if is_blank { // end the scan at the first blank block. Note the location. - self.fspace_log_next_addr = Some((page.as_usize() + ((1 + index) * aes::BLOCK_SIZE)) as PhysAddr); + self.fspace_log_next_addr = + Some((page.as_usize() + ((1 + index) * aes::BLOCK_SIZE)) as PhysAddr); break; } // now try to decrypt the ciphertext block @@ -1211,19 +1364,30 @@ impl PddbOs { cipher.decrypt_block(&mut block); if let Some(pp) = SpaceUpdate::try_into_phys_page(block.as_slice()) { log::debug!("maybe replacing fspace block: {:x?}", pp); - // note: pp.valid() isn't the cryptographic check, the cryptographic check of record validity is in try_into_phys_page() - if pp.valid() { // PS: it should always be valid! + // note: pp.valid() isn't the cryptographic check, the cryptographic check of record + // validity is in try_into_phys_page() + if pp.valid() { + // PS: it should always be valid! if let Some(prev_pp) = self.fspace_cache.get(&pp) { if pp.journal() > prev_pp.journal() { self.fspace_cache.replace(pp); } else if pp.journal() == prev_pp.journal() { - log::error!("got two identical journal revisions -- this shouldn't happen\n{:x?} (prev)\n{:x?}(candidate)", prev_pp, pp); - log::error!("replacing the previous version with the candidate! wish us luck."); + log::error!( + "got two identical journal revisions -- this shouldn't happen\n{:x?} (prev)\n{:x?}(candidate)", + prev_pp, + pp + ); + log::error!( + "replacing the previous version with the candidate! wish us luck." + ); self.fspace_cache.replace(pp); } } else { // this happens when the FSCB is flushed, and then a page is deleted. - log::debug!("Journal entry for a free space page that isn't already in our cache {:?}", pp); + log::debug!( + "Journal entry for a free space page that isn't already in our cache {:?}", + pp + ); self.fspace_cache.insert(pp); } } @@ -1241,19 +1405,22 @@ impl PddbOs { // pick a random page out of the blank pool (random for wear levelling) let random_index = self.entropy.borrow_mut().get_u32() as usize % blank_pages.len(); // set the next log address at an offset of one AES block in from the top. - self.fspace_log_next_addr = Some((blank_pages[random_index] + aes::BLOCK_SIZE) as PhysAddr); + self.fspace_log_next_addr = + Some((blank_pages[random_index] + aes::BLOCK_SIZE) as PhysAddr); } else { - log::warn!("FSCB has no blank space for new update records. This will cause fast_space_alloc() to fail, which can be remedied with a call to fast_space_generate()."); + log::warn!( + "FSCB has no blank space for new update records. This will cause fast_space_alloc() to fail, which can be remedied with a call to fast_space_generate()." + ); } } } else { panic!("invalid state!"); } } + /// returns a count of the number of pages in the fspace cache - pub fn fast_space_len(&self) -> usize { - self.fspace_cache.len() - } + pub fn fast_space_len(&self) -> usize { self.fspace_cache.len() } + /// Normally, the fspace_log_next_addr is just incremented, but when it hits the end of the /// page, it's set to None. This function will do a modestly expensive scan of the FSCB area /// to try and either find another partially filled page, or a completely empty page. @@ -1266,33 +1433,40 @@ impl PddbOs { let mut blank_pages = Vec::new(); for page_start in (0..fscb_slice.len()).step_by(PAGE_SIZE) { if (fscb_slice[page_start..page_start + aes::BLOCK_SIZE] == blank) - && (fscb_slice[page_start + aes::BLOCK_SIZE..page_start + aes::BLOCK_SIZE * 2] == blank) { + && (fscb_slice[page_start + aes::BLOCK_SIZE..page_start + aes::BLOCK_SIZE * 2] == blank) + { // page has met the criteria for being blank, skip to the next page blank_pages.push(page_start); - continue + continue; } else if fscb_slice[page_start..page_start + aes::BLOCK_SIZE] == blank { // this page contains update records; scan it for an empty slot - for (index, block) in - fscb_slice[page_start + aes::BLOCK_SIZE..page_start + PAGE_SIZE] - .chunks_exact(aes::BLOCK_SIZE).enumerate() { - // start with a size check; a failure mode of just iterating is the iterator will terminate early if the block sizes aren't the same. + for (index, block) in fscb_slice[page_start + aes::BLOCK_SIZE..page_start + PAGE_SIZE] + .chunks_exact(aes::BLOCK_SIZE) + .enumerate() + { + // start with a size check; a failure mode of just iterating is the iterator will + // terminate early if the block sizes aren't the same. let mut is_blank = block.len() == blank.len(); // now confirm that every item is the same for (&a, &b) in block.iter().zip(blank.iter()) { - if a != b {is_blank = false;} + if a != b { + is_blank = false; + } } if is_blank { - self.fspace_log_next_addr = Some( (page_start + (1 + index) * aes::BLOCK_SIZE) as PhysAddr ); + self.fspace_log_next_addr = + Some((page_start + (1 + index) * aes::BLOCK_SIZE) as PhysAddr); log::info!("Next FSCB entry: {:x?}", self.fspace_log_next_addr); - return true + return true; } } } else { // this is probably an encrypted FastSpace page, just skip it - continue + continue; } } - // if we got to this point, we couldn't find a partially full page. Pull a random page from the blank page pool. + // if we got to this point, we couldn't find a partially full page. Pull a random page from the + // blank page pool. if blank_pages.len() != 0 { // pick a random page out of the blank pool (random for wear levelling) let random_index = self.entropy.borrow_mut().get_u32() as usize % blank_pages.len(); @@ -1305,28 +1479,33 @@ impl PddbOs { } } } + pub fn fast_space_has_pages(&self, count: usize) -> bool { let mut free_count = 0; for pp in self.fspace_cache.iter() { - if (pp.space_state() == SpaceState::Free || pp.space_state() == SpaceState::Dirty) && (pp.journal() < PHYS_PAGE_JOURNAL_MAX) { + if (pp.space_state() == SpaceState::Free || pp.space_state() == SpaceState::Dirty) + && (pp.journal() < PHYS_PAGE_JOURNAL_MAX) + { free_count += 1; } else { log::trace!("fastpace other entry: {:?}", pp.space_state()); } if free_count >= count { - return true + return true; } } false } - /// Attempts to allocate a page out of the fspace cache (in RAM). This is the "normal" route for allocating pages. - /// This call should be prefixed by a call to ensure_fast_space_alloc() to make sure it doesn't fail. - /// We do a two-stage "look before you leap" because trying to dynamically redo the fast space allocation tables - /// in the middle of trying to allocate pages causes a borrow checker problem -- because you're already using the - /// page maps to figure out you ran out of space, but then you're trying to mutate them to allocate more space. - /// This can lead to concurrency issues, so we do a "look before you leap" method instead, where we just check that - /// the correct amount of free space is available before doing an allocation, and if not, we mutate the map to populate - /// new free space; and if so, we mutate the map to remove the allocated page. + + /// Attempts to allocate a page out of the fspace cache (in RAM). This is the "normal" route for + /// allocating pages. This call should be prefixed by a call to ensure_fast_space_alloc() to make sure + /// it doesn't fail. We do a two-stage "look before you leap" because trying to dynamically redo the + /// fast space allocation tables in the middle of trying to allocate pages causes a borrow checker + /// problem -- because you're already using the page maps to figure out you ran out of space, but then + /// you're trying to mutate them to allocate more space. This can lead to concurrency issues, so we do + /// a "look before you leap" method instead, where we just check that the correct amount of free space + /// is available before doing an allocation, and if not, we mutate the map to populate new free space; + /// and if so, we mutate the map to remove the allocated page. pub fn try_fast_space_alloc(&mut self) -> Option { // 1. Confirm that the fspace_log_next_addr is valid. If not, regenerate it, or fail. if !self.fast_space_ensure_next_log() { @@ -1334,15 +1513,18 @@ impl PddbOs { None } else { // 2. find the first page that is Free or Dirty. - // We made a mistake early on and assumed that the HashSet underlying type would have enough randomness, - // but unfortunately, it tends to be an MRU list of pages: so, recently de-allocated pages are returned - // preferentially. This is the opposite of what we want. Thus, we are now paying a bit of a price in - // computational efficiency to map the HashSet into a Vec and then pick a random entry to get the - // propreties that we were originally hoping for :-/ + // We made a mistake early on and assumed that the HashSet underlying type would have enough + // randomness, but unfortunately, it tends to be an MRU list of pages: so, recently + // de-allocated pages are returned preferentially. This is the opposite of what we + // want. Thus, we are now paying a bit of a price in computational efficiency to map + // the HashSet into a Vec and then pick a random entry to get the propreties that we + // were originally hoping for :-/ let mut maybe_alloc = None; let mut candidates = Vec::::new(); for pp in self.fspace_cache.iter() { - if (pp.space_state() == SpaceState::Free || pp.space_state() == SpaceState::Dirty) && (pp.journal() < PHYS_PAGE_JOURNAL_MAX) { + if (pp.space_state() == SpaceState::Free || pp.space_state() == SpaceState::Dirty) + && (pp.journal() < PHYS_PAGE_JOURNAL_MAX) + { candidates.push(pp.clone()); } } @@ -1350,9 +1532,10 @@ impl PddbOs { if candidates.len() > 0 { let mut ppc = candidates[self.entropy.borrow_mut().get_u32() as usize % candidates.len()]; // take the state directly to Used, skipping MaybeUsed. If the system crashes between now and - // when the page is actually used, the consequence is a "lost" entry in the FastSpace cache. However, - // the entry will be reclaimed on the next full-space scan. This is a less-bad outcome than filling up - // the log with 2x the number of operations to record MaybeUsed and then Used. + // when the page is actually used, the consequence is a "lost" entry in the FastSpace cache. + // However, the entry will be reclaimed on the next full-space scan. This is a + // less-bad outcome than filling up the log with 2x the number of operations + // to record MaybeUsed and then Used. ppc.set_space_state(SpaceState::Used); ppc.set_clean(false); // the allocated page is not clean, because it hasn't been written to disk ppc.set_valid(true); // the allocated page is now valid, so it should be flushed to disk @@ -1360,7 +1543,8 @@ impl PddbOs { // commit the usage to the journal self.syskey_ensure(); - let cipher = self.cipher_ecb.as_ref().expect("Inconsistent internal state - syskey_ensure() failed"); + let cipher = + self.cipher_ecb.as_ref().expect("Inconsistent internal state - syskey_ensure() failed"); let mut update = SpaceUpdate::new(self.entropy.borrow_mut().get_u64(), ppc); let mut block = Block::from_mut_slice(update.deref_mut()); log::trace!("block: {:x?}", block); @@ -1373,8 +1557,8 @@ impl PddbOs { if (next_addr & (PAGE_SIZE as PhysAddr - 1)) != 0 { self.fspace_log_next_addr = Some(next_addr as PhysAddr); } else { - // fspace_log_next_addr is already None because we used "take()". We'll find a free spot for the - // next journal entry the next time around. + // fspace_log_next_addr is already None because we used "take()". We'll find a free spot + // for the next journal entry the next time around. } maybe_alloc = Some(ppc); } @@ -1385,11 +1569,15 @@ impl PddbOs { //} } if let Some(alloc) = maybe_alloc { - assert!(self.fspace_cache.remove(&alloc), "inconsistent state: we found a free page, but later when we tried to update it, it wasn't there!"); + assert!( + self.fspace_cache.remove(&alloc), + "inconsistent state: we found a free page, but later when we tried to update it, it wasn't there!" + ); } maybe_alloc } } + pub fn fast_space_free(&mut self, pp: &mut PhysPage) { self.fast_space_ensure_next_log(); if !self.fspace_cache.remove(&pp) { @@ -1424,16 +1612,24 @@ impl PddbOs { // mark the page as invalid, so that it will be deleted on the next PT sync pp.set_valid(false); } + /// This is a "look before you leap" function that will potentially pause all system operations /// and do a deep scan for space if the required amount is not available. - pub fn ensure_fast_space_alloc(&mut self, pages: usize, cache: &Vec::) -> bool { + pub fn ensure_fast_space_alloc(&mut self, pages: usize, cache: &Vec) -> bool { const BUFFER: usize = 1; // a bit of slop in the trigger point let has_pages = self.fast_space_has_pages(pages + BUFFER); - log::trace!("alloc fast_space_len: {}, log_len {}, has {} pages: {}", self.fast_space_len(), self.fspace_log_len, pages + BUFFER, has_pages); + log::trace!( + "alloc fast_space_len: {}, log_len {}, has {} pages: {}", + self.fast_space_len(), + self.fspace_log_len, + pages + BUFFER, + has_pages + ); // make sure we have fast space pages... if has_pages // ..and make sure we have space for fast space log entries - && (self.fspace_log_len < (FSCB_PAGES - FASTSPACE_PAGES - 1) * PAGE_SIZE / aes::BLOCK_SIZE) { + && (self.fspace_log_len < (FSCB_PAGES - FASTSPACE_PAGES - 1) * PAGE_SIZE / aes::BLOCK_SIZE) + { true } else { if !has_pages { @@ -1445,9 +1641,7 @@ impl PddbOs { // we're out of free space false } else { - let mut fast_space = FastSpace { - free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN], - }; + let mut fast_space = FastSpace { free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN] }; for pp in fast_space.free_pool.iter_mut() { pp.set_journal(self.trng_u8() % FSCB_JOURNAL_RAND_RANGE) } @@ -1461,11 +1655,7 @@ impl PddbOs { self.fast_space_read(); // check that we have enough space now -- if not, we're just out of disk space! - if self.fast_space_len() > pages { - true - } else { - false - } + if self.fast_space_len() > pages { true } else { false } } } else { false @@ -1479,11 +1669,10 @@ impl PddbOs { } } } + /// This is a "fast" flush that expires all the PDDB SpaceUpdate journal pub(crate) fn fast_space_flush(&mut self) { - let mut fast_space = FastSpace { - free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN], - }; + let mut fast_space = FastSpace { free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN] }; for pp in fast_space.free_pool.iter_mut() { pp.set_journal(self.trng_u8() % FSCB_JOURNAL_RAND_RANGE) } @@ -1496,14 +1685,15 @@ impl PddbOs { // write just commits a new record to disk, but doesn't update our internal data cache // this also clears the fast space log. self.fast_space_write(&fast_space); - // this will re-read back in the data, shuffle the alloc order a bit, and ensure the data cache is fully in sync + // this will re-read back in the data, shuffle the alloc order a bit, and ensure the data cache is + // fully in sync self.fast_space_read(); // this will locate the next fast space log point. self.fast_space_ensure_next_log(); log::info!("Flush took {}ms", self.timestamp_now() - start); } - pub(crate) fn data_aad(&self, name: &str) -> Vec:: { + pub(crate) fn data_aad(&self, name: &str) -> Vec { let mut aad = Vec::::new(); aad.extend_from_slice(&name.as_bytes()); aad.extend_from_slice(&PDDB_VERSION.to_le_bytes()); @@ -1515,25 +1705,28 @@ impl PddbOs { } /// returns a decrypted page that still includes the journal number at the very beginning - /// We don't clip it off because it would require re-allocating a vector, and it's cheaper (although less elegant) to later - /// just index past it. - pub(crate) fn data_decrypt_page(&self, cipher: &Aes256GcmSiv, aad: &[u8], page: &PhysPage) -> Option> { - let ct_slice = unsafe { &self.pddb_mr.as_slice()[ - self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE .. - self.data_phys_base.as_usize() + (page.page_number() as usize + 1) * PAGE_SIZE] }; + /// We don't clip it off because it would require re-allocating a vector, and it's cheaper (although less + /// elegant) to later just index past it. + pub(crate) fn data_decrypt_page( + &self, + cipher: &Aes256GcmSiv, + aad: &[u8], + page: &PhysPage, + ) -> Option> { + let ct_slice = unsafe { + &self.pddb_mr.as_slice()[self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE + ..self.data_phys_base.as_usize() + (page.page_number() as usize + 1) * PAGE_SIZE] + }; let nonce = &ct_slice[..size_of::()]; let ct = &ct_slice[size_of::()..]; - match cipher.decrypt( - Nonce::from_slice(nonce), - Payload { - aad, - msg: ct, - } - ) { + match cipher.decrypt(Nonce::from_slice(nonce), Payload { aad, msg: ct }) { Ok(data) => { - assert!(data.len() == VPAGE_SIZE + size_of::(), "authentication successful, but wrong amount of data was recovered"); + assert!( + data.len() == VPAGE_SIZE + size_of::(), + "authentication successful, but wrong amount of data was recovered" + ); Some(data) - }, + } Err(e) => { log::trace!("Error decrypting page: {:?}", e); // sometimes this is totally "normal", like when we're testing for valid data. None @@ -1551,17 +1744,26 @@ impl PddbOs { /// - kcomm_nonce - 32 bytes /// - kcomm - 32 bytes /// - MAC - 16 bytes - /// We stripe the MAC at the end just in case the MAC has some arithmetic property that can betray the existence - /// of a basis root record with key commitment. The committed key and the nonce both should be indistinguishable - /// from ciphertext. - pub(crate) fn data_decrypt_page_with_commit(&self, key: &[u8], aad: &[u8], page: &PhysPage) -> Option> { + /// We stripe the MAC at the end just in case the MAC has some arithmetic property that can betray the + /// existence of a basis root record with key commitment. The committed key and the nonce both should + /// be indistinguishable from ciphertext. + pub(crate) fn data_decrypt_page_with_commit( + &self, + key: &[u8], + aad: &[u8], + page: &PhysPage, + ) -> Option> { const KCOM_NONCE_LEN: usize = 32; const KCOM_LEN: usize = 32; const MAC_LEN: usize = 16; - let ct_slice = unsafe { &self.pddb_mr.as_slice()[ - self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE .. - self.data_phys_base.as_usize() + (page.page_number() as usize + 1) * PAGE_SIZE] }; - log::debug!("commit data at 0x{:x}", self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE); + let ct_slice = unsafe { + &self.pddb_mr.as_slice()[self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE + ..self.data_phys_base.as_usize() + (page.page_number() as usize + 1) * PAGE_SIZE] + }; + log::debug!( + "commit data at 0x{:x}", + self.data_phys_base.as_usize() + page.page_number() as usize * PAGE_SIZE + ); let nonce = &ct_slice[..size_of::()]; let ct_total = &ct_slice[size_of::()..]; @@ -1587,7 +1789,10 @@ impl PddbOs { *dst = src; ct_pos += 1; } - assert!(ct_pos == PAGE_SIZE - size_of::(), "struct sizing error in unpacking page with key commit"); + assert!( + ct_pos == PAGE_SIZE - size_of::(), + "struct sizing error in unpacking page with key commit" + ); log::debug!("found nonce of {:x?}", nonce); log::debug!("found kcom_nonce of {:x?}", nonce_comm); @@ -1595,47 +1800,52 @@ impl PddbOs { let cipher = Aes256GcmSiv::new(&kenc.into()); // Attempt decryption. This is None on failure - let plaintext = cipher.decrypt( - Nonce::from_slice(nonce), - Payload { - aad, - msg: &ct_plus_mac, - } - ).ok(); + let plaintext = cipher.decrypt(Nonce::from_slice(nonce), Payload { aad, msg: &ct_plus_mac }).ok(); // Only return the plaintext if the stored key commitment agrees with the computed one - if kcom.ct_eq(&key_comm_stored).into() { - plaintext - } else { - None - } + if kcom.ct_eq(&key_comm_stored).into() { plaintext } else { None } } - /// `data` includes the journal entry on top. The data passed in must be exactly one vpage plus the journal entry - pub(crate) fn data_encrypt_and_patch_page(&self, cipher: &Aes256GcmSiv, aad: &[u8], data: &mut [u8], pp: &PhysPage) { - assert!(data.len() == VPAGE_SIZE + size_of::(), "did not get a page-sized region to patch"); - let j = JournalType::from_le_bytes(data[..size_of::()].try_into().unwrap()).saturating_add(1); - for (&src, dst) in j.to_le_bytes().iter().zip(data[..size_of::()].iter_mut()) { *dst = src; } + /// `data` includes the journal entry on top. The data passed in must be exactly one vpage plus the + /// journal entry + pub(crate) fn data_encrypt_and_patch_page( + &self, + cipher: &Aes256GcmSiv, + aad: &[u8], + data: &mut [u8], + pp: &PhysPage, + ) { + assert!( + data.len() == VPAGE_SIZE + size_of::(), + "did not get a page-sized region to patch" + ); + let j = JournalType::from_le_bytes(data[..size_of::()].try_into().unwrap()) + .saturating_add(1); + for (&src, dst) in j.to_le_bytes().iter().zip(data[..size_of::()].iter_mut()) { + *dst = src; + } let nonce = self.nonce_gen(); - let ciphertext = cipher.encrypt( - &nonce, - Payload { - aad, - msg: &data, - } - ).expect("couldn't encrypt data"); - // log::trace!("calling patch. nonce {:x?}, ct {:x?}, data {:x?}", nonce.as_slice(), &ciphertext[..32], &data[..32]); + let ciphertext = cipher.encrypt(&nonce, Payload { aad, msg: &data }).expect("couldn't encrypt data"); + // log::trace!("calling patch. nonce {:x?}, ct {:x?}, data {:x?}", nonce.as_slice(), + // &ciphertext[..32], &data[..32]); self.patch_data(&[nonce.as_slice(), &ciphertext].concat(), pp.page_number() * PAGE_SIZE as u32); } /// `data` includes the journal entry on top. - /// The data passed in must be exactly one vpage plus the journal entry minus the length of the commit structure (64 bytes), - /// which is 4004 bytes total + /// The data passed in must be exactly one vpage plus the journal entry minus the length of the commit + /// structure (64 bytes), which is 4004 bytes total /// This function increments the journal and re-nonces the structure. - pub(crate) fn data_encrypt_and_patch_page_with_commit(&self, key: &[u8], aad: &[u8], data: &mut [u8], pp: &PhysPage) { + pub(crate) fn data_encrypt_and_patch_page_with_commit( + &self, + key: &[u8], + aad: &[u8], + data: &mut [u8], + pp: &PhysPage, + ) { assert!(data.len() == KCOM_CT_LEN, "did not get a key-commit sized region to patch"); // updates the journal type - let j = JournalType::from_le_bytes(data[..size_of::()].try_into().unwrap()).saturating_add(1); + let j = JournalType::from_le_bytes(data[..size_of::()].try_into().unwrap()) + .saturating_add(1); data[..size_of::()].copy_from_slice(&j.to_le_bytes()); // gets the AES-GCM-SIV nonce let nonce = self.nonce_gen(); @@ -1645,29 +1855,25 @@ impl PddbOs { // generates the encryption and commit keys let (kenc, kcom) = self.kcom_func(key.try_into().unwrap(), &kcom_nonce); let cipher = Aes256GcmSiv::new(&kenc.into()); - let ciphertext = cipher.encrypt( - &nonce, - Payload { - aad, - msg: &data, - } - ).expect("couldn't encrypt data"); + let ciphertext = cipher.encrypt(&nonce, Payload { aad, msg: &data }).expect("couldn't encrypt data"); let mut dest_page = [0u8; PAGE_SIZE]; let mut written = 0; // used as a sanity check on the insane iterator chain constructed below - for (&src, dst) in - nonce.as_slice().iter() - .chain(ciphertext[..KCOM_CT_LEN].iter()) - .chain(kcom_nonce.iter()) - .chain(kcom.iter()) - .chain(ciphertext[KCOM_CT_LEN..].iter()) - .zip(dest_page.iter_mut()) { + for (&src, dst) in nonce + .as_slice() + .iter() + .chain(ciphertext[..KCOM_CT_LEN].iter()) + .chain(kcom_nonce.iter()) + .chain(kcom.iter()) + .chain(ciphertext[KCOM_CT_LEN..].iter()) + .zip(dest_page.iter_mut()) + { *dst = src; written += 1; } assert!(written == 4096, "data sizing error in encryption with key commit"); log::trace!("nonce: {:x?}", &nonce); - log::debug!("dest_page[kcom_nonce]: {:x?}", &dest_page[12+4004..12+4004+32]); + log::debug!("dest_page[kcom_nonce]: {:x?}", &dest_page[12 + 4004..12 + 4004 + 32]); self.patch_data(&dest_page, pp.page_number() * PAGE_SIZE as u32); } @@ -1675,10 +1881,7 @@ impl PddbOs { /// a `nonce` which is the 96-bit nonce used in the AES-GCM-SIV for a given block; /// and `nonce_com` which is the commitment nonce, set at 256 bits. /// The result is two tuples, (kenc, kcom). - fn kcom_func(&self, - key: &[u8; 32], - nonce_com: &[u8; 32]) -> ([u8; 32], [u8; 32]) { - + fn kcom_func(&self, key: &[u8; 32], nonce_com: &[u8; 32]) -> ([u8; 32], [u8; 32]) { let mut h_enc = Sha512Trunc256::new_with_strategy(FallbackStrategy::SoftwareOnly); h_enc.update(key); // per https://eprint.iacr.org/2020/1456.pdf Table 4 on page 13 Type I Lenc @@ -1701,18 +1904,28 @@ impl PddbOs { self.fast_space_read(); self.syskey_ensure(); if let Some(syskey) = self.system_basis_key.take() { - if let Some(sysbasis_map) = self.pt_scan_key(&syskey.pt, &syskey.data, PDDB_DEFAULT_SYSTEM_BASIS) { + if let Some(sysbasis_map) = self.pt_scan_key(&syskey.pt, &syskey.data, PDDB_DEFAULT_SYSTEM_BASIS) + { let aad = self.data_aad(PDDB_DEFAULT_SYSTEM_BASIS); // get the first page, where the basis root is guaranteed to be if let Some(root_page) = sysbasis_map.get(&VirtAddr::new(VPAGE_SIZE as u64).unwrap()) { let vpage = match self.data_decrypt_page_with_commit(&syskey.data, &aad, root_page) { Some(data) => data, - None => {log::error!("System basis decryption did not authenticate. Unrecoverable error."); return None;}, + None => { + log::error!("System basis decryption did not authenticate. Unrecoverable error."); + return None; + } }; - // if the below assertion fails, you will need to re-code this to decrypt more than one VPAGE and stripe into a basis root struct - assert!(size_of::() <= VPAGE_SIZE, "BasisRoot has grown past a single VPAGE, this routine needs to be re-coded to accommodate the extra bulk"); + // if the below assertion fails, you will need to re-code this to decrypt more than one + // VPAGE and stripe into a basis root struct + assert!( + size_of::() <= VPAGE_SIZE, + "BasisRoot has grown past a single VPAGE, this routine needs to be re-coded to accommodate the extra bulk" + ); let mut basis_root = BasisRoot::default(); - for (&src, dst) in vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) { + for (&src, dst) in + vpage[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) + { *dst = src; } if basis_root.magic != PDDB_MAGIC { @@ -1723,13 +1936,24 @@ impl PddbOs { log::error!("PDDB version mismatch in system basis root. Unrecoverable error."); return None; } - let basis_name = std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]).expect("basis is not valid utf-8"); + let basis_name = + std::str::from_utf8(&basis_root.name.data[..basis_root.name.len as usize]) + .expect("basis is not valid utf-8"); if basis_name != PDDB_DEFAULT_SYSTEM_BASIS { - log::error!("PDDB system basis name is incorrect: {}; aborting mount operation.", basis_name); + log::error!( + "PDDB system basis name is incorrect: {}; aborting mount operation.", + basis_name + ); return None; } log::info!("System BasisRoot record found, generating cache entry"); - let bce = BasisCacheEntry::mount(self, &basis_name, &syskey, false, BasisRetentionPolicy::Persist); + let bce = BasisCacheEntry::mount( + self, + &basis_name, + &syskey, + false, + BasisRetentionPolicy::Persist, + ); self.system_basis_key = Some(syskey); bce } else { @@ -1758,7 +1982,7 @@ impl PddbOs { self.rootkeys.decrypt_block(GenericArray::from_mut_slice(&mut checkblock_a)); log::info!("{}PDDB.CHECKPASS,{}", xous::BOOKEND_START, xous::BOOKEND_END); - #[cfg(any(feature="precursor", feature="renode"))] // skip this dialog in hosted mode + #[cfg(any(feature = "precursor", feature = "renode"))] // skip this dialog in hosted mode modals.show_notification(t!("pddb.checkpass", locales::LANG), None).expect("notification failed"); self.clear_password(); @@ -1769,15 +1993,13 @@ impl PddbOs { success = true; } else { log::info!("{}PDDB.PWFAIL,{}", xous::BOOKEND_START, xous::BOOKEND_END); - modals.show_notification(t!("pddb.checkpass_fail", locales::LANG), None).expect("notification failed"); + modals + .show_notification(t!("pddb.checkpass_fail", locales::LANG), None) + .expect("notification failed"); self.clear_password(); } } - if success { - Ok(()) - } else { - Err(Error::new(ErrorKind::PermissionDenied, "Password entry failed")) - } + if success { Ok(()) } else { Err(Error::new(ErrorKind::PermissionDenied, "Password entry failed")) } } /// this function attempts to change the PIN. returns Ok() if changed, error if not. @@ -1785,13 +2007,18 @@ impl PddbOs { if let Some(system_keys) = &self.system_basis_key { // get the new password self.clear_password(); - modals.show_notification(t!("pddb.changepin.enter_new_pin", locales::LANG), None) + modals + .show_notification(t!("pddb.changepin.enter_new_pin", locales::LANG), None) .map_err(|_| Error::new(ErrorKind::Other, "Internal error"))?; self.pw_check(modals)?; // wrap keys in the new password, and store them to disk - let wrapped_key = self.rootkeys.wrap_key(&system_keys.data).expect("Internal error wrapping our encryption key"); - let wrapped_key_pt = self.rootkeys.wrap_key(&system_keys.pt).expect("Internal error wrapping our encryption key"); + let wrapped_key = self + .rootkeys + .wrap_key(&system_keys.data) + .expect("Internal error wrapping our encryption key"); + let wrapped_key_pt = + self.rootkeys.wrap_key(&system_keys.pt).expect("Internal error wrapping our encryption key"); let mut crypto_keys = StaticCryptoData::default(); crypto_keys.deref_mut().copy_from_slice(&self.static_crypto_data_get().deref()); assert!(wrapped_key_pt.len() == 40); @@ -1804,13 +2031,17 @@ impl PddbOs { Err(Error::new(ErrorKind::PermissionDenied, "System basis keys not unlocked")) } } + /// this function is dangerous in that calling it will completely erase all of the previous data /// in the PDDB an replace it with a brand-spanking new, blank PDDB. - /// The number of servers that can connect to the Spinor crate is strictly tracked, so we borrow a reference - /// to the Spinor object allocated to the PDDB implementation for this operation. + /// The number of servers that can connect to the Spinor crate is strictly tracked, so we borrow a + /// reference to the Spinor object allocated to the PDDB implementation for this operation. pub(crate) fn pddb_format(&mut self, fast: bool, progress: Option<&modals::Modals>) -> Result<()> { if !self.rootkeys.is_initialized().unwrap() { - return Err(Error::new(ErrorKind::Unsupported, "Root keys are not initialized; cannot format a PDDB without root keys!")); + return Err(Error::new( + ErrorKind::Unsupported, + "Root keys are not initialized; cannot format a PDDB without root keys!", + )); } // step 0. If we have a modal, confirm that the password entered was correct with a double-entry. if let Some(modals) = progress { @@ -1821,15 +2052,21 @@ impl PddbOs { if !fast { log::info!("Erasing the PDDB region"); if let Some(modals) = progress { - modals.start_progress( - t!("pddb.erase", locales::LANG), - xous::PDDB_LOC, xous::PDDB_LOC + PDDB_A_LEN as u32, xous::PDDB_LOC) + modals + .start_progress( + t!("pddb.erase", locales::LANG), + xous::PDDB_LOC, + xous::PDDB_LOC + PDDB_A_LEN as u32, + xous::PDDB_LOC, + ) .expect("couldn't raise progress bar"); // retain this delay, because the next section is so compute-intensive, it may take a // while for the GAM to catch up. self.tt.sleep_ms(100).unwrap(); } - for offset in (xous::PDDB_LOC..(xous::PDDB_LOC + PDDB_A_LEN as u32)).step_by(SPINOR_BULK_ERASE_SIZE as usize) { + for offset in (xous::PDDB_LOC..(xous::PDDB_LOC + PDDB_A_LEN as u32)) + .step_by(SPINOR_BULK_ERASE_SIZE as usize) + { if (offset / SPINOR_BULK_ERASE_SIZE) % 4 == 0 { log::info!("Initial erase: {}/{}", offset - xous::PDDB_LOC, PDDB_A_LEN as u32); if let Some(modals) = progress { @@ -1839,7 +2076,11 @@ impl PddbOs { // do a blank check first to see if the sector really needs erasing let mut blank = true; let slice_start = (offset - xous::PDDB_LOC) as usize / size_of::(); - for word in unsafe { self.pddb_mr.as_slice::()[slice_start..slice_start + SPINOR_BULK_ERASE_SIZE as usize / size_of::()].iter() } { + for word in unsafe { + self.pddb_mr.as_slice::() + [slice_start..slice_start + SPINOR_BULK_ERASE_SIZE as usize / size_of::()] + .iter() + } { if *word != 0xFFFF_FFFF { blank = false; break; @@ -1850,16 +2091,20 @@ impl PddbOs { } } if let Some(modals) = progress { - modals.update_progress(xous::PDDB_LOC + PDDB_A_LEN as u32).expect("couldn't update progress bar"); + modals + .update_progress(xous::PDDB_LOC + PDDB_A_LEN as u32) + .expect("couldn't update progress bar"); modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } } // step 2. fill in the page table with junk, which marks it as cryptographically empty if let Some(modals) = progress { - modals.start_progress(t!("pddb.initpt", locales::LANG), 0, size_of::() as u32, 0).expect("couldn't raise progress bar"); + modals + .start_progress(t!("pddb.initpt", locales::LANG), 0, size_of::() as u32, 0) + .expect("couldn't raise progress bar"); } let mut temp: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; for page in (0..(size_of::() & !(PAGE_SIZE - 1))).step_by(PAGE_SIZE) { @@ -1872,11 +2117,16 @@ impl PddbOs { } } if let Some(modals) = progress { - modals.update_progress(size_of::() as u32).expect("couldn't update progress bar"); + modals + .update_progress(size_of::() as u32) + .expect("couldn't update progress bar"); } if size_of::() & (PAGE_SIZE - 1) != 0 { let remainder_start = size_of::() & !(PAGE_SIZE - 1); - log::info!("Page table does not end on a page boundary. Handling trailing page case of {} bytes", size_of::() - remainder_start); + log::info!( + "Page table does not end on a page boundary. Handling trailing page case of {} bytes", + size_of::() - remainder_start + ); let mut temp = Vec::::new(); for _ in remainder_start..size_of::() { temp.push(self.entropy.borrow_mut().get_u8()); @@ -1885,7 +2135,7 @@ impl PddbOs { } if let Some(modals) = progress { modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } @@ -1895,11 +2145,16 @@ impl PddbOs { // return Err(Error::new(ErrorKind::PermissionDenied, "unlock password was incorrect")); //} if let Some(modals) = progress { - modals.start_progress(t!("pddb.key", locales::LANG), 0, 100, 0).expect("couldn't raise progress bar"); - #[cfg(feature="ux-swap-delay")] + modals + .start_progress(t!("pddb.key", locales::LANG), 0, 100, 0) + .expect("couldn't raise progress bar"); + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } - assert!(size_of::() == PAGE_SIZE, "StaticCryptoData structure is not correctly sized"); + assert!( + size_of::() == PAGE_SIZE, + "StaticCryptoData structure is not correctly sized" + ); let mut system_basis_key_pt: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; let mut system_basis_key: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; self.entropy.borrow_mut().get_slice(&mut system_basis_key_pt); @@ -1908,19 +2163,16 @@ impl PddbOs { self.cipher_ecb = Some(Aes256::new(GenericArray::from_slice(&system_basis_key_pt))); let cipher_ecb = Aes256::new(GenericArray::from_slice(&system_basis_key_pt)); // a second copy for patching the page table later in this routine interior mutability blah blah work around oops // now wrap the key for storage - let wrapped_key = self.rootkeys.wrap_key(&system_basis_key).expect("Internal error wrapping our encryption key"); - let wrapped_key_pt = self.rootkeys.wrap_key(&system_basis_key_pt).expect("Internal error wrapping our encryption key"); - self.system_basis_key = Some( - BasisKeys { - pt: system_basis_key_pt, - data: system_basis_key, - } - ); // this causes system_basis_key to be owned by self and go out of scope + let wrapped_key = + self.rootkeys.wrap_key(&system_basis_key).expect("Internal error wrapping our encryption key"); + let wrapped_key_pt = + self.rootkeys.wrap_key(&system_basis_key_pt).expect("Internal error wrapping our encryption key"); + self.system_basis_key = Some(BasisKeys { pt: system_basis_key_pt, data: system_basis_key }); // this causes system_basis_key to be owned by self and go out of scope let mut crypto_keys = StaticCryptoData::default(); crypto_keys.version = SCD_VERSION; // should already be set by `default()` but let's be sure. if let Some(modals) = progress { modals.update_progress(50).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } // copy the encrypted key into the data structure for commit to Flash @@ -1939,69 +2191,79 @@ impl PddbOs { self.patch_keys(crypto_keys.deref(), 0); if let Some(modals) = progress { modals.update_progress(100).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } - // now we have a copy of the AES key necessary to encrypt the default System basis that we created in step 2. + // now we have a copy of the AES key necessary to encrypt the default System basis that we created in + // step 2. #[cfg(not(target_os = "xous"))] self.tt.sleep_ms(500).unwrap(); // delay for UX to catch up in emulation // step 4. mbbb handling - // mbbb should just be blank at this point, and the flash was erased in step 1, so there's nothing to do. + // mbbb should just be blank at this point, and the flash was erased in step 1, so there's nothing to + // do. // step 5. fscb handling // pick a set of random pages from the free pool and assign it to the fscb // pass the generator an empty cache - this causes it to treat the entire disk as free space if let Some(modals) = progress { - modals.start_progress(t!("pddb.fastspace", locales::LANG), 0, 100, 0).expect("couldn't raise progress bar"); + modals + .start_progress(t!("pddb.fastspace", locales::LANG), 0, 100, 0) + .expect("couldn't raise progress bar"); self.tt.sleep_ms(100).unwrap(); } let free_pool = self.fast_space_generate(BinaryHeap::>::new()); - let mut fast_space = FastSpace { - free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN], - }; + let mut fast_space = FastSpace { free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN] }; for (&src, dst) in free_pool.iter().zip(fast_space.free_pool.iter_mut()) { *dst = src; } if let Some(modals) = progress { modals.update_progress(50).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } self.fast_space_write(&fast_space); if let Some(modals) = progress { modals.update_progress(100).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } #[cfg(not(target_os = "xous"))] self.tt.sleep_ms(500).unwrap(); - // step 5. salt the free space with random numbers. this can take a while, we might need a "progress report" of some kind... - // this is coded using "direct disk" offsets...under the assumption that we only ever really want to do this here, and - // not re-use this routine elsewhere. + // step 5. salt the free space with random numbers. this can take a while, we might need a "progress + // report" of some kind... this is coded using "direct disk" offsets...under the assumption + // that we only ever really want to do this here, and not re-use this routine elsewhere. if let Some(modals) = progress { - modals.start_progress(t!("pddb.randomize", locales::LANG), - self.data_phys_base.as_u32(), PDDB_A_LEN as u32, self.data_phys_base.as_u32()).expect("couldn't raise progress bar"); - #[cfg(feature="ux-swap-delay")] + modals + .start_progress( + t!("pddb.randomize", locales::LANG), + self.data_phys_base.as_u32(), + PDDB_A_LEN as u32, + self.data_phys_base.as_u32(), + ) + .expect("couldn't raise progress bar"); + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } let blank = [0xffu8; aes::BLOCK_SIZE]; for offset in (self.data_phys_base.as_usize()..PDDB_A_LEN).step_by(PAGE_SIZE) { if fast { - // we could "skip" pages already encrypted to an old key as a short cut -- because we nuked our - // session key, previous data should be undecipherable. You shouldn't do this for a production erase - // but this is good for speeding up testing. + // we could "skip" pages already encrypted to an old key as a short cut -- because we nuked + // our session key, previous data should be undecipherable. You shouldn't do + // this for a production erase but this is good for speeding up testing. let mut is_blank = true; - let block: &[u8] = unsafe { &self.pddb_mr.as_slice()[offset + aes::BLOCK_SIZE * 3..offset + aes::BLOCK_SIZE * 4] }; + let block: &[u8] = unsafe { + &self.pddb_mr.as_slice()[offset + aes::BLOCK_SIZE * 3..offset + aes::BLOCK_SIZE * 4] + }; for (&a, &b) in block.iter().zip(blank.iter()) { if a != b { is_blank = false; @@ -2010,38 +2272,41 @@ impl PddbOs { } if !is_blank { if (offset / PAGE_SIZE) % 16 == 0 { - log::info!("Page at {} is likely to already have cryptographic data, skipping...", offset); + log::info!( + "Page at {} is likely to already have cryptographic data, skipping...", + offset + ); } continue; } } self.entropy.borrow_mut().get_slice(&mut temp); - if (offset / PAGE_SIZE) % 256 == 0 { // ~one update per megabyte + if (offset / PAGE_SIZE) % 256 == 0 { + // ~one update per megabyte log::info!("Cryptographic 'erase': {}/{}", offset, PDDB_A_LEN); if let Some(modals) = progress { modals.update_progress(offset as u32).expect("couldn't update progress bar"); } } - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &temp, - offset as u32 - ).expect("couldn't fill in disk with random datax"); + self.spinor + .patch(unsafe { self.pddb_mr.as_slice() }, xous::PDDB_LOC, &temp, offset as u32) + .expect("couldn't fill in disk with random datax"); } if let Some(modals) = progress { modals.update_progress(PDDB_A_LEN as u32).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } // step 6. create the system basis root structure if let Some(modals) = progress { - modals.start_progress(t!("pddb.structure", locales::LANG), 0, 100, 0).expect("couldn't raise progress bar"); - #[cfg(feature="ux-swap-delay")] + modals + .start_progress(t!("pddb.structure", locales::LANG), 0, 100, 0) + .expect("couldn't raise progress bar"); + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } let basis_root = BasisRoot { @@ -2056,7 +2321,7 @@ impl PddbOs { self.fast_space_read(); // we reconstitute our fspace map even though it was just generated, partially as a sanity check that everything is ok if let Some(modals) = progress { modals.update_progress(33).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } @@ -2076,12 +2341,12 @@ impl PddbOs { // from the basis_sync() method on a BasisCache entry, but because we haven't created a cache entry, // we're copypasta'ing the code here let aad = basis_root.aad(self.dna); - let pp = basis_v2p_map.get(&VirtAddr::new(1 * VPAGE_SIZE as u64).unwrap()) + let pp = basis_v2p_map + .get(&VirtAddr::new(1 * VPAGE_SIZE as u64).unwrap()) .expect("Internal consistency error: Basis exists, but its root map was not allocated!"); assert!(pp.valid(), "v2p returned an invalid page"); let journal_bytes = (self.trng_u32() % JOURNAL_RAND_RANGE).to_le_bytes(); - let slice_iter = - journal_bytes.iter() // journal rev + let slice_iter = journal_bytes.iter() // journal rev .chain(basis_root.as_ref().iter()); let mut block = [0 as u8; KCOM_CT_LEN]; for (&src, dst) in slice_iter.zip(block.iter_mut()) { @@ -2092,7 +2357,7 @@ impl PddbOs { self.system_basis_key = Some(syskey); // put the key back if let Some(modals) = progress { modals.update_progress(66).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } @@ -2104,10 +2369,10 @@ impl PddbOs { } if let Some(modals) = progress { modals.update_progress(100).expect("couldn't update progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); modals.finish_progress().expect("couldn't dismiss progress bar"); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(100).unwrap(); } Ok(()) @@ -2117,7 +2382,10 @@ impl PddbOs { /// Basis have been unlocked, the function returns `true`. The other option is the user /// can decline to unlock all the Basis right now, cancelling out of the process, which will /// cause the requesting free space sweep to fail. - pub(crate) fn pddb_generate_used_map(&self, cache: &Vec::) -> Option>> { + pub(crate) fn pddb_generate_used_map( + &self, + cache: &Vec, + ) -> Option>> { if let Some(all_keys) = self.pddb_get_all_keys(&cache) { let mut page_heap = BinaryHeap::new(); let mut page_check = std::collections::HashSet::new(); // this is just for sanity checking because you can't query a heap @@ -2135,7 +2403,6 @@ impl PddbOs { } } - // need to incorporate all of the FSCB knowledge into the scan as well, because there could be // cached pages inside "live" Basis that have not been committed to disk. However, it *should* be // the case that any cached allocations came out of the previous FSCB pool, therefore, by merging @@ -2144,10 +2411,16 @@ impl PddbOs { for pp in self.fspace_cache.iter() { if pp.space_state() == SpaceState::Used || pp.space_state() == SpaceState::MaybeUsed { if !page_check.insert(pp.page_number()) { - log::debug!("FSCB and page table both record this used page: {} (this is normal)", pp.page_number()); + log::debug!( + "FSCB and page table both record this used page: {} (this is normal)", + pp.page_number() + ); } else { page_heap.push(Reverse(pp.page_number())); - log::info!("FSCB contained {}, but not yet committed to disk; added to page_heap", pp.page_number()); + log::info!( + "FSCB contained {}, but not yet committed to disk; added to page_heap", + pp.page_number() + ); } } } @@ -2162,10 +2435,9 @@ impl PddbOs { /// /// If there are items allocated in the FSCB that have not had their corresponding physical /// page table entries on disk written, the operation will fail. - pub(crate) fn pddb_rekey(&mut self, op: PddbRekeyOp, cache: &Vec::) -> PddbRekeyOp { + pub(crate) fn pddb_rekey(&mut self, op: PddbRekeyOp, cache: &Vec) -> PddbRekeyOp { match op { - PddbRekeyOp::FromDnaFast(dna) | - PddbRekeyOp::FromDnaSafe(dna) => { + PddbRekeyOp::FromDnaFast(dna) | PddbRekeyOp::FromDnaSafe(dna) => { if cache.len() != 0 { log::error!("We can't have any previously mounted Bases if the PDDB has the wrong key."); return PddbRekeyOp::InternalError; @@ -2176,7 +2448,7 @@ impl PddbOs { self.syskey_ensure(); // this routine will continue to bang the user for a password. There is no way out otherwise. log::info!("migrating from dna 0x{:x} -> 0x{:x}", self.migration_dna, self.dna); self.fast_space_read(); // we hawe to re-read the FSCB, because it would have failed previously with lots of warnings - }, + } PddbRekeyOp::Churn => self.dna_mode = DnaMode::Churn, _ => (), }; @@ -2184,12 +2456,14 @@ impl PddbOs { // clean up any MBBB, as it will mess up our algorithm if let Some(mbbb) = self.mbbb_retrieve() { if let Some(erased_offset) = self.pt_find_erased_slot() { - self.spinor.patch( - unsafe { self.pddb_mr.as_slice() }, - xous::PDDB_LOC, - &mbbb, - self.pt_phys_base.as_u32() + erased_offset, - ).expect("couldn't write to page table"); + self.spinor + .patch( + unsafe { self.pddb_mr.as_slice() }, + xous::PDDB_LOC, + &mbbb, + self.pt_phys_base.as_u32() + erased_offset, + ) + .expect("couldn't write to page table"); } self.mbbb_erase(); } @@ -2222,7 +2496,16 @@ impl PddbOs { let aad_local = self.data_aad(&name); self.dna_mode = DnaMode::Migration; let aad_incoming = self.data_aad(&name); - keymap.insert(&name, MigrationCiphers { pt_ecb, data_gcm_siv, aad_incoming, aad_local, data_key: basis_keys.data.into() }); + keymap.insert( + &name, + MigrationCiphers { + pt_ecb, + data_gcm_siv, + aad_incoming, + aad_local, + data_key: basis_keys.data.into(), + }, + ); modals.update_progress(index as u32 + 1).ok(); } @@ -2243,7 +2526,9 @@ impl PddbOs { } modals.finish_progress().ok(); if !clean { - log::warn!("Unmapped FSCB records were found, but this is likely due to FSCB flushes called. Continuing."); + log::warn!( + "Unmapped FSCB records were found, but this is likely due to FSCB flushes called. Continuing." + ); // return PddbRekeyOp::VerifyFail } @@ -2259,30 +2544,45 @@ impl PddbOs { let pagetable: &[u8] = unsafe { &self.pddb_mr.as_slice()[..pddb_data_pages * size_of::()] }; log::info!("Derived page table of len 0x{:x}", pagetable.len()); let entries_per_page = PAGE_SIZE / size_of::(); - modals.start_progress(t!("pddb.rekey.running", locales::LANG), 0, (pddb_data_pages * size_of::()) as u32, 0).ok(); + modals + .start_progress( + t!("pddb.rekey.running", locales::LANG), + 0, + (pddb_data_pages * size_of::()) as u32, + 0, + ) + .ok(); for (chunk_enum, page) in pagetable.chunks(PAGE_SIZE).enumerate() { // this is the actual offset into pagetable[] that the page[] slice comes from let chunk_start_address = chunk_enum * PAGE_SIZE; let chunk_start_ppn = chunk_enum * entries_per_page; let mut chunk_len = 0; // we have to track this because the .chunks() will return a less-than-page sized chunk on the last iteration - log::info!("processing chunk at 0x{:x} (page number {}", chunk_start_address, chunk_start_ppn); + log::info!( + "processing chunk at 0x{:x} (page number {}", + chunk_start_address, + chunk_start_ppn + ); let mut new_pt_page = [0u8; PAGE_SIZE]; assert!(page.len() % size_of::() == 0); - for (index, (enc_pte, new_pte)) in - page.chunks_exact(size_of::()) - .zip(new_pt_page.chunks_exact_mut(size_of::())) - .enumerate() { + for (index, (enc_pte, new_pte)) in page + .chunks_exact(size_of::()) + .zip(new_pt_page.chunks_exact_mut(size_of::())) + .enumerate() + { let ppn = (chunk_start_ppn + index) as u32; chunk_len += size_of::(); if let Some(&keyname) = pagemap.get(&ppn) { log::debug!("re-encrypt page {} of {}", ppn, keyname); - let ciphers = keymap.get(keyname).expect("How do we have a mapping to a key that we don't have?"); - // ok, we know the physical page table number, and thus the page location, and the keys. we can - // now do the following: - // 1. re-encrypt the page table. It is relatively "free" to do a thorough re-encryption - // so we will always either redo the nonce on every entry and/or fill the unused entries - // with fresh noise. + let ciphers = keymap + .get(keyname) + .expect("How do we have a mapping to a key that we don't have?"); + // ok, we know the physical page table number, and thus the page location, and the + // keys. we can now do the following: + // 1. re-encrypt the page table. It is relatively "free" to do a thorough + // re-encryption + // so we will always either redo the nonce on every entry and/or fill the unused + // entries with fresh noise. let mut block = Block::clone_from_slice(enc_pte); ciphers.pt_ecb.decrypt_block(&mut block); if let Some(mut pte) = Pte::try_from_slice(block.as_slice()) { @@ -2292,48 +2592,53 @@ impl PddbOs { new_pte.copy_from_slice(enc_entry.as_slice()); } - // 2. re-encrypt the disclosed basis data from the previous DNA to the current DNA. Redo all the nonces + // 2. re-encrypt the disclosed basis data from the previous DNA to the current DNA. + // Redo all the nonces // while we are at it. if let Some(mut data) = self.data_decrypt_page( - &ciphers.data_gcm_siv, - &ciphers.aad_incoming, - &PhysPage(ppn)) { + &ciphers.data_gcm_siv, + &ciphers.aad_incoming, + &PhysPage(ppn), + ) { // this routine also redoes the nonce correctly. self.data_encrypt_and_patch_page( &ciphers.data_gcm_siv, &ciphers.aad_local, &mut data, - &PhysPage(ppn) + &PhysPage(ppn), ); } else { - // see if it's a root page, which is encrypted differently due to AES-GCM-SIV salamanders and key commits. + // see if it's a root page, which is encrypted differently due to AES-GCM-SIV + // salamanders and key commits. if let Some(mut data) = self.data_decrypt_page_with_commit( - &ciphers.data_key, - &ciphers.aad_incoming, - &PhysPage(ppn)) { + &ciphers.data_key, + &ciphers.aad_incoming, + &PhysPage(ppn), + ) { log::debug!("...as basis root page..."); self.data_encrypt_and_patch_page_with_commit( &ciphers.data_key, &ciphers.aad_local, &mut data, - &PhysPage(ppn) + &PhysPage(ppn), ); } else { log::warn!("Page number {} failed to decrypt. This data is now lost.", ppn); } } } else { - // it's an unused page. fill in the PTE with garbage, and *maybe* fill in the mapped page - // with new garbage, too. + // it's an unused page. fill in the PTE with garbage, and *maybe* fill in the mapped + // page with new garbage, too. self.trng_slice(new_pte); // the chance is expressed as a number from 0-255. It is compared against an 8-bit - // random number, and if the random number is less than the fraction, a rekey op will happen + // random number, and if the random number is less than the fraction, a rekey op will + // happen let blank_rekey_chance = match op { PddbRekeyOp::FromDnaFast(_) => FAST_REKEY_CHANCE, PddbRekeyOp::FromDnaSafe(_) => 256, // 100% chance of rekey - PddbRekeyOp::Churn => 256, // 100% chance of rekey - _ => continue, // don't rekey blanks for all other ops + PddbRekeyOp::Churn => 256, // 100% chance of rekey + _ => continue, // don't rekey blanks for all other ops }; if (self.trng_u8() as u32) < blank_rekey_chance { log::debug!("re-noising unused page {}", ppn); @@ -2346,7 +2651,8 @@ impl PddbOs { } } self.patch_pagetable_raw(&new_pt_page[..chunk_len], chunk_start_address as u32); - // this only updates once per page of PTEs, so, every 256 pages that get re-encrypted in the worst case. + // this only updates once per page of PTEs, so, every 256 pages that get re-encrypted in the + // worst case. modals.update_progress(chunk_start_address as u32).ok(); } modals.finish_progress().ok(); @@ -2356,8 +2662,7 @@ impl PddbOs { // 3. if we are not just changing the password on one Basis, regenerate the FSCB, since we // have all the Bases open let do_fscb = match op { - PddbRekeyOp::FromDnaFast(_dna) | - PddbRekeyOp::FromDnaSafe(_dna) => true, + PddbRekeyOp::FromDnaFast(_dna) | PddbRekeyOp::FromDnaSafe(_dna) => true, PddbRekeyOp::Churn => true, _ => false, }; @@ -2374,9 +2679,7 @@ impl PddbOs { } // generate and nuke old free space records let free_pool = self.fast_space_generate(page_heap); - let mut fast_space = FastSpace { - free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN], - }; + let mut fast_space = FastSpace { free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN] }; for pp in fast_space.free_pool.iter_mut() { pp.set_journal(self.trng_u8() % FSCB_JOURNAL_RAND_RANGE) } @@ -2403,9 +2706,9 @@ impl PddbOs { /// valid (it'll scan up and stop as soon as one Pte checks out). Note that it then only returns /// a Vec of keys & names, not a BasisCacheEntry -- so it means that the Basis still are "closed" /// at the conclusion of the sweep, but their page use can be accounted for. - #[cfg(not(all(feature="pddbtest", feature="autobasis")))] - pub(crate) fn pddb_get_all_keys(&self, cache: &Vec::) -> Option> { - #[cfg(feature="ux-swap-delay")] + #[cfg(not(all(feature = "pddbtest", feature = "autobasis")))] + pub(crate) fn pddb_get_all_keys(&self, cache: &Vec) -> Option> { + #[cfg(feature = "ux-swap-delay")] const SWAP_DELAY_MS: usize = 300; // populate the "known" entries let mut ret = Vec::<(BasisKeys, String)>::new(); @@ -2416,16 +2719,16 @@ impl PddbOs { for entry in cache { log::info!(" - {}", entry.name); } - // In the case of a migration, the basis cache would be empty, but the system basis key is already set up + // In the case of a migration, the basis cache would be empty, but the system basis key is already set + // up if self.dna_mode == DnaMode::Migration { - let syskeys = self.system_basis_key.as_ref() + let syskeys = self + .system_basis_key + .as_ref() .expect("Internal error: syskey_ensure() must be called prior to get_all_keys()"); ret.push(( - BasisKeys { - pt: syskeys.pt.into(), - data: syskeys.data.into(), - }, - PDDB_DEFAULT_SYSTEM_BASIS.to_string() + BasisKeys { pt: syskeys.pt.into(), data: syskeys.data.into() }, + PDDB_DEFAULT_SYSTEM_BASIS.to_string(), )); // note: because self.dna_mode is in 'Migration', the AAD checks in this section will be // using the original DNA, so they should pass. @@ -2435,14 +2738,17 @@ impl PddbOs { // alloc operation to fail let xns = xous_names::XousNames::new().unwrap(); let modals = modals::Modals::new(&xns).unwrap(); - modals.show_notification( - match self.dna_mode { - DnaMode::Normal => t!("pddb.freespace.request", locales::LANG), - DnaMode::Migration => t!("pddb.rekey.request", locales::LANG), - DnaMode::Churn => t!("pddb.churn.request", locales::LANG), - }, - None).ok(); - #[cfg(feature="ux-swap-delay")] + modals + .show_notification( + match self.dna_mode { + DnaMode::Normal => t!("pddb.freespace.request", locales::LANG), + DnaMode::Migration => t!("pddb.rekey.request", locales::LANG), + DnaMode::Churn => t!("pddb.churn.request", locales::LANG), + }, + None, + ) + .ok(); + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); // 0.5 display the Bases that we know @@ -2452,19 +2758,15 @@ impl PddbOs { blist.push_str(name); } modals.show_notification(&blist, None).ok(); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); // 1. prompt user to enter any name/password combos for other basis we want to keep while self.yes_no_approval(&modals, t!("pddb.freespace.enumerate_another", locales::LANG)) { - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); - match modals - .alert_builder(t!("pddb.freespace.name", locales::LANG)) - .field(None, None) - .build() - { + match modals.alert_builder(t!("pddb.freespace.name", locales::LANG)).field(None, None).build() { Ok(bname) => { let name = bname.first().as_str().to_string(); let request = BasisRequestPassword { @@ -2475,21 +2777,21 @@ impl PddbOs { buf.lend_mut(self.pw_cid, PwManagerOpcode::RequestPassword.to_u32().unwrap()).unwrap(); let retpass = buf.to_original::().unwrap(); // 2. validate the name/password combo - let basis_key = self.basis_derive_key( - &name, - retpass.plaintext_pw.unwrap().as_str().unwrap() - ); + let basis_key = + self.basis_derive_key(&name, retpass.plaintext_pw.unwrap().as_str().unwrap()); // validate the password by finding the root block of the basis. We rely entirely // upon the AEAD with key commit to ensure the password is correct. let maybe_entry = if let Some(basis_map) = - self.pt_scan_key(&basis_key.pt, &basis_key.data, &name) { + self.pt_scan_key(&basis_key.pt, &basis_key.data, &name) + { let aad = self.data_aad(&name); if let Some(root_page) = basis_map.get(&VirtAddr::new(VPAGE_SIZE as u64).unwrap()) { match self.data_decrypt_page_with_commit(&basis_key.data, &aad, root_page) { Some(_data) => { - // if the root page decrypts, we accept the password; no further checking is done. + // if the root page decrypts, we accept the password; no further checking + // is done. Some((basis_key, name.to_string())) - }, + } None => None, } } else { @@ -2503,13 +2805,13 @@ impl PddbOs { ret.push((basis_key, name)); } else { modals.show_notification(t!("pddb.freespace.badpass", locales::LANG), None).ok(); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); } - }, + } _ => return None, }; - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); // 4. repeat summary print-out let mut blist = String::from(t!("pddb.freespace.currentlist", locales::LANG)); @@ -2518,7 +2820,7 @@ impl PddbOs { blist.push_str(name); } modals.show_notification(&blist, None).ok(); - #[cfg(feature="ux-swap-delay")] + #[cfg(feature = "ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); } // done! @@ -2528,7 +2830,8 @@ impl PddbOs { DnaMode::Normal => t!("pddb.freespace.finished", locales::LANG), DnaMode::Migration => t!("pddb.rekey.finished", locales::LANG), DnaMode::Churn => t!("pddb.churn.finished", locales::LANG), - }) { + }, + ) { Some(ret) } else { None @@ -2536,9 +2839,9 @@ impl PddbOs { } fn yes_no_approval(&self, modals: &modals::Modals, request: &str) -> bool { - modals.add_list( - vec![t!("pddb.yes", locales::LANG), t!("pddb.no", locales::LANG)] - ).expect("couldn't build confirmation dialog"); + modals + .add_list(vec![t!("pddb.yes", locales::LANG), t!("pddb.no", locales::LANG)]) + .expect("couldn't build confirmation dialog"); match modals.get_radiobutton(request) { Ok(response) => { if &response == t!("pddb.yes", locales::LANG) { @@ -2550,7 +2853,7 @@ impl PddbOs { _ => { log::error!("get approval failed"); false - }, + } } } @@ -2559,7 +2862,8 @@ impl PddbOs { pub(crate) fn basis_derive_key(&self, basis_name: &str, password: &str) -> BasisKeys { // 1. derive the salt from the "key" region. First step is to create the salt lookup // table, which is done by hashing the name and password together with SHA-512 - // manage the allocation of the data for the basis & password explicitly so that we may wipe them later + // manage the allocation of the data for the basis & password explicitly so that we may wipe them + // later let mut bname_copy = [0u8; BASIS_NAME_LEN]; for (src, dst) in basis_name.bytes().zip(bname_copy.iter_mut()) { *dst = src; @@ -2584,7 +2888,7 @@ impl PddbOs { for (&src, dst) in result.iter().zip(salt.iter_mut()) { *dst = src; } - #[cfg(feature="hazardous-debug")] + #[cfg(feature = "hazardous-debug")] log::info!("derived salt: {:x?}", salt); // 3. use the salt + password and run bcrypt on it to derive a key. @@ -2608,43 +2912,43 @@ impl PddbOs { // shouldn't be optimized out or re-ordered let bn_ptr = bname_copy.as_mut_ptr(); for i in 0..bname_copy.len() { - unsafe{bn_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + bn_ptr.add(i).write_volatile(core::mem::zeroed()); + } } let pt_ptr = plaintext_pw.as_mut_ptr(); for i in 0..plaintext_pw.len() { - unsafe{pt_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + pt_ptr.add(i).write_volatile(core::mem::zeroed()); + } } core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - #[cfg(feature="hazardous-debug")] + #[cfg(feature = "hazardous-debug")] log::info!("okm_pt: {:x?}", okm_pt); - #[cfg(feature="hazardous-debug")] + #[cfg(feature = "hazardous-debug")] log::info!("okm_data: {:x?}", okm_data); - //6. return the keys - BasisKeys { - pt: okm_pt, - data: okm_data, - } + // 6. return the keys + BasisKeys { pt: okm_pt, data: okm_data } } - pub(crate) fn reset_dont_ask_init(&self) { - self.rootkeys.do_reset_dont_ask_init(); - } + pub(crate) fn reset_dont_ask_init(&self) { self.rootkeys.do_reset_dont_ask_init(); } - pub(crate) fn checksums(&self, modals: Option::<&Modals>) -> root_keys::api::Checksums { + pub(crate) fn checksums(&self, modals: Option<&Modals>) -> root_keys::api::Checksums { let mut checksums = root_keys::api::Checksums::default(); let pddb = unsafe { self.pddb_mr.as_slice() }; if let Some(m) = modals { - m.start_progress( - t!("pddb.checksums", locales::LANG), - 0, - checksums.checksums.len() as u32, - 0 - ).ok(); + m.start_progress(t!("pddb.checksums", locales::LANG), 0, checksums.checksums.len() as u32, 0) + .ok(); } - for (index, region) in pddb.chunks(root_keys::api::CHECKSUM_BLOCKLEN_PAGE as usize * PAGE_SIZE).enumerate() { - assert!(region.len() == root_keys::api::CHECKSUM_BLOCKLEN_PAGE as usize * PAGE_SIZE, "CHECKSUM_BLOCKLEN_PAGE is not an even divisor of the PDDB size"); + for (index, region) in + pddb.chunks(root_keys::api::CHECKSUM_BLOCKLEN_PAGE as usize * PAGE_SIZE).enumerate() + { + assert!( + region.len() == root_keys::api::CHECKSUM_BLOCKLEN_PAGE as usize * PAGE_SIZE, + "CHECKSUM_BLOCKLEN_PAGE is not an even divisor of the PDDB size" + ); let mut hasher = Sha512Trunc256::new_with_strategy(FallbackStrategy::HardwareThenSoftware); hasher.update(region); // reserve the first 32 bytes of salt for the HKDF let digest = hasher.finalize(); @@ -2659,6 +2963,7 @@ impl PddbOs { } checksums } + //-------------------------------- TESTING ----------------------------------------- // always gated behind a feature flag. Includes routines that are nonsensicle in normal operation at best, // and very dangerous from a security perspective at worst. @@ -2678,21 +2983,22 @@ impl PddbOs { /// The tooling and argument spec on this is deliberately sparse because this routine is meant to /// be only used "if you know what you're doing" and we want to make the arg passing as minimal/easy /// as possible (just a scalar) to cut down on testing overhead. - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] pub(crate) fn basis_testing(&mut self, cache: &mut BasisCache, config: &[Option; 32]) { for (index, &maybe_op) in config.iter().enumerate() { let name = format!("{}{}", BASIS_TEST_ROOTNAME, index); if let Some(op) = maybe_op { - if op { // try to mount the basis, or create it if it does not exist. + if op { + // try to mount the basis, or create it if it does not exist. self.testnames.insert(name.to_string()); - let maybe_basis = cache.basis_unlock( - self, &name, &name, BasisRetentionPolicy::Persist); + let maybe_basis = cache.basis_unlock(self, &name, &name, BasisRetentionPolicy::Persist); if maybe_basis.is_some() { cache.basis_add(maybe_basis.unwrap()); } else { cache.basis_create(self, &name, &name).expect("couldn't create basis"); - let basis = cache.basis_unlock(self, &name, &name, BasisRetentionPolicy::Persist) + let basis = cache + .basis_unlock(self, &name, &name, BasisRetentionPolicy::Persist) .expect("couldn't open just created basis"); cache.basis_add(basis); } @@ -2700,19 +3006,28 @@ impl PddbOs { let blist = cache.basis_list(); if blist.contains(&name) { match cache.basis_unmount(self, &name) { - Ok(_) => {}, + Ok(_) => {} Err(e) => log::error!("error unmounting basis {}: {:?}", name, e), } } else { - log::info!("attempted to unmount {} but it does not exist or is already locked", name); + log::info!( + "attempted to unmount {} but it does not exist or is already locked", + name + ); } } } } } - /// In testing, we want a way to automatically unlock all known test Bases. This stand-in feature automates that. - #[cfg(all(feature="pddbtest", feature="autobasis"))] - pub(crate) fn pddb_get_all_keys<'a>(&'a self, cache: &'a Vec::, _op: GetKeyOp) -> Option> { + + /// In testing, we want a way to automatically unlock all known test Bases. This stand-in feature + /// automates that. + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] + pub(crate) fn pddb_get_all_keys<'a>( + &'a self, + cache: &'a Vec, + _op: GetKeyOp, + ) -> Option> { // populate the "known" entries let mut ret = Vec::<(BasisKeys, String)>::new(); for entry in cache { @@ -2731,10 +3046,7 @@ impl PddbOs { } } if !exists { - let basis_key = self.basis_derive_key( - name, - name - ); + let basis_key = self.basis_derive_key(name, name); ret.push((basis_key, name.to_string())); } } @@ -2744,23 +3056,17 @@ impl PddbOs { } Some(ret) } - #[cfg(all(feature="pddbtest", feature="autobasis"))] + + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] pub(crate) fn dbg_extra(&self) -> Vec { let mut ret = Vec::::new(); for name in self.testnames.iter() { - let basis_key = self.basis_derive_key( - name, - name - ); + let basis_key = self.basis_derive_key(name, name); let mut basis_name = [0 as u8; 64]; for (&src, dst) in name.as_bytes().iter().zip(basis_name.iter_mut()) { *dst = src; } - ret.push(KeyExport { - basis_name, - key: basis_key.data, - pt_key: basis_key.pt, - }); + ret.push(KeyExport { basis_name, key: basis_key.data, pt_key: basis_key.pt }); } ret } @@ -2772,11 +3078,17 @@ impl PddbOs { /// Must be within this structure because it accesses the rootkeys, and we don't want to make that public. /// Need to pass this the old version of the StaticCryptoData, because it's already erased and replaced /// by updated keys by the time this routine is called. - #[cfg(feature="migration1")] - pub(crate) fn basis_derive_key_v00_00_01_01(&self, basis_name: &str, password: &str, scd: &StaticCryptoDataV1) -> [u8; AES_KEYSIZE] { + #[cfg(feature = "migration1")] + pub(crate) fn basis_derive_key_v00_00_01_01( + &self, + basis_name: &str, + password: &str, + scd: &StaticCryptoDataV1, + ) -> [u8; AES_KEYSIZE] { // 1. derive the salt from the "key" region. First step is to create the salt lookup // table, which is done by hashing the name and password together with SHA-512 - // manage the allocation of the data for the basis & password explicitly so that we may wipe them later + // manage the allocation of the data for the basis & password explicitly so that we may wipe them + // later let mut bname_copy = [0u8; BASIS_NAME_LEN]; for (src, dst) in basis_name.bytes().zip(bname_copy.iter_mut()) { *dst = src; @@ -2800,7 +3112,7 @@ impl PddbOs { for (&src, dst) in result.iter().zip(salt.iter_mut()) { *dst = src; } - #[cfg(feature="hazardous-debug")] + #[cfg(feature = "hazardous-debug")] log::info!("derived salt: {:x?}", salt); // 3. use the salt + password and run bcrypt on it to derive a key. @@ -2823,30 +3135,34 @@ impl PddbOs { // shouldn't be optimized out or re-ordered let bn_ptr = bname_copy.as_mut_ptr(); for i in 0..bname_copy.len() { - unsafe{bn_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + bn_ptr.add(i).write_volatile(core::mem::zeroed()); + } } let pt_ptr = plaintext_pw.as_mut_ptr(); for i in 0..plaintext_pw.len() { - unsafe{pt_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + pt_ptr.add(i).write_volatile(core::mem::zeroed()); + } } core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - //6. return the key + // 6. return the key key } - #[cfg(feature="migration1")] + #[cfg(feature = "migration1")] fn migration_v1_to_v2_inner( &mut self, - aad_v1: &Vec::, - aad_v2: &Vec::, + aad_v1: &Vec, + aad_v2: &Vec, key_v1: &[u8; AES_KEYSIZE], // needed to decrypt page with commit cipher_pt_v1: &Aes256, cipher_data_v1: &Aes256GcmSiv, key_data_v2: &[u8; AES_KEYSIZE], // this is needed because we have to do a key commitment cipher_pt_v2: &Aes256, cipher_data_v2: &Aes256GcmSiv, - used_pages: &mut BinaryHeap::>, + used_pages: &mut BinaryHeap>, ) -> bool { let blank = [0xffu8; aes::BLOCK_SIZE]; let pt = self.pt_as_slice(); @@ -2856,16 +3172,23 @@ impl PddbOs { for (page_index, pt_page) in pt.chunks(PAGE_SIZE).enumerate() { if pt_page[..aes::BLOCK_SIZE] == blank { if let Some(page) = self.mbbb_retrieve() { - log::info!("Found MBBB, restoring 0x100 pages @ ppn {:x} & erasing MBBB", (page_index * PAGE_SIZE / aes::BLOCK_SIZE)); - self.spinor.patch( - self.pddb_mr.as_slice(), - xous::PDDB_LOC, - &page, - self.pt_phys_base.as_u32() + (page_index * PAGE_SIZE) as u32, - ).expect("couldn't write to page table"); + log::info!( + "Found MBBB, restoring 0x100 pages @ ppn {:x} & erasing MBBB", + (page_index * PAGE_SIZE / aes::BLOCK_SIZE) + ); + self.spinor + .patch( + self.pddb_mr.as_slice(), + xous::PDDB_LOC, + &page, + self.pt_phys_base.as_u32() + (page_index * PAGE_SIZE) as u32, + ) + .expect("couldn't write to page table"); self.mbbb_erase(); } else { - log::info!("Blank page in PT found, but no MBBB entry exists. PT is either corrupted or not initialized!"); + log::info!( + "Blank page in PT found, but no MBBB entry exists. PT is either corrupted or not initialized!" + ); } } } @@ -2891,35 +3214,53 @@ impl PddbOs { let mut pp = PhysPage(0); pp.set_page_number(((page_index * PAGE_SIZE / aes::BLOCK_SIZE) + index) as PhysAddr); - // root records require key commitment (to avoid AES-GCM-SIV salamanders), so detect & handle that - if pte.vaddr_v1().get() == VPAGE_SIZE as u64 { // v1 addresses were full vaddrs, not page numbers - log::info!("migration: potential root block at (pp){:x}/(vp){:x}", pp.page_number(), pte.vaddr_v1().get()); + // root records require key commitment (to avoid AES-GCM-SIV salamanders), so detect & + // handle that + if pte.vaddr_v1().get() == VPAGE_SIZE as u64 { + // v1 addresses were full vaddrs, not page numbers + log::info!( + "migration: potential root block at (pp){:x}/(vp){:x}", + pp.page_number(), + pte.vaddr_v1().get() + ); // retrieve the data at `pp` let migrating_data = self.data_decrypt_page_with_commit(key_v1, aad_v1, &pp); if let Some(vpage) = migrating_data { log::info!("migration: found root block!"); found_basis = true; let mut basis_root = BasisRoot::default(); - for (&src, dst) in (&vpage)[size_of::()..].iter().zip(basis_root.deref_mut().iter_mut()) { + for (&src, dst) in (&vpage)[size_of::()..] + .iter() + .zip(basis_root.deref_mut().iter_mut()) + { *dst = src; } let (previous, _) = PDDB_MIGRATE_1; if basis_root.version != previous { - log::warn!("Root basis record did not match expected version during migration. Ignoring and attempting to move on..."); + log::warn!( + "Root basis record did not match expected version during migration. Ignoring and attempting to move on..." + ); } basis_root.version = PDDB_VERSION; // update the version to our current one log::info!("Basis root: {:?}", basis_root); - let slice_iter = - (&vpage[..size_of::()]).iter() // just copy the journal rev + let slice_iter = (&vpage[..size_of::()]).iter() // just copy the journal rev .chain(basis_root.as_ref().iter()); let mut block = [0 as u8; KCOM_CT_LEN]; for (&src, dst) in slice_iter.zip(block.iter_mut()) { *dst = src; } - self.data_encrypt_and_patch_page_with_commit(key_data_v2, aad_v2, &mut block, &pp); + self.data_encrypt_and_patch_page_with_commit( + key_data_v2, + aad_v2, + &mut block, + &pp, + ); } else { - // could be a checksum collision that triggers this, so it's not a hard error. A later "real" version will succeed and all would be fine. - log::warn!("Root basis record did not decrypt correctly, ignoring and hoping for the best, but your chances are slim."); + // could be a checksum collision that triggers this, so it's not a hard error. A + // later "real" version will succeed and all would be fine. + log::warn!( + "Root basis record did not decrypt correctly, ignoring and hoping for the best, but your chances are slim." + ); } } else { log::info!("migrating (pp){:x}/(vp){:x}", pp.page_number(), pte.vaddr_v1().get()); @@ -2929,12 +3270,16 @@ impl PddbOs { if let Some(mut vpage) = migrating_data { self.data_encrypt_and_patch_page(&cipher_data_v2, aad_v2, &mut vpage, &pp); } else { - log::warn!("Potential checksum collision found at pp {}; ignoring", pp.page_number()); + log::warn!( + "Potential checksum collision found at pp {}; ignoring", + pp.page_number() + ); } } - // *** 3. re-encrypt the PTE and the target page to the v2 keys and corrected addressing scheme - // a deconstructed pt_patch_mapping() call -- because the normal call would insert a MBBB block, which is not what we want in this case. + // *** 3. re-encrypt the PTE and the target page to the v2 keys and corrected addressing + // scheme a deconstructed pt_patch_mapping() call -- because the + // normal call would insert a MBBB block, which is not what we want in this case. let mut pte = Pte::new(pte.vaddr_v1(), PtFlags::CLEAN, Rc::clone(&self.entropy)); let mut pt_block = Block::from_mut_slice(pte.deref_mut()); cipher_pt_v2.encrypt_block(&mut pt_block); @@ -2954,11 +3299,12 @@ impl PddbOs { /// note that v1 also had an error where page tables were encoded in full addresess, when /// we meant to encode them using page numbers. This code also handles that migration. /// Must be within this structure because it accesses the rootkeys, and we don't want to make that public. - #[cfg(feature="migration1")] + #[cfg(feature = "migration1")] pub(crate) fn migration_v1_to_v2(&mut self, pw_cid: xous::CID) -> PasswordState { let scd = self.static_crypto_data_get_v1(); - if scd.version == 0xFFFF_FFFF { // system is in the blank state - return PasswordState::Uninit + if scd.version == 0xFFFF_FFFF { + // system is in the blank state + return PasswordState::Uninit; } if scd.version != SCD_VERSION_MIGRATION1 { return PasswordState::Uninit; @@ -2976,7 +3322,8 @@ impl PddbOs { // build a modals for migration messages let xns = xous_names::XousNames::new().unwrap(); let modals = modals::Modals::new(&xns).unwrap(); - // v1->v2 messages willl only be in English, because we don't have any non-English users yet (afaik) + // v1->v2 messages willl only be in English, because we don't have any non-English users yet + // (afaik) let cipher_v1 = Aes256::new(GenericArray::from_slice(&syskey)); let mut system_key_v1: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; @@ -2986,14 +3333,17 @@ impl PddbOs { // erase the old vector completely, now that the key is in system_key_v1 let nuke = syskey.as_mut_ptr(); for i in 0..syskey.len() { - unsafe{nuke.add(i).write_volatile(0)}; + unsafe { nuke.add(i).write_volatile(0) }; } - modals.dynamic_notification(Some("PDDB v1->v2 migration"), Some("Migrating System keys to v2")).unwrap(); + modals + .dynamic_notification(Some("PDDB v1->v2 migration"), Some("Migrating System keys to v2")) + .unwrap(); // ------ I. migrate the system basis ------- - // *** 0. generate v2 keys. This will immediately overwrite the SCD -- if we have an error or power outage - // after this point, we lose the entire PDDB. The other option is to commit the v2 keys at the end, but, - // similarly, we end up with a partial migration either way. + // *** 0. generate v2 keys. This will immediately overwrite the SCD -- if we have an error or + // power outage after this point, we lose the entire PDDB. The other option is + // to commit the v2 keys at the end, but, similarly, we end up with a partial + // migration either way. let mut system_basis_key_pt: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; let mut system_basis_key: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; let mut syskey_data_copy: [u8; AES_KEYSIZE] = [0; AES_KEYSIZE]; // we will need a copy of this later on for re-encrypting with key commit @@ -3008,14 +3358,15 @@ impl PddbOs { let data_cipher_v2 = Aes256GcmSiv::new(Key::from_slice(&system_basis_key)); let aad_v2 = self.data_aad(PDDB_DEFAULT_SYSTEM_BASIS); // now wrap the key for storage - let wrapped_key = self.rootkeys.wrap_key(&system_basis_key).expect("Internal error wrapping our encryption key"); - let wrapped_key_pt = self.rootkeys.wrap_key(&system_basis_key_pt).expect("Internal error wrapping our encryption key"); - self.system_basis_key = Some( - BasisKeys { - pt: system_basis_key_pt, - data: system_basis_key, - } - ); // this causes system_basis_key to be owned by self and go out of scope + let wrapped_key = self + .rootkeys + .wrap_key(&system_basis_key) + .expect("Internal error wrapping our encryption key"); + let wrapped_key_pt = self + .rootkeys + .wrap_key(&system_basis_key_pt) + .expect("Internal error wrapping our encryption key"); + self.system_basis_key = Some(BasisKeys { pt: system_basis_key_pt, data: system_basis_key }); // this causes system_basis_key to be owned by self and go out of scope let mut crypto_keys = StaticCryptoData::default(); crypto_keys.version = SCD_VERSION; // should already be set by `default()` but let's be sure. @@ -3044,9 +3395,11 @@ impl PddbOs { // *** 1. scan the page table modals.dynamic_notification_update(None, Some("Migrating System Basis")).unwrap(); // *** 2. if an entry matches, also decrypt the target page and store it here - // *** 3. re-encrypt the PTE and the target page to the v2 keys and corrected addressing scheme + // *** 3. re-encrypt the PTE and the target page to the v2 keys and corrected addressing + // scheme if !self.migration_v1_to_v2_inner( - &aad_v1, &aad_v2, + &aad_v1, + &aad_v2, &system_key_v1, &cipher_v1, &data_cipher_v1, @@ -3060,45 +3413,66 @@ impl PddbOs { log::info!("System basis migration successful"); } - // *** 4. The MBBB is natively migrated as part of this process, so nothing explictly needs to be done here. + // *** 4. The MBBB is natively migrated as part of this process, so nothing explictly needs to + // be done here. // ------ II. migrate any hidden basis ------ modals.dynamic_notification_close().unwrap(); - let mut prompt = String::from("Any secret Bases must be migrated now, or else their data will be lost.\n\nUnlock a Basis for migration?"); + let mut prompt = String::from( + "Any secret Bases must be migrated now, or else their data will be lost.\n\nUnlock a Basis for migration?", + ); loop { - modals.add_list_item(t!("pddb.yes", locales::LANG)).expect("couldn't build radio item list"); - modals.add_list_item(t!("pddb.no", locales::LANG)).expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.yes", locales::LANG)) + .expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.no", locales::LANG)) + .expect("couldn't build radio item list"); match modals.get_radiobutton(&prompt) { Ok(response) => { if response.as_str() == t!("pddb.yes", locales::LANG) { - match modals.alert_builder("Enter the Basis name") - .field(Some("My Secret Basis".to_string()), None) - .build() { + match modals + .alert_builder("Enter the Basis name") + .field(Some("My Secret Basis".to_string()), None) + .build() + { Ok(bname) => { let request = BasisRequestPassword { db_name: xous_ipc::String::from_str(bname.first().as_str()), plaintext_pw: None, }; let mut buf = Buffer::into_buf(request).unwrap(); - buf.lend_mut(pw_cid, PwManagerOpcode::RequestPassword.to_u32().unwrap()).unwrap(); + buf.lend_mut( + pw_cid, + PwManagerOpcode::RequestPassword.to_u32().unwrap(), + ) + .unwrap(); let ret = buf.to_original::().unwrap(); if let Some(pw) = ret.plaintext_pw { // derive old and new keys let basis_key_v1 = self.basis_derive_key_v00_00_01_01( bname.first().as_str(), pw.as_str().unwrap_or("UTF8-error"), - &scd + &scd, ); let basis_aad_v1 = data_aad_v1(&self, bname.first().as_str()); let basis_aad_v2 = self.data_aad(bname.first().as_str()); - let basis_pt_cipher_v1 = Aes256::new(GenericArray::from_slice(&basis_key_v1)); - let basis_data_cipher_v1 = Aes256GcmSiv::new(Key::from_slice(&basis_key_v1)); - let basis_keys = self.basis_derive_key(bname.first().as_str(), pw.as_str().unwrap_or("UTF8 error")); - let basis_pt_cipher_2 = Aes256::new(GenericArray::from_slice(&basis_keys.pt)); - let basis_data_cipher_2 = Aes256GcmSiv::new(Key::from_slice(&basis_keys.data)); + let basis_pt_cipher_v1 = + Aes256::new(GenericArray::from_slice(&basis_key_v1)); + let basis_data_cipher_v1 = + Aes256GcmSiv::new(Key::from_slice(&basis_key_v1)); + let basis_keys = self.basis_derive_key( + bname.first().as_str(), + pw.as_str().unwrap_or("UTF8 error"), + ); + let basis_pt_cipher_2 = + Aes256::new(GenericArray::from_slice(&basis_keys.pt)); + let basis_data_cipher_2 = + Aes256GcmSiv::new(Key::from_slice(&basis_keys.data)); // perform the migration if self.migration_v1_to_v2_inner( - &basis_aad_v1, &basis_aad_v2, + &basis_aad_v1, + &basis_aad_v2, &basis_key_v1, &basis_pt_cipher_v1, &basis_data_cipher_v1, @@ -3110,25 +3484,33 @@ impl PddbOs { #[cfg(not(target_os = "xous"))] { let mut name = [0 as u8; 64]; - for (&src, dst) in bname.first().as_str().as_bytes().iter().zip(name.iter_mut()) { + for (&src, dst) in bname + .first() + .as_str() + .as_bytes() + .iter() + .zip(name.iter_mut()) + { *dst = src; } - export.push( - KeyExport { - basis_name: name, - key: basis_keys.data, - pt_key: basis_keys.pt, - } - ); + export.push(KeyExport { + basis_name: name, + key: basis_keys.data, + pt_key: basis_keys.pt, + }); } prompt.clear(); - prompt.push_str("Migration success, migrate another secret Basis?"); + prompt.push_str( + "Migration success, migrate another secret Basis?", + ); } else { prompt.clear(); prompt.push_str("Migration failure, retry and/or migrate another secret Basis?"); } } else { - log::warn!("Couldn't retrieve password for the basis, ignoring and moving on"); + log::warn!( + "Couldn't retrieve password for the basis, ignoring and moving on" + ); prompt.clear(); prompt.push_str("Error unlocking Basis, retry?"); } @@ -3145,16 +3527,19 @@ impl PddbOs { log::warn!("Got unexpected return from radiobutton: {}", response); } } - _ => log::warn!("get_radiobutton failed") + _ => log::warn!("get_radiobutton failed"), } } // ------ III. nuke and regnerate fast space ----- - modals.dynamic_notification(Some("Finalizing PDDB v1->v2 migration"), Some("Regenerate FastSpace")).unwrap(); + modals + .dynamic_notification( + Some("Finalizing PDDB v1->v2 migration"), + Some("Regenerate FastSpace"), + ) + .unwrap(); let free_pool = self.fast_space_generate(used_pages); - let mut fast_space = FastSpace { - free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN], - }; + let mut fast_space = FastSpace { free_pool: [PhysPage(0); FASTSPACE_FREE_POOL_LEN] }; for pp in fast_space.free_pool.iter_mut() { pp.set_journal(self.trng_u8() % FSCB_JOURNAL_RAND_RANGE) } @@ -3172,12 +3557,16 @@ impl PddbOs { // clear out the v1 key let sk_ptr = system_key_v1.as_mut_ptr(); for i in 0..system_key_v1.len() { - unsafe{sk_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + sk_ptr.add(i).write_volatile(core::mem::zeroed()); + } } // clear out the copy of the v2 system data key, required for key commits of the basis let sk_ptr = syskey_data_copy.as_mut_ptr(); for i in 0..syskey_data_copy.len() { - unsafe{sk_ptr.add(i).write_volatile(core::mem::zeroed());} + unsafe { + sk_ptr.add(i).write_volatile(core::mem::zeroed()); + } } core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); diff --git a/services/pddb/src/backend/key.rs b/services/pddb/src/backend/key.rs index bda27519b..55125fe86 100644 --- a/services/pddb/src/backend/key.rs +++ b/services/pddb/src/backend/key.rs @@ -1,10 +1,10 @@ -use crate::api::*; -use super::*; - -use std::num::NonZeroU32; use core::ops::{Deref, DerefMut}; use std::cmp::Ordering; -use std::io::{Result, Error, ErrorKind}; +use std::io::{Error, ErrorKind, Result}; +use std::num::NonZeroU32; + +use super::*; +use crate::api::*; #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[repr(C, align(8))] @@ -30,12 +30,7 @@ impl KeyName { } } impl Default for KeyName { - fn default() -> KeyName { - KeyName { - len: 0, - data: [0; KEY_NAME_LEN - 1] - } - } + fn default() -> KeyName { KeyName { len: 0, data: [0; KEY_NAME_LEN - 1] } } } /// On-disk representation of the Key. Note that the storage on disk is mis-aligned relative @@ -58,74 +53,79 @@ pub(crate) struct KeyDescriptor { } impl Default for KeyDescriptor { fn default() -> Self { - KeyDescriptor { - start: 0, - len: 0, - reserved: 0, - flags: KeyFlags(0), - age: 0, - name: KeyName::default(), - } + KeyDescriptor { start: 0, len: 0, reserved: 0, flags: KeyFlags(0), age: 0, name: KeyName::default() } } } impl Deref for KeyDescriptor { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const KeyDescriptor as *const u8, core::mem::size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const KeyDescriptor as *const u8, + core::mem::size_of::(), + ) as &[u8] } } } impl DerefMut for KeyDescriptor { fn deref_mut(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self as *mut KeyDescriptor as *mut u8, core::mem::size_of::()) - as &mut [u8] + core::slice::from_raw_parts_mut( + self as *mut KeyDescriptor as *mut u8, + core::mem::size_of::(), + ) as &mut [u8] } } } /// In-RAM representation of a key. This file defines the storage for the KeyCacheEntry; most of the structure -/// manipulations happen inside `dictionary.rs`, in part because to locate a Key in absolute memory space you need -/// to know what Dictionary it comes from. This is a point to consider for a refactor: if we pull some info about -/// the containing Dictionary into the key, we could associate more methods with the data structure. However, this -/// means duplicating the dictionary index, a field that can then get out of sync. +/// manipulations happen inside `dictionary.rs`, in part because to locate a Key in absolute memory space you +/// need to know what Dictionary it comes from. This is a point to consider for a refactor: if we pull some +/// info about the containing Dictionary into the key, we could associate more methods with the data +/// structure. However, this means duplicating the dictionary index, a field that can then get out of sync. pub(crate) struct KeyCacheEntry { pub(crate) start: u64, pub(crate) len: u64, pub(crate) reserved: u64, pub(crate) flags: KeyFlags, pub(crate) age: u32, - /// this is an ephemeral time since boot, not commited to disk, used only for deciding which entries to evict + /// this is an ephemeral time since boot, not commited to disk, used only for deciding which entries to + /// evict pub(crate) atime: u64, /// the current on-disk index of the KeyCacheEntry item, enumerated as "0" being the Dict descriptor and - /// "1" being the first valid key. This is used to find the location of the key's metadata as stored on disk; - /// it has nothing to do with where the data itself is stored (that's derived from `start`). + /// "1" being the first valid key. This is used to find the location of the key's metadata as stored on + /// disk; it has nothing to do with where the data itself is stored (that's derived from `start`). pub(crate) descriptor_index: NonZeroU32, - /// indicates if the descriptor cache entry is currently synchronized with what's on disk. Does not imply anything about the data, - /// but if the `data` field is None then there is nothing to in cache to be dirtied. + /// indicates if the descriptor cache entry is currently synchronized with what's on disk. Does not imply + /// anything about the data, but if the `data` field is None then there is nothing to in cache to be + /// dirtied. pub(crate) clean: bool, - /// if Some, contains the keys data contents. if None, you must refer to the disk contents to retrieve it. - /// Current rule: "small" keys always have their data "hot"; large keys may often not keep their data around. + /// if Some, contains the keys data contents. if None, you must refer to the disk contents to retrieve + /// it. Current rule: "small" keys always have their data "hot"; large keys may often not keep their + /// data around. pub(crate) data: Option, } impl KeyCacheEntry { - /// Given a base offset of the dictionary containing the key, compute the starting VirtAddr of the key itself. + /// Given a base offset of the dictionary containing the key, compute the starting VirtAddr of the key + /// itself. pub(crate) fn descriptor_vaddr(&self, dict_offset: VirtAddr) -> VirtAddr { VirtAddr::new(dict_offset.get() + ((self.descriptor_index.get() as u64) * DK_STRIDE as u64)).unwrap() } + /// Computes the modular position of the KeyDescriptor within a vpage. #[allow(dead_code)] // I feel like we should have been calling this /somewhere/ at some point in time, but I probably just re-wrote the math long-hand. pub(crate) fn descriptor_modulus(&self) -> usize { (self.descriptor_index.get() as usize) % (VPAGE_SIZE / DK_STRIDE) } + /// Computes the vpage offset as measured from the start of the dictionary storage region pub(crate) fn descriptor_vpage_num(&self) -> usize { (self.descriptor_index.get() as usize) / DK_PER_VPAGE } + /// returns the list of large-pool virtual pages belonging to this entry, if any. - pub(crate) fn large_pool_vpages(&self) -> Vec:: { + pub(crate) fn large_pool_vpages(&self) -> Vec { let mut vpages = Vec::::new(); if self.start >= LARGE_POOL_START { for vbase in (self.start..self.start + self.reserved).step_by(VPAGE_SIZE) { @@ -134,6 +134,7 @@ impl KeyCacheEntry { } vpages } + /// returns a rough measure of the amount of data consumed by a given entry. It's not meant to /// be absolutely accurate, but it should give an accurate impression of the relative size of /// different cache entries. This metric is used to help decide which entries to discard. @@ -143,15 +144,17 @@ impl KeyCacheEntry { Some(kcd) => match kcd { KeyCacheData::Small(ksd) => ksd.data.len(), KeyCacheData::Large(kld) => kld.data.len(), - } + }, }; core::mem::size_of::() + data_size } + pub(crate) fn atime(&self) -> u64 { self.atime } + pub(crate) fn set_atime(&mut self, atime: u64) { self.atime = atime; } } -pub (crate) enum KeyCacheData { +pub(crate) enum KeyCacheData { Small(KeySmallData), // the "Medium" type has a region reserved for it, but we haven't coded a handler for it. #[allow(dead_code)] // Large data caching isn't implemented, so of course, we don't ever create this type @@ -160,7 +163,7 @@ pub (crate) enum KeyCacheData { /// Small data is optimized for low overhead, and always represent a complete copy of the data. pub(crate) struct KeySmallData { pub clean: bool, - pub(crate) data: Vec::, + pub(crate) data: Vec, } /// This can hold just a portion of a large key's data. For now, we now essentially manually /// encode a sub-slice in parts, but, later on we could get more clever and start to cache @@ -169,17 +172,18 @@ pub(crate) struct KeySmallData { pub(crate) struct KeyLargeData { pub clean: bool, pub(crate) start: u64, - pub(crate) data: Vec::, + pub(crate) data: Vec, } /// A storage pool for data that is strictly smaller than one VPAGE. These element are serialized /// and stored inside the "small data pool" area. pub(crate) struct KeySmallPool { - // location of data within the Small memory region. Index is in units of SMALL_CAPACITY. (this should be encoded in the vector position) - //pub(crate) index: u32, + // location of data within the Small memory region. Index is in units of SMALL_CAPACITY. (this should be + // encoded in the vector position) pub(crate) index: u32, /// list of data actually stored within the pool - resolve against `keys` HashMap. - pub(crate) contents: Vec::, - /// keeps track of the available space within the pool, avoiding an expensive lookup every time we want to query the available space + pub(crate) contents: Vec, + /// keeps track of the available space within the pool, avoiding an expensive lookup every time we want + /// to query the available space pub(crate) avail: u16, /// if false, contents are modified and need syncing to disk pub(crate) clean: bool, @@ -204,17 +208,11 @@ pub(crate) struct KeySmallPoolOrd { } // only compare based on the amount of data used impl Ord for KeySmallPoolOrd { - fn cmp(&self, other: &Self) -> Ordering { - self.avail.cmp(&other.avail) - } + fn cmp(&self, other: &Self) -> Ordering { self.avail.cmp(&other.avail) } } impl PartialOrd for KeySmallPoolOrd { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl PartialEq for KeySmallPoolOrd { - fn eq(&self, other: &Self) -> bool { - self.avail == other.avail - } + fn eq(&self, other: &Self) -> bool { self.avail == other.avail } } diff --git a/services/pddb/src/backend/migration1to2.rs b/services/pddb/src/backend/migration1to2.rs index 188acb554..ab3ffd7b5 100644 --- a/services/pddb/src/backend/migration1to2.rs +++ b/services/pddb/src/backend/migration1to2.rs @@ -1,6 +1,7 @@ -use crate::*; -use core::ops::Deref; use core::mem::size_of; +use core::ops::Deref; + +use crate::*; pub(crate) const SCD_VERSION_MIGRATION1: u32 = 1; @@ -24,15 +25,18 @@ impl StaticCryptoDataV1 { } impl Deref for StaticCryptoDataV1 { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { - core::slice::from_raw_parts(self as *const StaticCryptoDataV1 as *const u8, size_of::()) - as &[u8] + core::slice::from_raw_parts( + self as *const StaticCryptoDataV1 as *const u8, + size_of::(), + ) as &[u8] } } } -pub(crate) fn data_aad_v1(pddb_os: &PddbOs, name: &str) -> Vec:: { +pub(crate) fn data_aad_v1(pddb_os: &PddbOs, name: &str) -> Vec { let mut aad = Vec::::new(); aad.extend_from_slice(&name.as_bytes()); let (old_version, _new_version) = PDDB_MIGRATE_1; diff --git a/services/pddb/src/backend/murmur3.rs b/services/pddb/src/backend/murmur3.rs index 49e55a7d0..a7c03c2dc 100644 --- a/services/pddb/src/backend/murmur3.rs +++ b/services/pddb/src/backend/murmur3.rs @@ -7,7 +7,6 @@ // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. - // This murmur3 code was vendored in on April 28, 2021. We choose to vendor the code for this simple // non-cryptographic hash in to reduce the number of crate dependencies in the build system. See // https://github.com/betrusted-io/xous-core/issues/54 for more details on why. @@ -55,7 +54,6 @@ fn calc_k(k: u32) -> u32 { k.wrapping_mul(C1).rotate_left(R1).wrapping_mul(C2) } - #[cfg(test)] mod tests { use super::*; @@ -75,18 +73,11 @@ mod tests { slice: &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38], // "12345678", hash_32: 0x91b313ce, }, - Result { - slice: &[], - hash_32: 0, - }, + Result { slice: &[], hash_32: 0 }, ]; for test in &tests { - assert_eq!( - murmur3_32(test.slice, 0), - test.hash_32, - "Failed on slice {:x?}", test.slice - ); + assert_eq!(murmur3_32(test.slice, 0), test.hash_32, "Failed on slice {:x?}", test.slice); } } } diff --git a/services/pddb/src/backend/pagetable.rs b/services/pddb/src/backend/pagetable.rs index e6a4ae131..dd9f0c31c 100644 --- a/services/pddb/src/backend/pagetable.rs +++ b/services/pddb/src/backend/pagetable.rs @@ -1,13 +1,14 @@ -use super::{PAGE_SIZE, TrngPool, VirtAddr, murmur3_32, VPAGE_SIZE}; -use core::mem::size_of; -use aes_gcm_siv::{Nonce, Tag}; -use std::rc::Rc; use core::cell::RefCell; -use core::ops::{Deref, DerefMut}; use core::convert::TryInto; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; +use std::rc::Rc; +use aes_gcm_siv::{Nonce, Tag}; use bitflags::bitflags; +use super::{murmur3_32, TrngPool, VirtAddr, PAGE_SIZE, VPAGE_SIZE}; + bitflags! { /// flags used by the page table pub struct PtFlags: u8 { @@ -23,7 +24,7 @@ bitflags! { } } impl Default for PtFlags { - fn default() -> PtFlags {PtFlags::INVALID} + fn default() -> PtFlags { PtFlags::INVALID } } /// A Page Table Entry. Must be equal in length to one AES block size (128 bits). @@ -51,8 +52,8 @@ pub(crate) struct Pte { flags: PtFlags, /// 32-bit strength of a nonce, but can be varied nonce: [u8; 4], - /// 32-bit "weak" checksum, used only for quick scans of the PTE to determine a coarse "in" or "out" classifier - /// checksum is computed on all of the bits prior, so checksum(pddb_addr, flags, nonce) + /// 32-bit "weak" checksum, used only for quick scans of the PTE to determine a coarse "in" or "out" + /// classifier checksum is computed on all of the bits prior, so checksum(pddb_addr, flags, nonce) checksum: [u8; 4], } impl Pte { @@ -70,22 +71,26 @@ impl Pte { pte } + pub fn vaddr(&self) -> VirtAddr { let mut full_addr = [0u8; 8]; - // LSB encoded, so this loop deposits the partial pddb_addr in the LSBs, and the MSBs are correctly 0 from above initializer + // LSB encoded, so this loop deposits the partial pddb_addr in the LSBs, and the MSBs are correctly 0 + // from above initializer for (&src, dst) in self.pddb_addr.iter().zip(full_addr.iter_mut()) { *dst = src; } VirtAddr::new(u64::from_le_bytes(full_addr) * VPAGE_SIZE as u64).unwrap() } + /// V1 databases stored the virtual address as a full address, instead of as a page number, which means /// the overall size of our database was about 4000x smaller than we had thought. This was fixed in v2, /// but this getter is required to migrate from v1. /// This allows us to retrieve the old address format for the first phase of migration. - #[cfg(feature="migration1")] + #[cfg(feature = "migration1")] pub fn vaddr_v1(&self) -> VirtAddr { let mut full_addr = [0u8; 8]; - // LSB encoded, so this loop deposits the partial pddb_addr in the LSBs, and the MSBs are correctly 0 from above initializer + // LSB encoded, so this loop deposits the partial pddb_addr in the LSBs, and the MSBs are correctly 0 + // from above initializer for (&src, dst) in self.pddb_addr.iter().zip(full_addr.iter_mut()) { *dst = src; } @@ -93,9 +98,8 @@ impl Pte { } #[allow(dead_code)] - pub fn flags(&self) -> PtFlags { - self.flags - } + pub fn flags(&self) -> PtFlags { self.flags } + pub fn try_from_slice(slice: &[u8]) -> Option { if slice.len() == size_of::() { let mut maybe_pt = Pte::default(); @@ -112,6 +116,7 @@ impl Pte { None } } + /// Normally you should be using pt_patch_mapping(), which generates a new nonce every /// time the entry is patched. However, this function is provided for "bulk" operations /// such as migrations where we violate the abstractions to improve performance. @@ -125,6 +130,7 @@ impl Pte { } impl Deref for Pte { type Target = [u8]; + fn deref(&self) -> &[u8] { unsafe { core::slice::from_raw_parts(self as *const Pte as *const u8, core::mem::size_of::()) @@ -165,6 +171,7 @@ pub(crate) struct EncryptedPage { journal_rev: [u8; 4], /// data is encrypted and holds the good stuff data: [u8; PAGE_SIZE - size_of::() - size_of::() - size_of::()], - /// tag is the authentication tag. If the page decrypts & authenticates, we know it's a valid data block for us. + /// tag is the authentication tag. If the page decrypts & authenticates, we know it's a valid data block + /// for us. p_tag: [u8; size_of::()], -} \ No newline at end of file +} diff --git a/services/pddb/src/backend/trngpool.rs b/services/pddb/src/backend/trngpool.rs index f70531d2d..5a4d625f1 100644 --- a/services/pddb/src/backend/trngpool.rs +++ b/services/pddb/src/backend/trngpool.rs @@ -1,15 +1,16 @@ -use core::mem::size_of; -use aes_gcm_siv::Nonce; -use std::convert::TryInto; use core::cell::RefCell; +use core::mem::size_of; +use std::convert::TryInto; + +use aes_gcm_siv::Nonce; use rand_core::RngCore; /// Crate-shared resource for TRNGs. pub(crate) struct TrngPool { - trng: RefCell::, + trng: RefCell, /// The PDDB eats a lot of entropy. Keep a local pool of entropy, so we're not wasting a lot of /// overhead passing messages to the TRNG hardware server. - e_cache: RefCell::>, + e_cache: RefCell>, } impl TrngPool { pub fn new() -> Self { @@ -17,11 +18,9 @@ impl TrngPool { let mut trng = trng::Trng::new(&xns).unwrap(); let mut cache: [u8; 8192] = [0; 8192]; trng.fill_bytes(&mut cache); - TrngPool { - trng: RefCell::new(trng), - e_cache: RefCell::new(cache.to_vec()) - } + TrngPool { trng: RefCell::new(trng), e_cache: RefCell::new(cache.to_vec()) } } + pub(crate) fn ensure_entropy(&self, amount: usize) { if self.e_cache.borrow().len() < amount { let mut cache: [u8; 8192] = [0; 8192]; @@ -29,37 +28,42 @@ impl TrngPool { self.e_cache.borrow_mut().extend_from_slice(&cache); } } + pub(crate) fn get_u8(&self) -> u8 { self.ensure_entropy(1); self.e_cache.borrow_mut().pop().unwrap() } + pub(crate) fn get_u32(&self) -> u32 { self.ensure_entropy(4); - let ret = u32::from_le_bytes(self.e_cache.borrow()[self.e_cache.borrow().len() - 4..].try_into().unwrap()); + let ret = + u32::from_le_bytes(self.e_cache.borrow()[self.e_cache.borrow().len() - 4..].try_into().unwrap()); let ecache_len = self.e_cache.borrow().len(); self.e_cache.borrow_mut().truncate(ecache_len - 4); ret } + pub(crate) fn get_u64(&self) -> u64 { self.ensure_entropy(8); - let ret = u64::from_le_bytes(self.e_cache.borrow()[self.e_cache.borrow().len() - 8..].try_into().unwrap()); + let ret = + u64::from_le_bytes(self.e_cache.borrow()[self.e_cache.borrow().len() - 8..].try_into().unwrap()); let ecache_len = self.e_cache.borrow().len(); self.e_cache.borrow_mut().truncate(ecache_len - 8); ret } + pub(crate) fn get_slice(&self, bucket: &mut [u8]) { self.ensure_entropy(bucket.len()); let drain_start = self.e_cache.borrow().len() - bucket.len(); - for (src, dst) in self.e_cache.borrow_mut().drain( - drain_start.. - ).zip(bucket.iter_mut()) { + for (src, dst) in self.e_cache.borrow_mut().drain(drain_start..).zip(bucket.iter_mut()) { *dst = src; } } + /// generates a 96-bit nonce using the TRNG pub(crate) fn get_nonce(&self) -> [u8; size_of::()] { let mut nonce: [u8; size_of::()] = [0; size_of::()]; self.get_slice(&mut nonce); nonce } -} \ No newline at end of file +} diff --git a/services/pddb/src/backend/types.rs b/services/pddb/src/backend/types.rs index a06984347..95ac0dcce 100644 --- a/services/pddb/src/backend/types.rs +++ b/services/pddb/src/backend/types.rs @@ -1,18 +1,20 @@ use core::num::NonZeroU64; use core::ops::Add; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + +use bitfield::bitfield; + use super::{PAGE_SIZE, VPAGE_SIZE}; use crate::SpaceState; -use bitfield::bitfield; -use std::hash::{Hash, Hasher}; -use std::cmp::Ordering; /// for the life of me, I can't figure out how to query the AES crate to give me the length of a 256-bit key. /// I mean, we know what it is, it's well-defined and never changes. But it'd just be nice to you know, /// derive it from a const or something with symbolic meaning, but the KeySize Trait is buried in some sort /// of a NewBlock trait and I can't figure out how to access it. Looking at the example code on the AES crate /// on docs.rs, they just pull the number 16 out of their ass instead of referring to a trait. -/// So, maybe that's just what you're supposed to do. ¯\_(ツ)_/¯ Oddly enough, a BLOCK_SIZE constant /is/ defined, -/// but maybe that's because it's constant regardless of the key size so it's easy to do. +/// So, maybe that's just what you're supposed to do. ¯\_(ツ)_/¯ Oddly enough, a BLOCK_SIZE constant /is/ +/// defined, but maybe that's because it's constant regardless of the key size so it's easy to do. pub(crate) const AES_KEYSIZE: usize = 32; /// This has to be manually synchronized with the bit range of the `journal` field below. It doesn't look like @@ -44,24 +46,16 @@ bitfield! { } // hashes should only key off of the page number, not the metadata impl Hash for PhysPage { - fn hash(&self, state: &mut H) { - self.page_number().hash(state); - } + fn hash(&self, state: &mut H) { self.page_number().hash(state); } } impl PartialEq for PhysPage { - fn eq(&self, other: &Self) -> bool { - self.page_number() == other.page_number() - } + fn eq(&self, other: &Self) -> bool { self.page_number() == other.page_number() } } impl Ord for PhysPage { - fn cmp(&self, other: &Self) -> Ordering { - self.page_number().cmp(&other.page_number()) - } + fn cmp(&self, other: &Self) -> Ordering { self.page_number().cmp(&other.page_number()) } } impl PartialOrd for PhysPage { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Storage for journal revisions. @@ -90,10 +84,13 @@ impl PageAlignedVa { panic!("PageAlignedVa would not fit into a u32"); } } + #[allow(dead_code)] - pub(crate) fn as_u64(&self) -> u64 {self.0.get()} + pub(crate) fn as_u64(&self) -> u64 { self.0.get() } + #[allow(dead_code)] - pub(crate) fn as_usize(&self) -> usize {self.0.get() as usize} + pub(crate) fn as_usize(&self) -> usize { self.0.get() as usize } + /// This will turn a PageAlignedVa into a page number #[allow(dead_code)] pub(crate) fn as_vpage_num(&self) -> usize { @@ -102,8 +99,9 @@ impl PageAlignedVa { } } impl Default for PageAlignedVa { - // 0 is not a valid address; the offset at VPAGE_SIZE is the location of BasisRoot, and that is the default value. - fn default() -> Self {PageAlignedVa(VirtAddr::new(VPAGE_SIZE as u64).unwrap())} + // 0 is not a valid address; the offset at VPAGE_SIZE is the location of BasisRoot, and that is the + // default value. + fn default() -> Self { PageAlignedVa(VirtAddr::new(VPAGE_SIZE as u64).unwrap()) } } impl From for PageAlignedVa { fn from(arg: u64) -> Self { @@ -138,6 +136,7 @@ impl From for PageAlignedVa { } impl Add for PageAlignedVa { type Output = PageAlignedVa; + fn add(self, other: PageAlignedVa) -> PageAlignedVa { let a = self.0.get(); let b = other.0.get(); @@ -150,42 +149,42 @@ impl Add for PageAlignedVa { pub(crate) struct PageAlignedPa(PhysAddr); impl PageAlignedPa { #[allow(dead_code)] - pub(crate) fn as_u32(&self) -> u32 {self.0 as u32} + pub(crate) fn as_u32(&self) -> u32 { self.0 as u32 } + #[allow(dead_code)] - pub(crate) fn as_u64(&self) -> u64 {self.0 as u64} + pub(crate) fn as_u64(&self) -> u64 { self.0 as u64 } + #[allow(dead_code)] - pub(crate) fn as_usize(&self) -> usize {self.0 as usize} + pub(crate) fn as_usize(&self) -> usize { self.0 as usize } + #[allow(dead_code)] - pub(crate) fn as_phys_addr(&self) -> PhysAddr {self.0} + pub(crate) fn as_phys_addr(&self) -> PhysAddr { self.0 } } impl From for PageAlignedPa { fn from(arg: u32) -> Self { if arg & (PAGE_SIZE as u32 - 1) == 0 { - PageAlignedPa( (arg & !(PAGE_SIZE as u32 - 1)) as PhysAddr ) + PageAlignedPa((arg & !(PAGE_SIZE as u32 - 1)) as PhysAddr) } else { - PageAlignedPa( ((arg & !(PAGE_SIZE as u32 - 1)) + PAGE_SIZE as u32) as PhysAddr ) + PageAlignedPa(((arg & !(PAGE_SIZE as u32 - 1)) + PAGE_SIZE as u32) as PhysAddr) } } } impl From for PageAlignedPa { fn from(arg: usize) -> Self { if arg & (PAGE_SIZE as usize - 1) == 0 { - PageAlignedPa( (arg as u32 & !(PAGE_SIZE as u32 - 1)) as PhysAddr ) + PageAlignedPa((arg as u32 & !(PAGE_SIZE as u32 - 1)) as PhysAddr) } else { - PageAlignedPa( ((arg as u32 & !(PAGE_SIZE as u32 - 1)) + PAGE_SIZE as u32) as PhysAddr ) + PageAlignedPa(((arg as u32 & !(PAGE_SIZE as u32 - 1)) + PAGE_SIZE as u32) as PhysAddr) } } } impl From for u32 { - fn from(arg: PageAlignedPa) -> Self { - arg.0 as u32 - } + fn from(arg: PageAlignedPa) -> Self { arg.0 as u32 } } impl Add for PageAlignedPa { type Output = PageAlignedPa; - fn add(self, other: PageAlignedPa) -> PageAlignedPa { - PageAlignedPa(self.0 + other.0 as PhysAddr) - } + + fn add(self, other: PageAlignedPa) -> PageAlignedPa { PageAlignedPa(self.0 + other.0 as PhysAddr) } } #[cfg(test)] @@ -197,10 +196,10 @@ mod tests { assert!(PAGE_SIZE & (PAGE_SIZE - 1) == 0, "PAGE_SIZE is not a power of two!"); } /// This test exists because nothing in the bitfield spec explicitly requires that a true maps to a 1. - /// In fact a lot of code would work just fine if you mapped true to 0 and false to 1: if you're just using - /// the generated getter and setter, it woudln't matter. - /// However, in our application, we fully expect a true to be a 1. This test exists to ensure this seemingly - /// obvious but not explicitly stated fact always remains true. + /// In fact a lot of code would work just fine if you mapped true to 0 and false to 1: if you're just + /// using the generated getter and setter, it woudln't matter. + /// However, in our application, we fully expect a true to be a 1. This test exists to ensure this + /// seemingly obvious but not explicitly stated fact always remains true. #[test] fn test_bitfield_bool() { bitfield! { diff --git a/services/pddb/src/frontend/pddbkey.rs b/services/pddb/src/frontend/pddbkey.rs index 7e1e6784e..45b95d7b3 100644 --- a/services/pddb/src/frontend/pddbkey.rs +++ b/services/pddb/src/frontend/pddbkey.rs @@ -1,10 +1,11 @@ -use crate::*; -use xous::{CID, send_message, Message}; -use xous_ipc::Buffer; +use std::io::{Error, ErrorKind, Result}; +use std::io::{Read, Seek, SeekFrom, Write}; use num_traits::*; -use std::io::{Result, Error, ErrorKind}; -use std::io::{Read, Write, Seek, SeekFrom}; +use xous::{send_message, Message, CID}; +use xous_ipc::Buffer; + +use crate::*; pub struct PddbKey<'a> { pub(crate) token: ApiToken, @@ -16,13 +17,13 @@ pub struct PddbKey<'a> { /// PddbKeys are created by Pddb impl<'a> PddbKey<'a> { /// this will clear all residual values in the buffer. Should be called whenever the Basis set changes. - pub fn volatile_clear(&mut self) { - self.buf.volatile_clear(); - } + pub fn volatile_clear(&mut self) { self.buf.volatile_clear(); } + pub fn attributes(&self) -> Result { let req = PddbKeyAttrIpc::new(self.token); let mut buf = Buffer::into_buf(req).expect("Couldn't convert memory structure"); - buf.lend_mut(self.conn, Opcode::KeyAttributes.to_u32().unwrap()).expect("couldn't execute KeyAttributes opcode"); + buf.lend_mut(self.conn, Opcode::KeyAttributes.to_u32().unwrap()) + .expect("couldn't execute KeyAttributes opcode"); let ret = buf.to_original::().expect("couldn't restore req structure"); match ret.code { PddbRequestCode::NoErr => Ok(ret.to_attributes()), @@ -50,7 +51,8 @@ impl<'a> Seek for PddbKey<'a> { SeekFrom::End(p) => { let req = PddbKeyAttrIpc::new(self.token); let mut buf = Buffer::into_buf(req).expect("Couldn't convert memory structure"); - buf.lend_mut(self.conn, Opcode::KeyAttributes.to_u32().unwrap()).expect("couldn't execute KeyAttributes opcode"); + buf.lend_mut(self.conn, Opcode::KeyAttributes.to_u32().unwrap()) + .expect("couldn't execute KeyAttributes opcode"); let ret = buf.to_original::().expect("couldn't restore req structure"); match ret.code { PddbRequestCode::NoErr => { @@ -61,7 +63,7 @@ impl<'a> Seek for PddbKey<'a> { } else { Err(Error::new(ErrorKind::InvalidInput, "Seek to negative offset")) } - }, + } PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Key not found")), _ => Err(Error::new(ErrorKind::Other, "Internal error requesting key attributes")), } @@ -78,26 +80,24 @@ impl<'a> Read for PddbKey<'a> { // create pbuf from a pre-reserved chunk of memory, to save on allocator thrashing // note that it does mean that un-erased data from previous reads and writes are passed back // to the server, which is a kind of information leakage, but I think in practice we're - // leaking that data back to a server where the data had either originated from or was disclosed at - // one point. + // leaking that data back to a server where the data had either originated from or was disclosed + // at one point. let readlen = { let pbuf = PddbBuf::from_slice_mut(self.buf.as_mut()); // sure, we could make it a loop, but...unrolled seems better pbuf.token[0] = self.token[0]; pbuf.token[1] = self.token[1]; pbuf.token[2] = self.token[2]; - let readlen = if buf.len() <= pbuf.data.len() { - buf.len() as u16 - } else { - pbuf.data.len() as u16 - }; + let readlen = + if buf.len() <= pbuf.data.len() { buf.len() as u16 } else { pbuf.data.len() as u16 }; pbuf.len = readlen; pbuf.retcode = PddbRetcode::Uninit; pbuf.position = self.pos; readlen }; // this takes the buffer and remaps it to the server, and on return the data is mapped back - self.buf.lend_mut(self.conn, Opcode::ReadKey.to_u32().unwrap()) + self.buf + .lend_mut(self.conn, Opcode::ReadKey.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; { // at this point, pbuf has been mutated by the server with a return code and the return data. @@ -112,12 +112,16 @@ impl<'a> Read for PddbKey<'a> { Ok(pbuf.len as usize) } PddbRetcode::BasisLost => Err(Error::new(ErrorKind::BrokenPipe, "Basis lost")), - PddbRetcode::AccessDenied => Err(Error::new(ErrorKind::PermissionDenied, "Access denied")), - PddbRetcode::UnexpectedEof => Ok(0), // I believe this is the "expected" behavior for reads that want to read beyond the current end of file + PddbRetcode::AccessDenied => { + Err(Error::new(ErrorKind::PermissionDenied, "Access denied")) + } + PddbRetcode::UnexpectedEof => Ok(0), /* I believe this is the "expected" behavior for + * reads that want to read beyond the current end + * of file */ _ => { log::error!("Unhandled error code: {:?}", pbuf.retcode); Err(Error::new(ErrorKind::Other, "Unhandled error code in PddbKey Read")) - }, + } } } } @@ -135,11 +139,8 @@ impl<'a> Write for PddbKey<'a> { pbuf.token[0] = self.token[0]; pbuf.token[1] = self.token[1]; pbuf.token[2] = self.token[2]; - let writelen = if buf.len() <= pbuf.data.len() { - buf.len() as u16 - } else { - pbuf.data.len() as u16 - }; + let writelen = + if buf.len() <= pbuf.data.len() { buf.len() as u16 } else { pbuf.data.len() as u16 }; pbuf.len = writelen; pbuf.retcode = PddbRetcode::Uninit; for (&src, dst) in buf.iter().zip(pbuf.data.iter_mut()) { @@ -149,7 +150,8 @@ impl<'a> Write for PddbKey<'a> { writelen }; // this takes the buffer and remaps it to the server, and on return the data is mapped back - self.buf.lend_mut(self.conn, Opcode::WriteKey.to_u32().unwrap()) + self.buf + .lend_mut(self.conn, Opcode::WriteKey.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; { // at this point, pbuf has been mutated by the server with a return code and the return data. @@ -161,22 +163,28 @@ impl<'a> Write for PddbKey<'a> { Ok(pbuf.len as usize) } PddbRetcode::BasisLost => Err(Error::new(ErrorKind::BrokenPipe, "Basis lost")), - PddbRetcode::AccessDenied => Err(Error::new(ErrorKind::PermissionDenied, "Access denied")), + PddbRetcode::AccessDenied => { + Err(Error::new(ErrorKind::PermissionDenied, "Access denied")) + } _ => Err(Error::new(ErrorKind::Other, "Unhandled error code in PddbKey Write")), } } } } + fn flush(&mut self) -> Result<()> { let response = send_message( self.conn, - Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 0, 0, 0, 0) - ).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 0, 0, 0, 0), + ) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; if let xous::Result::Scalar1(rcode) = response { match FromPrimitive::from_u8(rcode as u8) { Some(PddbRetcode::Ok) => Ok(()), Some(PddbRetcode::BasisLost) => Err(Error::new(ErrorKind::BrokenPipe, "Basis lost")), - Some(PddbRetcode::DiskFull) => Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")), + Some(PddbRetcode::DiskFull) => { + Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")) + } _ => Err(Error::new(ErrorKind::Interrupted, "Flush failed for unspecified reasons")), } } else { @@ -191,12 +199,22 @@ impl<'a> Drop for PddbKey<'a> { self.buf.volatile_clear(); // clears any confidential data in our memory buffer // notify the server that we can drop the connection state when our object goes out of scope - send_message(self.conn, Message::new_blocking_scalar(Opcode::KeyDrop.to_usize().unwrap(), - self.token[0] as usize, self.token[1] as usize, self.token[2] as usize, 0)).expect("couldn't send KeyDrop message"); + send_message( + self.conn, + Message::new_blocking_scalar( + Opcode::KeyDrop.to_usize().unwrap(), + self.token[0] as usize, + self.token[1] as usize, + self.token[2] as usize, + 0, + ), + ) + .expect("couldn't send KeyDrop message"); if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { - unsafe{xous::disconnect(self.conn).unwrap();} + unsafe { + xous::disconnect(self.conn).unwrap(); + } } } } - diff --git a/services/pddb/src/lib.rs b/services/pddb/src/lib.rs index a568e3417..0f1a93d63 100644 --- a/services/pddb/src/lib.rs +++ b/services/pddb/src/lib.rs @@ -3,59 +3,63 @@ pub mod api; pub use api::*; pub mod frontend; -pub use frontend::*; - -use num_traits::*; -use std::io::{Result, Error, ErrorKind}; -use xous::{CID, SID, msg_scalar_unpack, send_message, Message}; -use xous_ipc::Buffer; - +use core::sync::atomic::{AtomicU32, Ordering}; use std::collections::HashMap; +use std::io::{Error, ErrorKind, Result}; use std::sync::{Arc, Mutex}; use std::thread; use std::thread::JoinHandle; -use core::sync::atomic::{AtomicU32, Ordering}; +pub use frontend::*; +use num_traits::*; +use xous::{msg_scalar_unpack, send_message, Message, CID, SID}; +use xous_ipc::Buffer; pub(crate) static REFCOUNT: AtomicU32 = AtomicU32::new(0); pub(crate) static POLLER_REFCOUNT: AtomicU32 = AtomicU32::new(0); -use std::cell::RefCell; - -use core::ops::DerefMut; use core::mem::size_of; +use core::ops::DerefMut; +use std::cell::RefCell; use std::convert::TryInto; + use rkyv::{ archived_value, de::deserializers::AllocDeserializer, - ser::{Serializer, serializers::WriteSerializer}, - AlignedVec, - Deserialize, + ser::{serializers::WriteSerializer, Serializer}, + AlignedVec, Deserialize, }; #[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)] pub enum CbOp { Change, - Quit + Quit, } pub struct PddbMountPoller { - conn: CID + conn: CID, } impl PddbMountPoller { pub fn new() -> Self { POLLER_REFCOUNT.fetch_add(1, Ordering::Relaxed); let xns = xous_names::XousNames::new().unwrap(); - let conn = xns.request_connection_blocking(api::SERVER_NAME_PDDB_POLLER).expect("can't connect to Pddb mount poller server"); + let conn = xns + .request_connection_blocking(api::SERVER_NAME_PDDB_POLLER) + .expect("can't connect to Pddb mount poller server"); PddbMountPoller { conn } } - /// This call is guaranteed to *never* block and return the instantaneous state of the PDDB, even if the server itself - /// is currently busy processing other requests. This has to be done with a separate server from the main one, because - /// the main server will block during the mount operations, as it owns the PDDB data objects and cannot concurrently process - /// the mount check task while manipulating them. Instead, this routine queries a separate thread that shares an AtomicBool - /// with the main thread that reports the mount state. + + /// This call is guaranteed to *never* block and return the instantaneous state of the PDDB, even if the + /// server itself is currently busy processing other requests. This has to be done with a separate + /// server from the main one, because the main server will block during the mount operations, as it + /// owns the PDDB data objects and cannot concurrently process the mount check task while manipulating + /// them. Instead, this routine queries a separate thread that shares an AtomicBool with the main + /// thread that reports the mount state. pub fn is_mounted_nonblocking(&self) -> bool { - match send_message(self.conn, - Message::new_blocking_scalar(PollOp::Poll.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't poll mount poller") { + match send_message( + self.conn, + Message::new_blocking_scalar(PollOp::Poll.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't poll mount poller") + { xous::Result::Scalar1(is_mounted) => { if is_mounted == 0 { false @@ -63,19 +67,20 @@ impl PddbMountPoller { true } } - _ => false + _ => false, } } } impl Drop for PddbMountPoller { fn drop(&mut self) { if POLLER_REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { - unsafe{xous::disconnect(self.conn).unwrap();} + unsafe { + xous::disconnect(self.conn).unwrap(); + } } } } - /// The intention is that one Pddb management object is made per process, and this serves /// as the gateway for parcelling out PddbKey objects, which are the equivalent of a File /// in a convention system that implements read/write operations. @@ -84,9 +89,10 @@ impl Drop for PddbMountPoller { /// and callbacks in case data changes. pub struct Pddb { conn: CID, - /// a SID that we can directly share with the PDDB server for the purpose of handling key change callbacks + /// a SID that we can directly share with the PDDB server for the purpose of handling key change + /// callbacks cb: RefCell>, - cb_handle: RefCell>>, + cb_handle: RefCell>>, /// Handle key change updates. The general intent is that the closure implements a /// `send` of a message to the server to deal with a key change appropriately, /// but no mutable data is allowed within the closure itself due to safety problems. @@ -97,7 +103,7 @@ pub struct Pddb { /// the application behavior might be something like a refresh of the data from storage /// in the case of a basis change. Basis changes are thought to be rare; so, big changes /// like this are probably OK. - keys: Arc >>>, + keys: Arc>>>, trng: trng::Trng, /// These are temporary fields only to be used by the consistency check feature. key_count: RefCell, @@ -107,8 +113,10 @@ impl Pddb { pub fn new() -> Self { REFCOUNT.fetch_add(1, Ordering::Relaxed); let xns = xous_names::XousNames::new().unwrap(); - let conn = xns.request_connection_blocking(api::SERVER_NAME_PDDB).expect("can't connect to Pddb server"); - let keys: Arc >>> = Arc::new(Mutex::new(HashMap::new())); + let conn = + xns.request_connection_blocking(api::SERVER_NAME_PDDB).expect("can't connect to Pddb server"); + let keys: Arc>>> = + Arc::new(Mutex::new(HashMap::new())); Pddb { conn, cb: RefCell::new(None), @@ -120,6 +128,7 @@ impl Pddb { found_key_count: RefCell::new(0), } } + fn ensure_async_responder(&self) { if self.cb.borrow().is_none() { let sid = xous::create_server().unwrap(); @@ -138,11 +147,12 @@ impl Pddb { log::warn!("Key changed but no callback was hooked to receive it"); } }), - Some(CbOp::Quit) => { // blocking scalar + Some(CbOp::Quit) => { + // blocking scalar xous::return_scalar(msg.sender, 0).unwrap(); break; - }, - _ =>log::warn!("Got unknown opcode: {:?}", msg), + } + _ => log::warn!("Got unknown opcode: {:?}", msg), } } xous::destroy_server(sid).unwrap(); @@ -152,48 +162,63 @@ impl Pddb { self.cb_handle.replace(Some(handle)); } } + /// This blocks until the PDDB is mounted by the end user. It blocks using the deferred-response pattern, /// so the blocking does not spin-wait; the caller does not consume CPU cycles. /// /// This is typically the API call one would use to hold execution of a service until the PDDB is mounted. pub fn is_mounted_blocking(&self) { loop { - let ret = send_message(self.conn, Message::new_blocking_scalar( - Opcode::IsMounted.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query"); + let ret = send_message( + self.conn, + Message::new_blocking_scalar(Opcode::IsMounted.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't execute IsMounted query"); match ret { xous::Result::Scalar2(code, _count) => { - if code == 0 { // mounted successfully + if code == 0 { + // mounted successfully break; } - }, + } _ => panic!("Internal error"), } } } + pub fn mount_attempted_blocking(&self) { loop { - let ret = send_message(self.conn, Message::new_blocking_scalar( - Opcode::MountAttempted.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query"); + let ret = send_message( + self.conn, + Message::new_blocking_scalar(Opcode::MountAttempted.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't execute IsMounted query"); match ret { xous::Result::Scalar2(code, _count) => { - if code == 0 { // mounted successfully + if code == 0 { + // mounted successfully break; } - }, + } _ => panic!("Internal error"), } } } + /// Attempts to mount the system basis. Returns `true` on success, `false` on failure. - /// This call may cause a password request box to pop up, in the case that the boot PIN is not currently cached. + /// This call may cause a password request box to pop up, in the case that the boot PIN is not currently + /// cached. /// /// Returns: /// Ok(true) on successful mount /// Ok(false) on user-directed abort of mount /// Ok(usize) on system-forced abort of mount, with `count` failures pub fn try_mount(&self) -> (bool, usize) { - let ret = send_message(self.conn, Message::new_blocking_scalar( - Opcode::TryMount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute TryMounted query"); + let ret = send_message( + self.conn, + Message::new_blocking_scalar(Opcode::TryMount.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't execute TryMounted query"); match ret { xous::Result::Scalar2(code, count) => { if code == 0 { @@ -206,48 +231,59 @@ impl Pddb { // system aborted with `count` retries (false, count) } - }, + } _ => panic!("TryMount unexpected return result"), } } + /// Unmounts the PDDB. First attempts to unmount any open secret bases, and then finally unmounts /// the system basis. Returns `true` on success, `false` on failure. pub fn try_unmount(&self) -> bool { - let ret = send_message(self.conn, Message::new_blocking_scalar( - Opcode::TryUnmount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute TryUnmount query"); + let ret = send_message( + self.conn, + Message::new_blocking_scalar(Opcode::TryUnmount.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't execute TryUnmount query"); match ret { xous::Result::Scalar1(code) => { - if code == 0 {false} else {true} - }, + if code == 0 { + false + } else { + true + } + } _ => panic!("Internal error"), } } - /// This is a non-blocking message that permanently halts the PDDB server by wedging it in an infinite loop. - /// It is meant to be called prior to system shutdows or backups, to ensure that other auto-mounting processes - /// don't undo the shutdown procedure because of an ill-timed "cron" job (the system doesn't literally have - /// a cron daemon, but it does have the notion of long-running background jobs that might do something like - /// trigger an NTP update, which would then try to write the updated time to the PDDB). + + /// This is a non-blocking message that permanently halts the PDDB server by wedging it in an infinite + /// loop. It is meant to be called prior to system shutdows or backups, to ensure that other + /// auto-mounting processes don't undo the shutdown procedure because of an ill-timed "cron" job (the + /// system doesn't literally have a cron daemon, but it does have the notion of long-running + /// background jobs that might do something like trigger an NTP update, which would then try to write + /// the updated time to the PDDB). pub fn pddb_halt(&self) { - send_message(self.conn, Message::new_scalar( - Opcode::PddbHalt.to_usize().unwrap(), 0, 0, 0, 0)).expect("Couldn't halt the PDDB"); + send_message(self.conn, Message::new_scalar(Opcode::PddbHalt.to_usize().unwrap(), 0, 0, 0, 0)) + .expect("Couldn't halt the PDDB"); } - /// Computes checksums on the entire PDDB database. This operation can take some time and causes a progress - /// bar to pop up. This should be called only after the PDDB has been unmounted, to ensure that the disk - /// contents do not change after the checksums have been computed. + + /// Computes checksums on the entire PDDB database. This operation can take some time and causes a + /// progress bar to pop up. This should be called only after the PDDB has been unmounted, to ensure + /// that the disk contents do not change after the checksums have been computed. pub fn compute_checksums(&self) -> root_keys::api::Checksums { let alloc = root_keys::api::Checksums::default(); let mut buf = Buffer::into_buf(alloc).expect("Couldn't convert memory structure"); - buf.lend_mut(self.conn, Opcode::ComputeBackupHashes.to_u32().unwrap()).expect("Couldn't execute ComputeBackupHashes"); + buf.lend_mut(self.conn, Opcode::ComputeBackupHashes.to_u32().unwrap()) + .expect("Couldn't execute ComputeBackupHashes"); buf.to_original::().expect("Couldn't convert IPC structure") } + /// return a list of all open bases - pub fn list_basis(&self) -> Vec:: { - let list_alloc = PddbBasisList { - list: [xous_ipc::String::::default(); 63], - num: 0 - }; + pub fn list_basis(&self) -> Vec { + let list_alloc = PddbBasisList { list: [xous_ipc::String::::default(); 63], num: 0 }; let mut buf = Buffer::into_buf(list_alloc).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::ListBasis.to_u32().unwrap()).expect("Couldn't execute ListBasis opcode"); + buf.lend_mut(self.conn, Opcode::ListBasis.to_u32().unwrap()) + .expect("Couldn't execute ListBasis opcode"); let list = buf.to_original::().expect("couldn't restore list structure"); if list.num > list.list.len() as u32 { log::warn!("Number of open basis larger than our IPC structure. May need to refactor this API."); @@ -261,14 +297,13 @@ impl Pddb { } ret } + /// The caller of this function will block and return only if the order of Bases has changed. - pub fn monitor_basis(&self) -> Vec:: { - let list_alloc = PddbBasisList { - list: [xous_ipc::String::::default(); 63], - num: 0 - }; + pub fn monitor_basis(&self) -> Vec { + let list_alloc = PddbBasisList { list: [xous_ipc::String::::default(); 63], num: 0 }; let mut buf = Buffer::into_buf(list_alloc).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::BasisMonitor.to_u32().unwrap()).expect("Couldn't execute ListBasis opcode"); + buf.lend_mut(self.conn, Opcode::BasisMonitor.to_u32().unwrap()) + .expect("Couldn't execute ListBasis opcode"); let list = buf.to_original::().expect("couldn't restore list structure"); if list.num > list.list.len() as u32 { log::warn!("Number of open basis larger than our IPC structure. May need to refactor this API."); @@ -282,6 +317,7 @@ impl Pddb { } ret } + /// returns the latest basis that is opened -- this is where all new values are being sent by default /// if the PDDB is not mounted, returns None pub fn latest_basis(&self) -> Option { @@ -291,21 +327,19 @@ impl Pddb { policy: None, }; let mut buf = Buffer::into_buf(mgmt).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::LatestBasis.to_u32().unwrap()).expect("Couldn't execute ListBasis opcode"); + buf.lend_mut(self.conn, Opcode::LatestBasis.to_u32().unwrap()) + .expect("Couldn't execute ListBasis opcode"); let ret = buf.to_original::().expect("couldn't restore list structure"); match ret.code { - PddbRequestCode::NoErr => { - Some(ret.name.as_str().expect("name wasn't valid utf-8").to_string()) - } - PddbRequestCode::NotMounted => { - None - } + PddbRequestCode::NoErr => Some(ret.name.as_str().expect("name wasn't valid utf-8").to_string()), + PddbRequestCode::NotMounted => None, _ => { log::error!("Invalid return from latest basis call"); panic!("Invalid return from latest basis call"); } } } + pub fn create_basis(&self, basis_name: &str) -> Result<()> { if basis_name.len() > BASIS_NAME_LEN - 1 { return Err(Error::new(ErrorKind::InvalidInput, "basis name too long")); @@ -316,19 +350,27 @@ impl Pddb { policy: None, }; let mut buf = Buffer::into_buf(mgmt).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::CreateBasis.to_u32().unwrap()).expect("Couldn't execute CreateBasis opcode"); + buf.lend_mut(self.conn, Opcode::CreateBasis.to_u32().unwrap()) + .expect("Couldn't execute CreateBasis opcode"); let ret = buf.to_original::().expect("couldn't restore mgmt structure"); match ret.code { PddbRequestCode::NoErr => Ok(()), - PddbRequestCode::NoFreeSpace => Err(Error::new(ErrorKind::OutOfMemory, "No free space to create basis")), - PddbRequestCode::InternalError => Err(Error::new(ErrorKind::Other, "Internal error creating basis")), - PddbRequestCode::DuplicateEntry => Err(Error::new(ErrorKind::AlreadyExists, "Basis already exists")), + PddbRequestCode::NoFreeSpace => { + Err(Error::new(ErrorKind::OutOfMemory, "No free space to create basis")) + } + PddbRequestCode::InternalError => { + Err(Error::new(ErrorKind::Other, "Internal error creating basis")) + } + PddbRequestCode::DuplicateEntry => { + Err(Error::new(ErrorKind::AlreadyExists, "Basis already exists")) + } _ => { log::error!("Invalid return code"); panic!("Invalid return code"); } } } + pub fn unlock_basis(&self, basis_name: &str, policy: Option) -> Result<()> { if basis_name.len() > BASIS_NAME_LEN - 1 { return Err(Error::new(ErrorKind::InvalidInput, "basis name too long")); @@ -339,18 +381,24 @@ impl Pddb { policy, }; let mut buf = Buffer::into_buf(mgmt).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::OpenBasis.to_u32().unwrap()).expect("Couldn't execute OpenBasis opcode"); + buf.lend_mut(self.conn, Opcode::OpenBasis.to_u32().unwrap()) + .expect("Couldn't execute OpenBasis opcode"); let ret = buf.to_original::().expect("couldn't restore mgmt structure"); match ret.code { PddbRequestCode::NoErr => Ok(()), - PddbRequestCode::AccessDenied => Err(Error::new(ErrorKind::PermissionDenied, "Authentication error")), - PddbRequestCode::InternalError => Err(Error::new(ErrorKind::Other, "Internal error creating basis")), + PddbRequestCode::AccessDenied => { + Err(Error::new(ErrorKind::PermissionDenied, "Authentication error")) + } + PddbRequestCode::InternalError => { + Err(Error::new(ErrorKind::Other, "Internal error creating basis")) + } _ => { log::error!("Invalid return code"); panic!("Invalid return code"); } } } + pub fn lock_basis(&self, basis_name: &str) -> Result<()> { if basis_name.len() > BASIS_NAME_LEN - 1 { return Err(Error::new(ErrorKind::InvalidInput, "basis name too long")); @@ -361,18 +409,22 @@ impl Pddb { policy: None, }; let mut buf = Buffer::into_buf(mgmt).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::CloseBasis.to_u32().unwrap()).expect("Couldn't execute CloseBasis opcode"); + buf.lend_mut(self.conn, Opcode::CloseBasis.to_u32().unwrap()) + .expect("Couldn't execute CloseBasis opcode"); let ret = buf.to_original::().expect("couldn't restore mgmt structure"); match ret.code { PddbRequestCode::NoErr => Ok(()), PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Basis not found")), - PddbRequestCode::InternalError => Err(Error::new(ErrorKind::Other, "Internal error closing basis")), + PddbRequestCode::InternalError => { + Err(Error::new(ErrorKind::Other, "Internal error closing basis")) + } _ => { log::error!("Invalid return code"); panic!("Invalid return code"); } } } + pub fn delete_basis(&self, basis_name: &str) -> Result<()> { if basis_name.len() > BASIS_NAME_LEN - 1 { return Err(Error::new(ErrorKind::InvalidInput, "basis name too long")); @@ -383,12 +435,15 @@ impl Pddb { policy: None, }; let mut buf = Buffer::into_buf(mgmt).expect("Couldn't convert to memory structure"); - buf.lend_mut(self.conn, Opcode::DeleteBasis.to_u32().unwrap()).expect("Couldn't execute DeleteBasis opcode"); + buf.lend_mut(self.conn, Opcode::DeleteBasis.to_u32().unwrap()) + .expect("Couldn't execute DeleteBasis opcode"); let ret = buf.to_original::().expect("couldn't restore mgmt structure"); match ret.code { PddbRequestCode::NoErr => Ok(()), PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Basis not found")), - PddbRequestCode::InternalError => Err(Error::new(ErrorKind::Other, "Internal error deleting basis")), + PddbRequestCode::InternalError => { + Err(Error::new(ErrorKind::Other, "Internal error deleting basis")) + } _ => { log::error!("Invalid return code"); panic!("Invalid return code"); @@ -398,12 +453,20 @@ impl Pddb { /// If the `create_*` flags are set, creates the asset if they do not exist, otherwise if false, returns /// an error if the asset does not exist. - /// `alloc_hint` is an optional field to guide the PDDB allocator to put the key in the right pool. Setting it to `None` - /// is perfectly fine, it just has a potential performance impact, especially for very large keys. - /// `key_changed_cb` is a static function meant to initiate a message to a server in case the key in question - /// goes away due to a basis locking. - pub fn get(&self, dict_name: &str, key_name: &str, basis_name: Option<&str>, - create_dict: bool, create_key: bool, alloc_hint: Option, key_changed_cb: Option) -> Result { + /// `alloc_hint` is an optional field to guide the PDDB allocator to put the key in the right pool. + /// Setting it to `None` is perfectly fine, it just has a potential performance impact, especially for + /// very large keys. `key_changed_cb` is a static function meant to initiate a message to a server in + /// case the key in question goes away due to a basis locking. + pub fn get( + &self, + dict_name: &str, + key_name: &str, + basis_name: Option<&str>, + create_dict: bool, + create_key: bool, + alloc_hint: Option, + key_changed_cb: Option, + ) -> Result { if key_name.len() > (KEY_NAME_LEN - 1) { return Err(Error::new(ErrorKind::InvalidInput, "key name too long")); } @@ -424,11 +487,7 @@ impl Pddb { } let maybe_cb = self.cb.take(); - let cb_sid = if let Some(sid) = maybe_cb { - Some(sid.to_array()) - } else { - None - }; + let cb_sid = if let Some(sid) = maybe_cb { Some(sid.to_array()) } else { None }; self.cb.replace(maybe_cb); let request = PddbKeyRequest { @@ -441,10 +500,10 @@ impl Pddb { token: None, result: PddbRequestCode::Uninit, cb_sid, - alloc_hint: if let Some(a) = alloc_hint {Some(a as u64)} else {None}, + alloc_hint: if let Some(a) = alloc_hint { Some(a as u64) } else { None }, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::KeyRequest.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; @@ -462,10 +521,11 @@ impl Pddb { } REFCOUNT.fetch_add(1, Ordering::Relaxed); let pk = PddbKey { - // i think these fields are redundant, let's save the storage and remove them for now... - //dict: String::from(dict_name), + // i think these fields are redundant, let's save the storage and remove them for + // now... dict: String::from(dict_name), //key: String::from(key_name), - //basis: if basis_name.is_some() {Some(String::from(bname.as_str().unwrap()))} else {None}, + //basis: if basis_name.is_some() {Some(String::from(bname.as_str().unwrap()))} else + // {None}, pos: 0, token, buf: Buffer::new(core::mem::size_of::()), @@ -476,10 +536,14 @@ impl Pddb { Err(Error::new(ErrorKind::PermissionDenied, "Dict/Key access denied")) } } - PddbRequestCode::AccessDenied => Err(Error::new(ErrorKind::PermissionDenied, "Dict/Key access denied")), + PddbRequestCode::AccessDenied => { + Err(Error::new(ErrorKind::PermissionDenied, "Dict/Key access denied")) + } PddbRequestCode::NoFreeSpace => Err(Error::new(ErrorKind::OutOfMemory, "No more space on disk")), PddbRequestCode::NotMounted => Err(Error::new(ErrorKind::ConnectionReset, "PDDB was unmounted")), - PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")), + PddbRequestCode::NotFound => { + Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")) + } _ => Err(Error::new(ErrorKind::Other, format!("Unhandled return code: {:?}", response.result))), } } @@ -502,11 +566,7 @@ impl Pddb { }; let maybe_cb = self.cb.take(); - let cb_sid = if let Some(sid) = maybe_cb { - Some(sid.to_array()) - } else { - None - }; + let cb_sid = if let Some(sid) = maybe_cb { Some(sid.to_array()) } else { None }; self.cb.replace(maybe_cb); let request = PddbKeyRequest { @@ -521,22 +581,31 @@ impl Pddb { cb_sid, alloc_hint: None, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::DeleteKey.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; let response = buf.to_original::().unwrap(); match response.result { PddbRequestCode::NoErr => Ok(()), - PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")), - _ => Err(Error::new(ErrorKind::Other, "Internal error")) + PddbRequestCode::NotFound => { + Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")) + } + _ => Err(Error::new(ErrorKind::Other, "Internal error")), } } - /// deletes a list of keys from a dictionary. The list of keys must be less than MAX_PDDB_DELETE_LEN characters long. - pub fn delete_key_list(&self, dict_name: &str, key_list: Vec::, basis_name: Option<&str>) -> Result<()> { + + /// deletes a list of keys from a dictionary. The list of keys must be less than MAX_PDDB_DELETE_LEN + /// characters long. + pub fn delete_key_list( + &self, + dict_name: &str, + key_list: Vec, + basis_name: Option<&str>, + ) -> Result<()> { if key_list.len() == 0 { - return Ok(()) + return Ok(()); } if dict_name.len() > (DICT_NAME_LEN - 1) { return Err(Error::new(ErrorKind::InvalidInput, "dictionary name too long")); @@ -554,7 +623,7 @@ impl Pddb { basis: xous_ipc::String::::from_str(&bname), dict: xous_ipc::String::::from_str(dict_name), retcode: PddbRetcode::Uninit, - data: [0u8; MAX_PDDB_DELETE_LEN] + data: [0u8; MAX_PDDB_DELETE_LEN], }; let mut index = 0; for keyname in key_list { @@ -568,21 +637,30 @@ impl Pddb { request.data[index..index + keyname.len()].copy_from_slice(keyname.as_bytes()); index += keyname.len(); } else { - return Err(Error::new(ErrorKind::OutOfMemory, "Key list total size exceeds MAX_PDDBKLISTLEN")); + return Err(Error::new( + ErrorKind::OutOfMemory, + "Key list total size exceeds MAX_PDDBKLISTLEN", + )); } } - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::DictBulkDelete.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; let response = buf.as_flat::().unwrap(); match response.retcode { ArchivedPddbRetcode::Ok => Ok(()), - ArchivedPddbRetcode::AccessDenied => Err(Error::new(ErrorKind::NotFound, "Dictionary not found, or inaccessible")), - ArchivedPddbRetcode::Uninit => Err(Error::new(ErrorKind::ConnectionAborted, "Return code not set processing bulk delete, server aborted?")), + ArchivedPddbRetcode::AccessDenied => { + Err(Error::new(ErrorKind::NotFound, "Dictionary not found, or inaccessible")) + } + ArchivedPddbRetcode::Uninit => Err(Error::new( + ErrorKind::ConnectionAborted, + "Return code not set processing bulk delete, server aborted?", + )), _ => Err(Error::new(ErrorKind::Other, "Internal Error handling bulk delete list")), } } + /// deletes the entire dictionary pub fn delete_dict(&self, dict_name: &str, basis_name: Option<&str>) -> Result<()> { if dict_name.len() > (DICT_NAME_LEN - 1) { @@ -598,11 +676,7 @@ impl Pddb { }; let maybe_cb = self.cb.take(); - let cb_sid = if let Some(sid) = maybe_cb { - Some(sid.to_array()) - } else { - None - }; + let cb_sid = if let Some(sid) = maybe_cb { Some(sid.to_array()) } else { None }; self.cb.replace(maybe_cb); let request = PddbKeyRequest { @@ -617,46 +691,55 @@ impl Pddb { cb_sid, alloc_hint: None, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::DeleteDict.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; let response = buf.to_original::().unwrap(); match response.result { PddbRequestCode::NoErr => Ok(()), - PddbRequestCode::NotFound => Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")), - _ => Err(Error::new(ErrorKind::Other, "Internal error")) + PddbRequestCode::NotFound => { + Err(Error::new(ErrorKind::NotFound, "Dictionary or key was not found")) + } + _ => Err(Error::new(ErrorKind::Other, "Internal error")), } } pub fn sync(&self) -> Result<()> { let response = send_message( self.conn, - Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 0, 0, 0, 0) - ).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 0, 0, 0, 0), + ) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; if let xous::Result::Scalar1(rcode) = response { match FromPrimitive::from_u8(rcode as u8) { Some(PddbRetcode::Ok) => Ok(()), Some(PddbRetcode::BasisLost) => Err(Error::new(ErrorKind::BrokenPipe, "Basis lost")), - Some(PddbRetcode::DiskFull) => Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")), + Some(PddbRetcode::DiskFull) => { + Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")) + } _ => Err(Error::new(ErrorKind::Interrupted, "Flush failed for unspecified reasons")), } } else { Err(Error::new(ErrorKind::Other, "Xous internal error")) } } + /// cleans up inconsistencies in the PDDB. fsck-like. pub fn sync_cleanup(&self) -> Result<()> { let response = send_message( self.conn, - Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 1, 0, 0, 0) - ).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + Message::new_blocking_scalar(Opcode::WriteKeyFlush.to_usize().unwrap(), 1, 0, 0, 0), + ) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; if let xous::Result::Scalar1(rcode) = response { match FromPrimitive::from_u8(rcode as u8) { Some(PddbRetcode::Ok) => Ok(()), Some(PddbRetcode::BasisLost) => Err(Error::new(ErrorKind::BrokenPipe, "Basis lost")), - Some(PddbRetcode::DiskFull) => Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")), + Some(PddbRetcode::DiskFull) => { + Err(Error::new(ErrorKind::OutOfMemory, "Out of disk space, some data lost on sync")) + } _ => Err(Error::new(ErrorKind::Interrupted, "Flush failed for unspecified reasons")), } } else { @@ -673,7 +756,7 @@ impl Pddb { (*self.key_count.borrow(), *self.found_key_count.borrow()) } - pub fn list_keys(&self, dict_name: &str, basis_name: Option<&str>) -> Result> { + pub fn list_keys(&self, dict_name: &str, basis_name: Option<&str>) -> Result> { if dict_name.len() > (DICT_NAME_LEN - 1) { return Err(Error::new(ErrorKind::InvalidInput, "dictionary name too long")); } @@ -685,8 +768,14 @@ impl Pddb { } else { xous_ipc::String::::new() }; - // this is a two-phase query, because it's quite likely that the number of keys can be very large in a dict. - let token = [self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap()]; + // this is a two-phase query, because it's quite likely that the number of keys can be very large in a + // dict. + let token = [ + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + ]; let request = PddbDictRequest { basis_specified: basis_name.is_some(), basis: xous_ipc::String::::from_str(&bname), @@ -699,8 +788,8 @@ impl Pddb { key_count: 0, found_key_count: 0, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::KeyCountInDict.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; @@ -708,14 +797,21 @@ impl Pddb { match response.code { PddbRequestCode::NoErr => (), PddbRequestCode::NotFound => return Err(Error::new(ErrorKind::NotFound, "dictionary not found")), - PddbRequestCode::AccessDenied => return Err(Error::new(ErrorKind::PermissionDenied, "concurrent operation in progress")), - PddbRequestCode::Uninit => return Err(Error::new(ErrorKind::ConnectionAborted, "Return code not set getting key count, server aborted?")), + PddbRequestCode::AccessDenied => { + return Err(Error::new(ErrorKind::PermissionDenied, "concurrent operation in progress")); + } + PddbRequestCode::Uninit => { + return Err(Error::new( + ErrorKind::ConnectionAborted, + "Return code not set getting key count, server aborted?", + )); + } _ => return Err(Error::new(ErrorKind::Other, "Internal error generating key count")), }; *self.key_count.borrow_mut() = response.key_count; *self.found_key_count.borrow_mut() = response.found_key_count; - // v2 key listing packs the key list into a larger [u8] field that should cut down on the number of messages - // required to list a large dictionary by about a factor of 50. + // v2 key listing packs the key list into a larger [u8] field that should cut down on the number of + // messages required to list a large dictionary by about a factor of 50. let mut key_list = Vec::::new(); // just make sure we didn't screw up the sizing of this record assert!(core::mem::size_of::() < 4096); @@ -724,17 +820,27 @@ impl Pddb { token, end: false, retcode: PddbRetcode::Uninit, - data: [0u8; MAX_PDDBKLISTLEN] + data: [0u8; MAX_PDDBKLISTLEN], }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::ListKeyV2.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; let response = buf.as_flat::().unwrap(); match response.retcode { ArchivedPddbRetcode::Ok => (), - ArchivedPddbRetcode::AccessDenied => return Err(Error::new(ErrorKind::PermissionDenied, "KeyList facility locked by another process, try again later")), - ArchivedPddbRetcode::Uninit => return Err(Error::new(ErrorKind::ConnectionAborted, "Return code not set fetching list, server aborted?")), + ArchivedPddbRetcode::AccessDenied => { + return Err(Error::new( + ErrorKind::PermissionDenied, + "KeyList facility locked by another process, try again later", + )); + } + ArchivedPddbRetcode::Uninit => { + return Err(Error::new( + ErrorKind::ConnectionAborted, + "Return code not set fetching list, server aborted?", + )); + } _ => return Err(Error::new(ErrorKind::Other, "Internal Error fetching list")), } // the [u8] data is structured as a packed list of u8-len + u8 data slice. The max length of @@ -748,10 +854,13 @@ impl Pddb { log::error!("Logic error in key list, index would be out of bounds. Aborting"); break; } - if strlen == 0 { // case of an empty dictionary + if strlen == 0 { + // case of an empty dictionary break; } - let key = String::from(std::str::from_utf8(&response.data[index..index+strlen]).unwrap_or("UTF8 error")); + let key = String::from( + std::str::from_utf8(&response.data[index..index + strlen]).unwrap_or("UTF8 error"), + ); log::trace!("Returned {}", key); key_list.push(key); index += strlen; @@ -759,13 +868,11 @@ impl Pddb { if response.end { break; } - } Ok(key_list) } - - pub fn list_dict(&self, basis_name: Option<&str>) -> Result> { + pub fn list_dict(&self, basis_name: Option<&str>) -> Result> { let bname = if let Some(bname) = basis_name { if bname.len() > BASIS_NAME_LEN - 1 { return Err(Error::new(ErrorKind::InvalidInput, "basis name too long")); @@ -774,8 +881,14 @@ impl Pddb { } else { xous_ipc::String::::new() }; - // this is a two-phase query, because it's quite likely that the number of keys can be very large in a dict. - let token = [self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap(), self.trng.get_u32().unwrap()]; + // this is a two-phase query, because it's quite likely that the number of keys can be very large in a + // dict. + let token = [ + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + self.trng.get_u32().unwrap(), + ]; let request = PddbDictRequest { basis_specified: basis_name.is_some(), basis: xous_ipc::String::::from_str(&bname), @@ -788,8 +901,8 @@ impl Pddb { key_count: 0, found_key_count: 0, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::DictCountInBasis.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; @@ -815,81 +928,95 @@ impl Pddb { key_count: 0, found_key_count: 0, }; - let mut buf = Buffer::into_buf(request) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = + Buffer::into_buf(request).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::GetDictNameAtIndex.to_u32().unwrap()) .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; let response = buf.to_original::().unwrap(); match response.code { - PddbRequestCode::NoErr => dict_list.push(String::from(response.dict.as_str().expect("utf-8 parse error in key name"))), + PddbRequestCode::NoErr => dict_list + .push(String::from(response.dict.as_str().expect("utf-8 parse error in key name"))), _ => return Err(Error::new(ErrorKind::Other, "Internal error")), } } Ok(dict_list) } - /// Public function to query efuse security state. Replicated here to avoid exposing RootKeys full API to the world. + + /// Public function to query efuse security state. Replicated here to avoid exposing RootKeys full API to + /// the world. pub fn is_efuse_secured(&self) -> bool { - let response = send_message(self.conn, - Message::new_blocking_scalar(Opcode::IsEfuseSecured.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't make call to query efuse security state"); + let response = send_message( + self.conn, + Message::new_blocking_scalar(Opcode::IsEfuseSecured.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't make call to query efuse security state"); if let xous::Result::Scalar1(result) = response { - if result == 1 { - true - } else { - false - } + if result == 1 { true } else { false } } else { panic!("Internal error: wrong return code for is_efuse_secured()"); } } - /// Reset the "don't ask to init root keys" flag. Used primarily by the OQC test routine to reset this in case - /// a worker accidentally set it during testing. + + /// Reset the "don't ask to init root keys" flag. Used primarily by the OQC test routine to reset this in + /// case a worker accidentally set it during testing. pub fn reset_dont_ask_init(&self) { - send_message(self.conn, - Message::new_blocking_scalar(Opcode::ResetDontAskInit.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't send ResetDontAskInit"); + send_message( + self.conn, + Message::new_blocking_scalar(Opcode::ResetDontAskInit.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't send ResetDontAskInit"); } - /// Flush the `SpaceUpdate` journal. SpaceUpdates will leak the last couple hundred or so free space operations, - /// so periodically flushing this is needed to restore deniability. + + /// Flush the `SpaceUpdate` journal. SpaceUpdates will leak the last couple hundred or so free space + /// operations, so periodically flushing this is needed to restore deniability. pub fn flush_space_update(&self) { - send_message(self.conn, - Message::new_blocking_scalar(Opcode::FlushSpaceUpdate.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't send FlushSpaceUpdate"); + send_message( + self.conn, + Message::new_blocking_scalar(Opcode::FlushSpaceUpdate.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't send FlushSpaceUpdate"); } + /// Manually prune the PDDB cache. /// Mostly provided for force-triggering for testing; normally this is done automatically pub fn manual_prune(&self) { - send_message(self.conn, - Message::new_blocking_scalar(Opcode::Prune.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't send FlushSpaceUpdate"); + send_message(self.conn, Message::new_blocking_scalar(Opcode::Prune.to_usize().unwrap(), 0, 0, 0, 0)) + .expect("couldn't send FlushSpaceUpdate"); } + /// Rekey the PDDB. This can be a very long-running blocking operation that will definitely. /// interrupt normal user flow. pub fn rekey_pddb(&self, op: PddbRekeyOp) -> Result<()> { - let mut buf = Buffer::into_buf(op) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let mut buf = Buffer::into_buf(op).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend_mut(self.conn, Opcode::RekeyPddb.to_u32().unwrap()) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))).map(|_| ())?; - let result = buf.to_original::() + .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))) + .map(|_| ())?; + let result = buf + .to_original::() .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; match result { PddbRekeyOp::Success => Ok(()), PddbRekeyOp::AuthFail => Err(Error::new(ErrorKind::PermissionDenied, "Permission error")), PddbRekeyOp::UserAbort => Err(Error::new(ErrorKind::Interrupted, "User aborted operation")), PddbRekeyOp::InternalError => Err(Error::new(ErrorKind::Other, "Xous internal error")), - PddbRekeyOp::VerifyFail => Err(Error::new(ErrorKind::InvalidData, "Data verification error while rekeying")), + PddbRekeyOp::VerifyFail => { + Err(Error::new(ErrorKind::InvalidData, "Data verification error while rekeying")) + } _ => Err(Error::new(ErrorKind::Unsupported, "Return code was never set")), } } pub fn run_test(&self, args: [u32; 4]) -> Result<(u32, u32)> { - match send_message(self.conn, - Message::new_blocking_scalar(Opcode::InternalTest.to_usize().unwrap(), - args[0] as usize, - args[1] as usize, - args[2] as usize, - args[3] as usize, - )) { + match send_message( + self.conn, + Message::new_blocking_scalar( + Opcode::InternalTest.to_usize().unwrap(), + args[0] as usize, + args[1] as usize, + args[2] as usize, + args[3] as usize, + ), + ) { Ok(xous::Result::Scalar2(a, b)) => Ok((a as u32, b as u32)), Ok(xous::Result::Scalar1(a)) => Ok((a as u32, 0)), _ => Err(Error::new(ErrorKind::Other, "Return type not recognized")), @@ -901,58 +1028,63 @@ impl Pddb { /// data sections are `None`, instead of `Some(Vec::)`. Keys that are zero-length are also returned /// with a data section of `None`; check the `len` field of the PddbKeyRecord to determine which is which. /// Defaults to a size limit of up to 32k of bulk data returned, if it is not explicitly specified. - pub fn read_dict(&self, dict: &str, basis: Option<&str>, size_limit: Option) -> Result> { + pub fn read_dict( + &self, + dict: &str, + basis: Option<&str>, + size_limit: Option, + ) -> Result> { // about the biggest we can move around in Precursor and not break heap. const MAX_BUFLEN: usize = 32 * 1024; // compromise between memory zeroing time and latency to send a message const DEFAULT_LIMIT: usize = 32 * 1024; /* - The operation proceeds in two phases. All operations use the DictBulkRead opcode. + The operation proceeds in two phases. All operations use the DictBulkRead opcode. - The first phase establishes the read request. The caller first decides how many - memory pages it will use to return data from the PDDB main thread, and allocates - an appropriately-sized buffer. + The first phase establishes the read request. The caller first decides how many + memory pages it will use to return data from the PDDB main thread, and allocates + an appropriately-sized buffer. - It then serializes a `PddbDictRequest` structure, where the token and index fields are disregarded. + It then serializes a `PddbDictRequest` structure, where the token and index fields are disregarded. - The main server then receives this, and if there are no tokens pending for `DictBulkRead`, it allocates - a 128-bit token for this transaction and records it, locking out any other potential requests. - If a token is already allocated, it responds immediately with an error code. + The main server then receives this, and if there are no tokens pending for `DictBulkRead`, it allocates + a 128-bit token for this transaction and records it, locking out any other potential requests. + If a token is already allocated, it responds immediately with an error code. - The main server proceeds to serialize keys out of the dictionary into `PddbKeyRecord` structures. - It will serialize exactly as many as can fit into the given buffer. In case the data is larger than - the allocated buffer, the data will always be returned as `None`; in case the data is larger than - the available space, the serialization stops, and the record is returned. + The main server proceeds to serialize keys out of the dictionary into `PddbKeyRecord` structures. + It will serialize exactly as many as can fit into the given buffer. In case the data is larger than + the allocated buffer, the data will always be returned as `None`; in case the data is larger than + the available space, the serialization stops, and the record is returned. - Subsequent calls from the client to the main server requires a `PddbDictRequest` structure - to be in the buffer, but only the "token" field is considered. The rest of the fields are disregarded; - changing the requested dictionary or other parameters has no impact on the call trajectory. + Subsequent calls from the client to the main server requires a `PddbDictRequest` structure + to be in the buffer, but only the "token" field is considered. The rest of the fields are disregarded; + changing the requested dictionary or other parameters has no impact on the call trajectory. - The PddbKeyRecords are manually packed into the memory messages with the following format: - Data slice: - - `code`: u32 containing the current transaction code - - `starting_key_index` u32 of the current index-offset that the return data starts at. Used as a sanity check. - - `len` u32 of number of records expected inside the return data structure. - - `total`: u32 with total number of keys to be transmitted (shouldn't change over the life of the session) - - `token`: [u32; 4] confirming or establishing the session token (shouldn't change over the life of the session) - First record start at data[24]: - - `size` of archived struct: u32 - - `pos` of archived struct, relative to the current start: u32 - - data[u8] of length `size` - Next record start at data[24 + size]: - - `size` of archived struct: u32 - - `pos` of archived struct, relative to the current start: u32 - - data[u8] of length `size` - If `size` and `pos` are both zero, then, there are no more valid records in the return structure. + The PddbKeyRecords are manually packed into the memory messages with the following format: + Data slice: + - `code`: u32 containing the current transaction code + - `starting_key_index` u32 of the current index-offset that the return data starts at. Used as a sanity check. + - `len` u32 of number of records expected inside the return data structure. + - `total`: u32 with total number of keys to be transmitted (shouldn't change over the life of the session) + - `token`: [u32; 4] confirming or establishing the session token (shouldn't change over the life of the session) + First record start at data[24]: + - `size` of archived struct: u32 + - `pos` of archived struct, relative to the current start: u32 + - data[u8] of length `size` + Next record start at data[24 + size]: + - `size` of archived struct: u32 + - `pos` of archived struct, relative to the current start: u32 + - data[u8] of length `size` + If `size` and `pos` are both zero, then, there are no more valid records in the return structure. - The caller shall repeatedly call `DictBulkRead` until the `code` is `Last`, at which point the token - is deleted from the server, freeing it to service a new transaction. The caller should then collate the - results into the corresponding return vector. + The caller shall repeatedly call `DictBulkRead` until the `code` is `Last`, at which point the token + is deleted from the server, freeing it to service a new transaction. The caller should then collate the + results into the corresponding return vector. - If the caller fails to drain all the data, the server eventually times out and forgets the token. - The client will then get a "Busy" return code or "NotFound", depending on if they had fully initialized the - request structure for the subsequent calls. - */ + If the caller fails to drain all the data, the server eventually times out and forgets the token. + The client will then get a "Busy" return code or "NotFound", depending on if they had fully initialized the + request structure for the subsequent calls. + */ // allocate buffer, which is shuffled back and forth to the PDDB server let alloc_target = if let Some(size_limit) = size_limit { if size_limit < 4096 { @@ -968,13 +1100,9 @@ impl Pddb { } else { DEFAULT_LIMIT }; - let mut msg_mem = xous::map_memory( - None, - None, - alloc_target, - xous::MemoryFlags::R | xous::MemoryFlags::W, - ) - .expect("Likely OOM in allocating read_dict buffer"); + let mut msg_mem = + xous::map_memory(None, None, alloc_target, xous::MemoryFlags::R | xous::MemoryFlags::W) + .expect("Likely OOM in allocating read_dict buffer"); // setup the request arguments let mut request = PddbDictRequest { @@ -1007,27 +1135,28 @@ impl Pddb { valid: xous::MemorySize::new(buf.len()), }; // do a mutable lend to the server - let (_pos, _valid) = match xous::send_message( - self.conn, - Message::MutableBorrow(msg) - ) { + let (_pos, _valid) = match xous::send_message(self.conn, Message::MutableBorrow(msg)) { Ok(xous::Result::MemoryReturned(offset, valid)) => (offset, valid), - _ => return Err(Error::new(ErrorKind::Other, "Xous internal error")) + _ => return Err(Error::new(ErrorKind::Other, "Xous internal error")), }; - // unpack the return code. The result is not a single rkyv struct, it's hand-packed binary data. Unpack it. + // unpack the return code. The result is not a single rkyv struct, it's hand-packed binary data. + // Unpack it. let mut index = 0; let mut header = BulkReadHeader::default(); // Safety: `u8` contains no undefined values - header.deref_mut().copy_from_slice( - unsafe { &msg_mem.as_slice()[index..index + size_of::()] } - ); + header + .deref_mut() + .copy_from_slice(unsafe { &msg_mem.as_slice()[index..index + size_of::()] }); index += size_of::(); match FromPrimitive::from_u32(header.code).unwrap_or(PddbBulkReadCode::InternalError) { PddbBulkReadCode::NotFound => { - return Err(Error::new(ErrorKind::NotFound, format!("Bulk Read: dictionary '{}' not found", dict))) + return Err(Error::new( + ErrorKind::NotFound, + format!("Bulk Read: dictionary '{}' not found", dict), + )); } PddbBulkReadCode::Busy => { - return Err(Error::new(ErrorKind::TimedOut, "PDDB server busy or request timed out")) + return Err(Error::new(ErrorKind::TimedOut, "PDDB server busy or request timed out")); } PddbBulkReadCode::Streaming | PddbBulkReadCode::Last => { // --- unpack the received data @@ -1035,7 +1164,11 @@ impl Pddb { request.token = header.token; check_total_keys = header.total; if check_key_index != header.starting_key_index { - log::error!("local key index did not match remote key index: {}, {}", check_key_index, header.starting_key_index); + log::error!( + "local key index did not match remote key index: {}, {}", + check_key_index, + header.starting_key_index + ); } let mut key_count = 0; log::debug!("header: {:?}", header); @@ -1045,17 +1178,28 @@ impl Pddb { break; } // Safety: `u32` contains no undefined values - let size = unsafe { u32::from_le_bytes(msg_mem.as_slice()[index..index + size_of::()].try_into().unwrap()) }; + let size = unsafe { + u32::from_le_bytes( + msg_mem.as_slice()[index..index + size_of::()].try_into().unwrap(), + ) + }; index += size_of::(); // Safety: `u32` contains no undefined values - let pos = unsafe { u32::from_le_bytes(msg_mem.as_slice()[index..index + size_of::()].try_into().unwrap()) }; + let pos = unsafe { + u32::from_le_bytes( + msg_mem.as_slice()[index..index + size_of::()].try_into().unwrap(), + ) + }; index += size_of::(); log::trace!("unpacking message at {}({})", size, pos); if size != 0 && pos != 0 { //log::info!("extract archive: {}, {}, {}, {}", index, size, pos, msg_mem.len()); //log::info!("{:x?}", &msg_mem.as_slice::()[index..index + (size as usize)]); let archived = unsafe { - archived_value::(&msg_mem.as_slice()[index..index + (size as usize)], pos as usize) + archived_value::( + &msg_mem.as_slice()[index..index + (size as usize)], + pos as usize, + ) }; //log::info!("increment index"); index += size as usize; @@ -1065,7 +1209,7 @@ impl Pddb { Err(e) => { log::error!("deserialization error: {:?}", e); panic!("deserializer error"); - }, + } }; //log::info!("pushing result"); ret.push(key); @@ -1085,13 +1229,15 @@ impl Pddb { break; } } - _ => { - return Err(Error::new(ErrorKind::Other, "Bulk read invalid return value")) - } + _ => return Err(Error::new(ErrorKind::Other, "Bulk read invalid return value")), } } if check_total_keys != check_key_index { - log::error!("Number of keys read does not match expected value: {}, {}", check_total_keys, check_key_index); + log::error!( + "Number of keys read does not match expected value: {}, {}", + check_total_keys, + check_key_index + ); } xous::unmap_memory(msg_mem).unwrap(); Ok(ret) @@ -1100,66 +1246,60 @@ impl Pddb { /// Triggers a dump of the PDDB to host disk #[cfg(not(target_os = "xous"))] pub fn dbg_dump(&self, name: &str) -> Result<()> { - let ipc = PddbDangerousDebug { - request: DebugRequest::Dump, - dump_name: xous_ipc::String::from_str(name), - }; - let buf = Buffer::into_buf(ipc) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let ipc = + PddbDangerousDebug { request: DebugRequest::Dump, dump_name: xous_ipc::String::from_str(name) }; + let buf = Buffer::into_buf(ipc).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend(self.conn, Opcode::DangerousDebug.to_u32().unwrap()) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))).map(|_| ()) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))) + .map(|_| ()) } + /// Triggers an umount/remount, forcing a read of the PDDB from disk back into the cache structures. /// It's a cheesy way to test a power cycle, without having to power cycle. #[cfg(not(target_os = "xous"))] pub fn dbg_remount(&self) -> Result<()> { - let ipc = PddbDangerousDebug { - request: DebugRequest::Remount, - dump_name: xous_ipc::String::new(), - }; - let buf = Buffer::into_buf(ipc) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let ipc = PddbDangerousDebug { request: DebugRequest::Remount, dump_name: xous_ipc::String::new() }; + let buf = Buffer::into_buf(ipc).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend(self.conn, Opcode::DangerousDebug.to_u32().unwrap()) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))).map(|_| ()) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))) + .map(|_| ()) } + /// Forces a basis pruning, for testing the pruning and prune recovery code. #[cfg(not(target_os = "xous"))] pub fn dbg_prune(&self) -> Result<()> { - let ipc = PddbDangerousDebug { - request: DebugRequest::Prune, - dump_name: xous_ipc::String::new(), - }; - let buf = Buffer::into_buf(ipc) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let ipc = PddbDangerousDebug { request: DebugRequest::Prune, dump_name: xous_ipc::String::new() }; + let buf = Buffer::into_buf(ipc).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend(self.conn, Opcode::DangerousDebug.to_u32().unwrap()) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))).map(|_| ()) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))) + .map(|_| ()) } + /// Turns on debug spew #[cfg(not(target_os = "xous"))] pub fn dbg_set_debug(&self) -> Result<()> { - let ipc = PddbDangerousDebug { - request: DebugRequest::SetDebug, - dump_name: xous_ipc::String::new(), - }; - let buf = Buffer::into_buf(ipc) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; + let ipc = PddbDangerousDebug { request: DebugRequest::SetDebug, dump_name: xous_ipc::String::new() }; + let buf = Buffer::into_buf(ipc).or(Err(Error::new(ErrorKind::Other, "Xous internal error")))?; buf.lend(self.conn, Opcode::DangerousDebug.to_u32().unwrap()) - .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))).map(|_| ()) + .or(Err(Error::new(ErrorKind::Other, "Xous internal error"))) + .map(|_| ()) } - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] pub fn basis_testing(&self, config: &[Option; 32]) { let mut op = 0; let mut valid = 0; for (index, &c) in config.iter().enumerate() { if let Some(opt) = c { valid |= 1 << index; - op |= (if opt {1} else {0}) << index; + op |= (if opt { 1 } else { 0 }) << index; } } - send_message(self.conn, - Message::new_scalar(Opcode::BasisTesting.to_usize().unwrap(), op, valid, 0, 0) - ).expect("couldn't send basis test message"); + send_message( + self.conn, + Message::new_scalar(Opcode::BasisTesting.to_usize().unwrap(), op, valid, 0, 0), + ) + .expect("couldn't send basis test message"); } } @@ -1168,13 +1308,18 @@ impl Drop for Pddb { if let Some(cb_sid) = self.cb.take() { let handle = self.cb_handle.take().unwrap(); // we guarantee this is always set when cb is set let cid = xous::connect(cb_sid).unwrap(); - send_message(cid, Message::new_blocking_scalar(CbOp::Quit.to_usize().unwrap(), 0, 0, 0, 0)).unwrap(); - unsafe{xous::disconnect(cid).ok();} + send_message(cid, Message::new_blocking_scalar(CbOp::Quit.to_usize().unwrap(), 0, 0, 0, 0)) + .unwrap(); + unsafe { + xous::disconnect(cid).ok(); + } handle.join().expect("couldn't terminate callback helper thread"); } if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { - unsafe{xous::disconnect(self.conn).unwrap();} + unsafe { + xous::disconnect(self.conn).unwrap(); + } } } } diff --git a/services/pddb/src/libstd/mod.rs b/services/pddb/src/libstd/mod.rs index b12e95bb3..a01148d76 100644 --- a/services/pddb/src/libstd/mod.rs +++ b/services/pddb/src/libstd/mod.rs @@ -1,12 +1,12 @@ mod senres; mod utils; +use senres::{Senres, SenresMut}; + use crate::backend::BasisCache; use crate::backend::PddbOs; use crate::FileHandle; -use senres::{Senres, SenresMut}; - #[repr(u8)] enum FileType { Basis = 0, @@ -48,13 +48,8 @@ pub(crate) fn stat_path( let mut backing = unsafe { senres::Message::from_mut_slice(mem.buf.as_slice_mut()) } .or(Err(crate::PddbRetcode::InternalError))?; - let reader = backing - .reader(*b"StaQ") - .ok_or(crate::PddbRetcode::InternalError)?; - let path = reader - .try_get_ref_from::() - .or(Err(crate::PddbRetcode::InternalError))? - .to_owned(); + let reader = backing.reader(*b"StaQ").ok_or(crate::PddbRetcode::InternalError)?; + let path = reader.try_get_ref_from::().or(Err(crate::PddbRetcode::InternalError))?.to_owned(); // TODO: use the internal cache inside `basis_cache` to avoid cloning let (basis, remainder) = @@ -62,18 +57,13 @@ pub(crate) fn stat_path( .or(Err(crate::PddbRetcode::InternalError))?; core::mem::drop(reader); - let mut writer = backing - .writer(*b"StaR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"StaR").ok_or(crate::PddbRetcode::InternalError)?; // The only time these two are empty is when we want to list the default basis, which // itself is not a valid path if basis.is_none() && remainder.is_none() { writer.append(4u8); // None - log::error!( - "path {} has no basis and no remainder, and therefore does not exist", - path - ); + log::error!("path {} has no basis and no remainder, and therefore does not exist", path); return Ok(()); } @@ -153,22 +143,16 @@ pub(crate) fn list_path( let mut backing = unsafe { senres::Message::from_mut_slice(mem.buf.as_slice_mut()) } .or(Err(crate::PddbRetcode::InternalError))?; - let reader = backing - .reader(*b"PthQ") - .ok_or(crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"PthQ").ok_or(crate::PddbRetcode::InternalError)?; - let path = reader - .try_get_ref_from() - .or(Err(crate::PddbRetcode::InternalError))?; + let path = reader.try_get_ref_from().or(Err(crate::PddbRetcode::InternalError))?; let (basis, dict) = utils::split_basis_and_dict(path, || basis_cache.basis_latest().map(|m| m.to_owned())) .or(Err(crate::PddbRetcode::InternalError))?; core::mem::drop(reader); - let mut writer = backing - .writer(*b"PthR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"PthR").ok_or(crate::PddbRetcode::InternalError)?; // The only time these two are empty is when we want to list the default basis. if basis.is_none() && dict.is_none() { @@ -249,13 +233,9 @@ pub(crate) fn list_basis( .or(Err(crate::PddbRetcode::InternalError))?; { - backing - .reader(*b"basQ") - .ok_or(crate::PddbRetcode::InternalError)?; + backing.reader(*b"basQ").ok_or(crate::PddbRetcode::InternalError)?; } - let mut writer = backing - .writer(*b"basR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"basR").ok_or(crate::PddbRetcode::InternalError)?; // Add the count of bases writer.append(basis_list.len() as u32); @@ -288,41 +268,22 @@ pub(crate) fn open_key( let alloc_hint: usize; let cb_sid: Option; let (requested_basis, requested_dict) = { - let reader = backing - .reader(*b"KyOQ") - .ok_or(crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"KyOQ").ok_or(crate::PddbRetcode::InternalError)?; - let path = reader - .try_get_ref_from() - .or(Err(crate::PddbRetcode::InternalError))?; - create_file = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))?; + let path = reader.try_get_ref_from().or(Err(crate::PddbRetcode::InternalError))?; + create_file = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))?; - create_path = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))?; + create_path = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))?; - create_new = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))?; + create_new = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))?; - append = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))?; + append = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))?; - truncate = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))?; + truncate = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))?; - alloc_hint = reader - .try_get_from::() - .or(Err(crate::PddbRetcode::InternalError))? as usize; + alloc_hint = reader.try_get_from::().or(Err(crate::PddbRetcode::InternalError))? as usize; - cb_sid = if let Some(sid) = reader - .try_get_from() - .or(Err(crate::PddbRetcode::InternalError))? - { + cb_sid = if let Some(sid) = reader.try_get_from().or(Err(crate::PddbRetcode::InternalError))? { Some(xous::SID::from_array(sid)) } else { None @@ -339,16 +300,13 @@ pub(crate) fn open_key( // Split the final ":key" off from the dict. If there are no ":" then this will fail // with the "no key was specified" error. - let (requested_dict, requested_key) = requested_dict - .rsplit_once(std::path::MAIN_SEPARATOR) - .ok_or_else(|| { + let (requested_dict, requested_key) = + requested_dict.rsplit_once(std::path::MAIN_SEPARATOR).ok_or_else(|| { log::error!("no key was specified"); crate::PddbRetcode::AccessDenied })?; - let mut writer = backing - .writer(*b"KyOR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"KyOR").ok_or(crate::PddbRetcode::InternalError)?; // Behavior for opening files: // 1. If `bname` is defined, then we only loop once. @@ -359,24 +317,19 @@ pub(crate) fn open_key( let bname = Some(basis.as_str()); // If the dictionary wasn't found, either add it or move on - if basis_cache - .dict_attributes(pddb_os, requested_dict, bname) - .is_err() - { + if basis_cache.dict_attributes(pddb_os, requested_dict, bname).is_err() { if !create_path { continue; } - basis_cache - .dict_add(pddb_os, requested_dict, bname) - .map_err(|e| { - log::error!( - "unable to add dict {} to basis {}: {:?}", - requested_dict, - bname.unwrap_or("internal_error"), - e - ); - crate::PddbRetcode::InternalError - })?; + basis_cache.dict_add(pddb_os, requested_dict, bname).map_err(|e| { + log::error!( + "unable to add dict {} to basis {}: {:?}", + requested_dict, + bname.unwrap_or("internal_error"), + e + ); + crate::PddbRetcode::InternalError + })?; } let mut len = 0; @@ -399,11 +352,7 @@ pub(crate) fn open_key( requested_key, &[], None, - if alloc_hint > 0 { - Some(alloc_hint) - } else { - None - }, + if alloc_hint > 0 { Some(alloc_hint) } else { None }, bname, true, ) @@ -420,7 +369,9 @@ pub(crate) fn open_key( } else if create_new { log::error!( "user requested to create {}{}{} with `create_new` set, but that file already exists", - requested_dict, std::path::MAIN_SEPARATOR, requested_key + requested_dict, + std::path::MAIN_SEPARATOR, + requested_key ); return Err(crate::PddbRetcode::DiskFull); } else if truncate { @@ -432,11 +383,7 @@ pub(crate) fn open_key( requested_key, &[], None, - if alloc_hint > 0 { - Some(alloc_hint) - } else { - None - }, + if alloc_hint > 0 { Some(alloc_hint) } else { None }, bname, true, ) @@ -452,8 +399,7 @@ pub(crate) fn open_key( } // The basis exists for sure. - let conn = - cb_sid.map(|cb_sid| xous::connect(cb_sid).expect("couldn't connect for callback")); + let conn = cb_sid.map(|cb_sid| xous::connect(cb_sid).expect("couldn't connect for callback")); let file_handle = crate::FileHandle { dict: String::from(requested_dict), key: String::from(requested_key), @@ -462,11 +408,7 @@ pub(crate) fn open_key( length: len, deleted: false, conn, - alloc_hint: if alloc_hint > 0 { - Some(alloc_hint) - } else { - None - }, + alloc_hint: if alloc_hint > 0 { Some(alloc_hint) } else { None }, }; // Look for the first `None` record in the file descriptor list. If there @@ -494,11 +436,7 @@ pub(crate) fn open_key( return Ok(()); } - log::error!( - "unable to find key {} in dict {}", - requested_key, - requested_dict - ); + log::error!("unable to find key {} in dict {}", requested_key, requested_dict); // If we fall off the end, then the dict/key do not exist Err(crate::PddbRetcode::BasisLost) @@ -557,45 +495,26 @@ pub(crate) fn delete_key( let backing = unsafe { senres::Message::from_mut_slice(mem.buf.as_slice_mut()) } .or(Err(crate::PddbRetcode::InternalError))?; - let reader = backing - .reader(*b"RmKQ") - .ok_or(crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"RmKQ").ok_or(crate::PddbRetcode::InternalError)?; - let full_path = reader - .try_get_ref_from::() - .or(Err(crate::PddbRetcode::BasisLost))?; + let full_path = reader.try_get_ref_from::().or(Err(crate::PddbRetcode::BasisLost))?; - let (basis, path) = utils::split_basis_and_dict(full_path, || { - basis_cache.basis_latest().map(|m| m.to_owned()) - }) - .or(Err(crate::PddbRetcode::AccessDenied))?; + let (basis, path) = + utils::split_basis_and_dict(full_path, || basis_cache.basis_latest().map(|m| m.to_owned())) + .or(Err(crate::PddbRetcode::AccessDenied))?; let path = path.ok_or(crate::PddbRetcode::AccessDenied)?; let bname = basis.as_deref(); - let (dict, key) = path - .rsplit_once(std::path::MAIN_SEPARATOR) - .ok_or(crate::PddbRetcode::AccessDenied)?; + let (dict, key) = path.rsplit_once(std::path::MAIN_SEPARATOR).ok_or(crate::PddbRetcode::AccessDenied)?; // Perform the actual removal - basis_cache - .key_remove(pddb_os, dict, key, bname, false) - .or_else(|e| { - log::error!( - "unable to delete key {} in dict {} (basis {:?}): {:?}", - key, - dict, - bname, - e - ); - Err(crate::PddbRetcode::UnexpectedEof) - })?; + basis_cache.key_remove(pddb_os, dict, key, bname, false).or_else(|e| { + log::error!("unable to delete key {} in dict {} (basis {:?}): {:?}", key, dict, bname, e); + Err(crate::PddbRetcode::UnexpectedEof) + })?; // Mark the entry as deleted in all remaining file handles in the entire system for fds in all_fds.values_mut() { - for fd in fds - .iter_mut() - .filter(|f| f.is_some()) - .map(|f| f.as_mut().unwrap()) - { + for fd in fds.iter_mut().filter(|f| f.is_some()).map(|f| f.as_mut().unwrap()) { if fd.basis == basis && fd.key == key && fd.dict == dict { fd.deleted = true; } @@ -616,12 +535,7 @@ pub(crate) fn write_key( let mut retcode = crate::PddbRetcode::InternalError; for basis in basis_cache.access_list().iter() { - log::debug!( - "write (spec: {:?}){:?} {}", - file.basis, - file.basis.as_ref().unwrap_or(basis), - file.key - ); + log::debug!("write (spec: {:?}){:?} {}", file.basis, file.basis.as_ref().unwrap_or(basis), file.key); let length_to_write = mem.valid.map(|v| v.get()).unwrap_or_default(); // Safety: all `u8` values are valid if basis_cache @@ -633,10 +547,11 @@ pub(crate) fn write_key( // &mut pbuf.data[..pbuf.len as usize], Some(file.offset as usize), file.alloc_hint, - // this is a bit inefficient because if a specific basis is specified *and* the key does not exist, - // it'll retry the same basis for a number of times equal to the number of bases open. - // However, usually, there's only 1-2 bases open, and usually, if you specify a basis, - // the key will be a hit, so, we let it stand. + // this is a bit inefficient because if a specific basis is specified *and* the key does not + // exist, it'll retry the same basis for a number of times equal to the + // number of bases open. However, usually, there's only 1-2 bases open, and + // usually, if you specify a basis, the key will be a hit, so, we let it + // stand. Some(file.basis.as_ref().unwrap_or(basis)), false, ) @@ -665,11 +580,7 @@ pub(crate) fn seek_key( ) -> Result { let file = get_fd(fds, fd)?; - fn seek_from_point( - this: &mut FileHandle, - point: u64, - by: i64, - ) -> Result { + fn seek_from_point(this: &mut FileHandle, point: u64, by: i64) -> Result { let by64 = by as u64; // Note that it's possible to seek past the end of a key, and in this case // the `offset` will be greater than the `len`. This is fine, and `len` will @@ -712,12 +623,7 @@ pub(crate) fn read_key( let mut retcode = crate::PddbRetcode::Ok; for basis in basis_cache.access_list().iter() { - log::debug!( - "read (spec: {:?}){:?} {}", - file.basis, - file.basis.as_ref().unwrap_or(basis), - file.key - ); + log::debug!("read (spec: {:?}){:?} {}", file.basis, file.basis.as_ref().unwrap_or(basis), file.key); // Safety: all u8 values are valid if let Ok(readlen) = basis_cache .key_read( @@ -726,18 +632,17 @@ pub(crate) fn read_key( &file.key, unsafe { &mut mem.buf.as_slice_mut()[0..mem.valid.map(|v| v.get()).unwrap_or_default()] }, Some(file.offset as usize), - // this is a bit inefficient because if a specific basis is specified *and* the key does not exist, - // it'll retry the same basis for a number of times equal to the number of bases open. - // However, usually, there's only 1-2 bases open, and usually, if you specify a basis, - // the key will be a hit, so, we let it stand. + // this is a bit inefficient because if a specific basis is specified *and* the key does not + // exist, it'll retry the same basis for a number of times equal to the + // number of bases open. However, usually, there's only 1-2 bases open, and + // usually, if you specify a basis, the key will be a hit, so, we let it + // stand. Some(file.basis.as_ref().unwrap_or(basis)), ) .map_err(|e| { match e.kind() { std::io::ErrorKind::NotFound => retcode = crate::PddbRetcode::BasisLost, - std::io::ErrorKind::UnexpectedEof => { - retcode = crate::PddbRetcode::UnexpectedEof - } + std::io::ErrorKind::UnexpectedEof => retcode = crate::PddbRetcode::UnexpectedEof, std::io::ErrorKind::OutOfMemory => retcode = crate::PddbRetcode::DiskFull, _ => retcode = crate::PddbRetcode::InternalError, }; @@ -749,12 +654,7 @@ pub(crate) fn read_key( return Ok(()); } } - log::error!( - "couldn't find file {} in dict {} in basis {:?}", - file.key, - file.dict, - file.basis - ); + log::error!("couldn't find file {} in dict {} in basis {:?}", file.key, file.dict, file.basis); Err(retcode) } @@ -768,17 +668,11 @@ pub(crate) fn list_dict( .or(Err(crate::PddbRetcode::InternalError))?; let bname; { - let reader = backing - .reader(*b"LiDQ") - .ok_or(crate::PddbRetcode::InternalError)?; - bname = reader - .try_get_from::>() - .map_err(|_| crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"LiDQ").ok_or(crate::PddbRetcode::InternalError)?; + bname = reader.try_get_from::>().map_err(|_| crate::PddbRetcode::InternalError)?; } - let mut writer = backing - .writer(*b"LiDR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"LiDR").ok_or(crate::PddbRetcode::InternalError)?; let dict_list = basis_cache.dict_list(pddb_os, bname.as_deref()); writer.append(dict_list.len() as u32); @@ -800,33 +694,17 @@ pub(crate) fn list_key( let bname; let key; { - let reader = backing - .reader(*b"LiKQ") - .ok_or(crate::PddbRetcode::InternalError)?; - bname = reader - .try_get_from::>() - .map_err(|_| crate::PddbRetcode::InternalError)?; - key = reader - .try_get_ref_from::() - .map_err(|_| crate::PddbRetcode::InternalError)? - .to_owned(); + let reader = backing.reader(*b"LiKQ").ok_or(crate::PddbRetcode::InternalError)?; + bname = reader.try_get_from::>().map_err(|_| crate::PddbRetcode::InternalError)?; + key = reader.try_get_ref_from::().map_err(|_| crate::PddbRetcode::InternalError)?.to_owned(); } - let mut writer = backing - .writer(*b"LiKR") - .ok_or(crate::PddbRetcode::InternalError)?; + let mut writer = backing.writer(*b"LiKR").ok_or(crate::PddbRetcode::InternalError)?; - let (key_list, _, _) = basis_cache - .key_list(pddb_os, &key, bname.as_deref()) - .or_else(|e| { - log::error!( - "unable to get key list of dict {} in basis {:?}: {:?}", - key, - bname, - e - ); - Err(crate::PddbRetcode::InternalError) - })?; + let (key_list, _, _) = basis_cache.key_list(pddb_os, &key, bname.as_deref()).or_else(|e| { + log::error!("unable to get key list of dict {} in basis {:?}: {:?}", key, bname, e); + Err(crate::PddbRetcode::InternalError) + })?; writer.append(key_list.len() as u32); for dict in key_list.iter() { @@ -844,13 +722,9 @@ pub(crate) fn delete_dict( // Safety: the memory message must be aligned, and we test for validity here let backing = unsafe { senres::Message::from_mut_slice(mem.buf.as_slice_mut()) } .or(Err(crate::PddbRetcode::InternalError))?; - let reader = backing - .reader(*b"RmDQ") - .ok_or(crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"RmDQ").ok_or(crate::PddbRetcode::InternalError)?; - let path = reader - .try_get_ref_from::() - .or(Err(crate::PddbRetcode::InternalError))?; + let path = reader.try_get_ref_from::().or(Err(crate::PddbRetcode::InternalError))?; let (bname, dict) = utils::split_basis_and_dict(path, || basis_cache.basis_latest().map(|m| m.to_owned())) .or(Err(crate::PddbRetcode::InternalError))?; @@ -870,10 +744,7 @@ pub(crate) fn delete_dict( } } - if basis_cache - .dict_remove(pddb_os, &dict, bname.as_deref(), false) - .is_err() - { + if basis_cache.dict_remove(pddb_os, &dict, bname.as_deref(), false).is_err() { log::error!("error removing dict {} in basis {:?}", dict, bname); return Err(crate::PddbRetcode::InternalError); } @@ -889,29 +760,23 @@ pub(crate) fn create_dict( // Safety: the memory message must be aligned, and we test for validity here let backing = unsafe { senres::Message::from_mut_slice(mem.buf.as_slice_mut()) } .or(Err(crate::PddbRetcode::InternalError))?; - let reader = backing - .reader(*b"NuDQ") - .ok_or(crate::PddbRetcode::InternalError)?; + let reader = backing.reader(*b"NuDQ").ok_or(crate::PddbRetcode::InternalError)?; - let path = reader - .try_get_ref_from::() - .or(Err(crate::PddbRetcode::InternalError))?; + let path = reader.try_get_ref_from::().or(Err(crate::PddbRetcode::InternalError))?; let (basis, dict) = utils::split_basis_and_dict(path, || basis_cache.basis_latest().map(|m| m.to_owned())) .or(Err(crate::PddbRetcode::InternalError))?; let dict = dict.ok_or(crate::PddbRetcode::InternalError)?; - basis_cache - .dict_add(pddb_os, &dict, basis.as_deref()) - .map_err(|e| { - log::error!( - "unable to add dict {} to basis {}: {:?}", - dict, - basis.as_deref().unwrap_or("internal_error"), - e - ); - crate::PddbRetcode::InternalError - })?; + basis_cache.dict_add(pddb_os, &dict, basis.as_deref()).map_err(|e| { + log::error!( + "unable to add dict {} to basis {}: {:?}", + dict, + basis.as_deref().unwrap_or("internal_error"), + e + ); + crate::PddbRetcode::InternalError + })?; Ok(()) } diff --git a/services/pddb/src/libstd/senres.rs b/services/pddb/src/libstd/senres.rs index 8d4c5eff9..e09ce1771 100644 --- a/services/pddb/src/libstd/senres.rs +++ b/services/pddb/src/libstd/senres.rs @@ -69,26 +69,18 @@ impl<'a> Message<'a> { data: unsafe { core::slice::from_raw_parts_mut(data as *mut u8, len) }, }) } + // Need to figure out how to make the non-mutable version work, since `data` // must be a mutable vec. pub fn from_slice(data: &'a [u8]) -> Result { - Ok(Message { - message_id: 0, - auto_return: false, - data, - }) + Ok(Message { message_id: 0, auto_return: false, data }) } + pub fn from_mut_slice(data: &'a mut [u8]) -> Result, ()> { - Ok(MutableMessage { - message_id: 0, - mutable: true, - auto_return: false, - data, - }) - } - pub fn auto_return(&mut self, enable: bool) { - self.auto_return = enable; + Ok(MutableMessage { message_id: 0, mutable: true, auto_return: false, data }) } + + pub fn auto_return(&mut self, enable: bool) { self.auto_return = enable; } } #[cfg(target_os = "xous")] @@ -123,52 +115,35 @@ impl<'a> Drop for Message<'a> { fn drop(&mut self) { if self.auto_return { #[cfg(target_os = "xous")] - return_memory_message( - self.message_id, - self.data.as_ptr() as usize, - self.data.len(), - ); + return_memory_message(self.message_id, self.data.as_ptr() as usize, self.data.len()); } } } impl<'a> SenresMut for MutableMessage<'a> { - fn as_mut_slice(&mut self) -> &mut [u8] { - self.data - } - fn as_mut_ptr(&mut self) -> *mut u8 { - self as *mut _ as *mut u8 - } + fn as_mut_slice(&mut self) -> &mut [u8] { self.data } + + fn as_mut_ptr(&mut self) -> *mut u8 { self as *mut _ as *mut u8 } } impl<'a> Senres for MutableMessage<'a> { - fn as_slice(&self) -> &[u8] { - self.data - } - fn len(&self) -> usize { - self.data.len() - } - fn as_ptr(&self) -> *const u8 { - self as *const _ as *const u8 - } - fn can_create_writer(&self) -> bool { - self.mutable - } + fn as_slice(&self) -> &[u8] { self.data } + + fn len(&self) -> usize { self.data.len() } + + fn as_ptr(&self) -> *const u8 { self as *const _ as *const u8 } + + fn can_create_writer(&self) -> bool { self.mutable } } impl<'a> Senres for Message<'a> { - fn as_slice(&self) -> &[u8] { - self.data - } - fn len(&self) -> usize { - self.data.len() - } - fn as_ptr(&self) -> *const u8 { - self as *const _ as *const u8 - } - fn can_create_writer(&self) -> bool { - false - } + fn as_slice(&self) -> &[u8] { self.data } + + fn len(&self) -> usize { self.data.len() } + + fn as_ptr(&self) -> *const u8 { self as *const _ as *const u8 } + + fn can_create_writer(&self) -> bool { false } } pub trait Senres { @@ -176,18 +151,13 @@ pub trait Senres { fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; - fn can_create_writer(&self) -> bool { - true - } + fn can_create_writer(&self) -> bool { true } fn reader(&self, fourcc: [u8; 4]) -> Option> where Self: core::marker::Sized, { - let reader = Reader { - backing: self, - offset: core::cell::Cell::new(0), - }; + let reader = Reader { backing: self, offset: core::cell::Cell::new(0) }; if SENRES_V1_MAGIC != reader.try_get_from().ok()? { return None; } @@ -199,9 +169,7 @@ pub trait Senres { } #[cfg(not(target_os = "xous"))] - fn lend(&self, _connection: u32, _opcode: usize) -> Result<(), ()> { - Ok(()) - } + fn lend(&self, _connection: u32, _opcode: usize) -> Result<(), ()> { Ok(()) } #[cfg(target_os = "xous")] fn lend(&self, connection: u32, opcode: usize) -> Result<(), ()> { @@ -246,18 +214,13 @@ pub trait SenresMut: Senres { if !self.can_create_writer() { return None; } - let mut writer = Writer { - backing: self, - offset: 0, - }; + let mut writer = Writer { backing: self, offset: 0 }; writer.append(SENRES_V1_MAGIC); writer.append(fourcc); Some(writer) } #[cfg(not(target_os = "xous"))] - fn lend_mut(&mut self, _connection: u32, _opcode: usize) -> Result<(), ()> { - Ok(()) - } + fn lend_mut(&mut self, _connection: u32, _opcode: usize) -> Result<(), ()> { Ok(()) } #[cfg(target_os = "xous")] fn lend_mut(&mut self, connection: u32, opcode: usize) -> Result<(), ()> { let mut a0 = Syscall::SendMessage as usize; @@ -294,7 +257,6 @@ pub trait SenresMut: Senres { } } - pub struct Writer<'a, Backing: SenresMut> { backing: &'a mut Backing, offset: usize, @@ -341,30 +303,21 @@ impl Stack { } impl SenresMut for Stack { - fn as_mut_slice(&mut self) -> &mut [u8] { - self.data.as_mut_slice() - } - fn as_mut_ptr(&mut self) -> *mut u8 { - &mut self.data as *mut _ as *mut u8 - } + fn as_mut_slice(&mut self) -> &mut [u8] { self.data.as_mut_slice() } + + fn as_mut_ptr(&mut self) -> *mut u8 { &mut self.data as *mut _ as *mut u8 } } impl Senres for Stack { - fn as_slice(&self) -> &[u8] { - self.data.as_slice() - } - fn len(&self) -> usize { - N - } - fn as_ptr(&self) -> *const u8 { - &self.data as *const _ as *const u8 - } + fn as_slice(&self) -> &[u8] { self.data.as_slice() } + + fn len(&self) -> usize { N } + + fn as_ptr(&self) -> *const u8 { &self.data as *const _ as *const u8 } } impl<'a, Backing: SenresMut> Writer<'a, Backing> { - pub fn append>(&mut self, other: T) { - other.append_to(self); - } + pub fn append>(&mut self, other: T) { other.append_to(self); } pub fn delayed_append>(&mut self) -> DelayedWriter { let delayed_writer = DelayedWriter { @@ -398,9 +351,7 @@ impl<'a, Backing: SenresMut> Writer<'a, Backing> { } impl<'a, Backing: Senres> Reader<'a, Backing> { - pub fn try_get_from>(&self) -> Result { - T::try_get_from(self) - } + pub fn try_get_from>(&self) -> Result { T::try_get_from(self) } pub fn try_get_ref_from + ?Sized>(&'a self) -> Result<&T, ()> { T::try_get_ref_from(self) @@ -418,10 +369,8 @@ macro_rules! primitive_impl { impl SenSer for $SelfT { fn append_to(&self, senres: &mut Writer) { senres.align_to(core::mem::align_of::()); - for (src, dest) in self - .to_le_bytes() - .iter() - .zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) + for (src, dest) in + self.to_le_bytes().iter().zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) { *dest = *src; senres.offset += 1; @@ -438,9 +387,7 @@ macro_rules! primitive_impl { return Err(()); } let val = Self::from_le_bytes( - senres.backing.as_slice()[offset..offset + my_size] - .try_into() - .unwrap(), + senres.backing.as_slice()[offset..offset + my_size].try_into().unwrap(), ); senres.offset.set(offset + my_size); Ok(val) @@ -562,10 +509,8 @@ impl, Backing: Senres, const N: usize> RecDes for [T impl SenSer for str { fn append_to(&self, senres: &mut Writer) { senres.append(self.len() as u32); - for (src, dest) in self - .as_bytes() - .iter() - .zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) + for (src, dest) in + self.as_bytes().iter().zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) { *dest = *src; senres.offset += 1; @@ -576,10 +521,8 @@ impl SenSer for str { impl SenSer for &str { fn append_to(&self, senres: &mut Writer) { senres.append(self.len() as u32); - for (src, dest) in self - .as_bytes() - .iter() - .zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) + for (src, dest) in + self.as_bytes().iter().zip(senres.backing.as_mut_slice()[senres.offset..].iter_mut()) { *dest = *src; senres.offset += 1; @@ -594,12 +537,10 @@ impl RecDes for String { if offset + len > senres.backing.as_slice().len() { return Err(()); } - core::str::from_utf8(&senres.backing.as_slice()[offset..offset + len]) - .or(Err(())) - .map(|e| { - senres.offset.set(offset + len); - e.to_owned() - }) + core::str::from_utf8(&senres.backing.as_slice()[offset..offset + len]).or(Err(())).map(|e| { + senres.offset.set(offset + len); + e.to_owned() + }) } } @@ -610,12 +551,10 @@ impl<'a, Backing: Senres> RecDesRef<'a, Backing> for str { if offset + len > senres.backing.as_slice().len() { return Err(()); } - core::str::from_utf8(&senres.backing.as_slice()[offset..offset + len]) - .or(Err(())) - .map(|e| { - senres.offset.set(offset + len); - e - }) + core::str::from_utf8(&senres.backing.as_slice()[offset..offset + len]).or(Err(())).map(|e| { + senres.offset.set(offset + len); + e + }) } } @@ -627,10 +566,7 @@ impl<'a, Backing: Senres, T: RecDes> RecDesRef<'a, Backing> for [T] { return Err(()); } let ret = unsafe { - core::slice::from_raw_parts( - senres.backing.as_slice().as_ptr().add(offset) as *const T, - len, - ) + core::slice::from_raw_parts(senres.backing.as_slice().as_ptr().add(offset) as *const T, len) }; senres.offset.set(offset + len * core::mem::size_of::()); Ok(ret) @@ -638,9 +574,7 @@ impl<'a, Backing: Senres, T: RecDes> RecDesRef<'a, Backing> for [T] { } impl Default for Stack { - fn default() -> Self { - Self::new() - } + fn default() -> Self { Self::new() } } fn do_stuff(_r: &Stack) { @@ -683,9 +617,7 @@ fn smoke_test() { println!("u32 val: {}", val); let val: u64 = reader.try_get_from().expect("couldn't get the u64 value"); println!("u64 val: {:x}", val); - let val: &str = reader - .try_get_ref_from() - .expect("couldn't get string value"); + let val: &str = reader.try_get_ref_from().expect("couldn't get string value"); println!("String val: {}", val); let val: String = reader.try_get_from().expect("couldn't get string2 value"); println!("String2 val: {}", val); @@ -694,9 +626,7 @@ fn smoke_test() { let val: Option = reader.try_get_from().expect("couldn't get Option"); println!("Option val: {:?}", val); - let val: u8 = reader - .try_get_from() - .expect("couldn't get u8 weird padding"); + let val: u8 = reader.try_get_from().expect("couldn't get u8 weird padding"); println!("u8 val: {}", val); let val: &[i32] = reader.try_get_ref_from().expect("couldn't get &[i32]"); diff --git a/services/pddb/src/libstd/utils.rs b/services/pddb/src/libstd/utils.rs index cece3aa78..833e00472 100644 --- a/services/pddb/src/libstd/utils.rs +++ b/services/pddb/src/libstd/utils.rs @@ -19,15 +19,14 @@ pub fn get_path<'a>(s: &'a str, prefix: &'a str) -> Option<&'a str> { let without_prefix = s.strip_prefix(prefix)?.strip_prefix(':')?; let mut path_split = without_prefix.split(':'); let parent = path_split.next(); - if path_split.next().is_some() { - None - } else { - parent - } + if path_split.next().is_some() { None } else { parent } } /// Split a path into its constituant Basis and Dict, if the path is legal. -pub fn split_basis_and_dict Option>(src: &str, mut default: F) -> io::Result<(Option, Option)> { +pub fn split_basis_and_dict Option>( + src: &str, + mut default: F, +) -> io::Result<(Option, Option)> { let mut basis = None; let dict; if let Some(src) = src.strip_prefix(MAIN_SEP) { @@ -70,9 +69,7 @@ pub fn split_basis_and_dict Option>(src: &str, mut default } #[cfg(test)] -fn default_path() -> Option { - Some("{DEFAULT}".to_owned()) -} +fn default_path() -> Option { Some("{DEFAULT}".to_owned()) } #[test] fn empty_string() { @@ -91,26 +88,23 @@ fn dict_with_colon() { #[test] fn dict_with_two_colons() { - assert_eq!(split_basis_and_dict("one:two:three", default_path).unwrap(), (None, Some("one:two:three".to_owned()))); + assert_eq!( + split_basis_and_dict("one:two:three", default_path).unwrap(), + (None, Some("one:two:three".to_owned())) + ); } #[test] #[should_panic] -fn dict_with_trailing_colon() { - split_basis_and_dict("one:", default_path).unwrap(); -} +fn dict_with_trailing_colon() { split_basis_and_dict("one:", default_path).unwrap(); } #[test] #[should_panic] -fn two_dicts_with_trailing_colon() { - split_basis_and_dict("one:two:", default_path).unwrap(); -} +fn two_dicts_with_trailing_colon() { split_basis_and_dict("one:two:", default_path).unwrap(); } #[test] #[should_panic] -fn basis_with_dict_with_trailing_colon() { - split_basis_and_dict(":one:two:", default_path).unwrap(); -} +fn basis_with_dict_with_trailing_colon() { split_basis_and_dict(":one:two:", default_path).unwrap(); } #[test] #[should_panic] @@ -125,12 +119,18 @@ fn basis_missing_colon() { #[test] fn basis_with_one_dict() { - assert_eq!(split_basis_and_dict(":one:two", default_path).unwrap(), (Some("one".to_owned()), Some("two".to_owned()))); + assert_eq!( + split_basis_and_dict(":one:two", default_path).unwrap(), + (Some("one".to_owned()), Some("two".to_owned())) + ); } #[test] fn basis_with_two_dicts() { - assert_eq!(split_basis_and_dict(":one:two:three", default_path).unwrap(), (Some("one".to_owned()), Some("two:three".to_owned()))); + assert_eq!( + split_basis_and_dict(":one:two:three", default_path).unwrap(), + (Some("one".to_owned()), Some("two:three".to_owned())) + ); } #[test] fn double_colon() { @@ -146,29 +146,29 @@ fn single_colon() { #[test] fn double_colon_two_keys() { let default = default_path(); - assert_eq!(split_basis_and_dict("::foo:bar", default_path).unwrap(), (default, Some("foo:bar".to_owned()))); + assert_eq!( + split_basis_and_dict("::foo:bar", default_path).unwrap(), + (default, Some("foo:bar".to_owned())) + ); } #[test] fn double_colon_three_keys() { let default = default_path(); - assert_eq!(split_basis_and_dict("::foo:bar:baz", default_path).unwrap(), (default, Some("foo:bar:baz".to_owned()))); + assert_eq!( + split_basis_and_dict("::foo:bar:baz", default_path).unwrap(), + (default, Some("foo:bar:baz".to_owned())) + ); } #[test] #[should_panic] -fn double_colon_three_keys_trailing_colon() { - split_basis_and_dict("::foo:bar:baz:", default_path).unwrap(); -} +fn double_colon_three_keys_trailing_colon() { split_basis_and_dict("::foo:bar:baz:", default_path).unwrap(); } #[test] #[should_panic] -fn dict_with_two_keys_two_trailing_colons() { - split_basis_and_dict("foo:bar::", default_path).unwrap(); -} +fn dict_with_two_keys_two_trailing_colons() { split_basis_and_dict("foo:bar::", default_path).unwrap(); } #[test] #[should_panic] -fn dict_with_two_keys_three_trailing_colons() { - split_basis_and_dict("foo:bar:::", default_path).unwrap(); -} \ No newline at end of file +fn dict_with_two_keys_three_trailing_colons() { split_basis_and_dict("foo:bar:::", default_path).unwrap(); } diff --git a/services/pddb/src/main.rs b/services/pddb/src/main.rs index a4dcf20d0..44e0f0f5a 100644 --- a/services/pddb/src/main.rs +++ b/services/pddb/src/main.rs @@ -1,30 +1,31 @@ #![cfg_attr(target_os = "none", no_std)] #![cfg_attr(target_os = "none", no_main)] +extern crate bitfield; /// # PDDB - Plausibly Deniable DataBase /// /// ## Glossary: /// * Basis - a unionizeable dictionary that can be unlocked with a password-protected key. -/// * Dictionary - similar to a "volume" on a typical filesystem. Contains a group of k/v pairs -/// that share attributes such as access permissions +/// * Dictionary - similar to a "volume" on a typical filesystem. Contains a group of k/v pairs that share +/// attributes such as access permissions /// * Key - a unique string that identifies a piece of data in the PDDB -/// * Path - Rust's notion of how to locate a file, except we interpret it as a PDDB key, where -/// the "root" directory is the dictionary, and the remainder is the "key" +/// * Path - Rust's notion of how to locate a file, except we interpret it as a PDDB key, where the "root" +/// directory is the dictionary, and the remainder is the "key" /// * Value - the value associated with a key, isomorphic ot a "file" in Rust /// * Open Basis - A Basis that is decryptable and known to the system. /// * Closed Basis - A Basis that is not decryptable. It's unknown to the system and potentially treated /// identically to free disk space, e.g., it could be partially overwritten. -/// * FSCB - Free Space Commit Buffer. A few pages allocated to tracking a subset of free space, -/// meant to accelerate PDDB operations. Creates a side-channel that can reveal that some activity -/// has happened, but without disclosing what and why. Contains FastSpace and SpaceUpdate records. -/// Frequently updated, so the buffer is slightly oversized, and which sector is "hot" is randomized -/// for wear-levelling. -/// * MBBB - Make Before Break Buffer. A set of randomly allocated pages that are a shadow copy -/// of a page table page. If any data exists, its contents override those of a corrupted page table. -/// * FastSpace - a collection of random pages that are known to be empty. The number of pages in FastSpace -/// is reduced from the absolute amount of free space available by at least a factor of FSCB_FILL_COEFFICIENT. +/// * FSCB - Free Space Commit Buffer. A few pages allocated to tracking a subset of free space, meant to +/// accelerate PDDB operations. Creates a side-channel that can reveal that some activity has happened, but +/// without disclosing what and why. Contains FastSpace and SpaceUpdate records. Frequently updated, so the +/// buffer is slightly oversized, and which sector is "hot" is randomized for wear-levelling. +/// * MBBB - Make Before Break Buffer. A set of randomly allocated pages that are a shadow copy of a page +/// table page. If any data exists, its contents override those of a corrupted page table. +/// * FastSpace - a collection of random pages that are known to be empty. The number of pages in FastSpace is +/// reduced from the absolute amount of free space available by at least a factor of FSCB_FILL_COEFFICIENT. /// * SpaceUpdate - encrypted patches to the FastSpace table. The FastSpace table is "heavyweight", and would -/// be too expensive to update on every page allocation, so SpaceUpdate is used to patch the FastSpace table. +/// be too expensive to update on every page allocation, so SpaceUpdate is used to patch the FastSpace +/// table. /// /// A `Path` like the following is deconstructed as follows by the PDDB: /// @@ -108,25 +109,24 @@ /// ## Auditor Notices: /// There's probably a lot of things the PDDB does wrong. Here's a list of some things /// to worry about: -/// - The device RootKeys shares one salt across all of its keys, instead of a per-key salt. -/// Currently there are only two keys in the RootKey store sharing the one salt. The concern -/// is that a migration to an ASIC base design would make eFuse bits scarce, so, the initial -/// draft of the RootKeys shares one salt to maximize key capacity. The per-key salt is slightly -/// modified by adding the key index to the salt. This isn't meant to be a robust mitigation: -/// it just prevents a naive rainbow attack from re-using its table. +/// - The device RootKeys shares one salt across all of its keys, instead of a per-key salt. Currently there +/// are only two keys in the RootKey store sharing the one salt. The concern is that a migration to an ASIC +/// base design would make eFuse bits scarce, so, the initial draft of the RootKeys shares one salt to +/// maximize key capacity. The per-key salt is slightly modified by adding the key index to the salt. This +/// isn't meant to be a robust mitigation: it just prevents a naive rainbow attack from re-using its table. /// - The bcrypt() implementation is vendored in from a Rust bcrypt crate. It hasn't been audited. -/// - The COST of 7 for bcrypt is relatively low by today's standards (should be 10). However, -/// we can't raise the cost to 10 because our CPU is slower than most modern x86 devices. There is -/// an open issue to try to improve this with hardware acceleration. The mitigation is to use -/// a longer passphrase instead of a 12 or 14-character password. -/// - The RootKey is used to decrypt a locally stored System Basis key. The key is encrypted using -/// straight AES-256 with no authentication. -/// - Secret basis keys are not stored anywhere on the device. They are all derived from a password -/// using bcrypt. The salt for the password is drawn from a "salt pool", whose index is derived from -/// a weak hash of the password itself. This means there is a chance that a salt gets re-used. However, -/// we do not store per-password salts because the existence of the salt would betray the existence of -/// a password. -/// - Disk blocks are encrypted & authenticated on a page-by-page basis using AES-GCM-SIV, with a 256-bit key. +/// - The COST of 7 for bcrypt is relatively low by today's standards (should be 10). However, we can't raise +/// the cost to 10 because our CPU is slower than most modern x86 devices. There is an open issue to try to +/// improve this with hardware acceleration. The mitigation is to use a longer passphrase instead of a 12 +/// or 14-character password. +/// - The RootKey is used to decrypt a locally stored System Basis key. The key is encrypted using straight +/// AES-256 with no authentication. +/// - Secret basis keys are not stored anywhere on the device. They are all derived from a password using +/// bcrypt. The salt for the password is drawn from a "salt pool", whose index is derived from a weak hash +/// of the password itself. This means there is a chance that a salt gets re-used. However, we do not store +/// per-password salts because the existence of the salt would betray the existence of a password. +/// - Disk blocks are encrypted & authenticated on a page-by-page basis using AES-GCM-SIV, with a 256-bit +/// key. /// - Page table entries and space update entries are salted & hinted using a 64-bit nonce + weak checksum. /// There is a fairly high chance of a checksum collision, thus PTE decrypts are regarded as advisory and /// not final; the PTE is not accepted as authentic until the AES-GCM-SIV behind it checks out. Free space @@ -154,10 +154,10 @@ /// /// Furthermore, it is possible for a Basis to be closed on a "File" that is currently open. /// In this case, two things happen: -/// - If a notification callback is registered, it's pinged by the PDDB. The notification -/// of closure callback is an additional feature to the typical Rust File interface. -/// - If the client attempts to read or write to any keys that span a Basis modification, -/// the now-ambiguous key operation will return a `BrokenPipe` error to the caller. +/// - If a notification callback is registered, it's pinged by the PDDB. The notification of closure callback +/// is an additional feature to the typical Rust File interface. +/// - If the client attempts to read or write to any keys that span a Basis modification, the now-ambiguous +/// key operation will return a `BrokenPipe` error to the caller. /// /// ## General flash->key structure /// @@ -178,7 +178,8 @@ /// When a Basis is created, it starts with 64k of space allocated to it. Most of this is blank (but encrypted /// in flash), but it is important in a PD filesystem to claim some freespace for day-to-day operations. This /// is because when you need to allocate more data, you have to make a guess as to what is free, and what -/// previously allocated but currently denied to exist (locked Basis look identical to free space when locked). +/// previously allocated but currently denied to exist (locked Basis look identical to free space when +/// locked). /// /// Each Dictionary within a Basis starts with a 4k allocation as well, although most of it is free space. /// Keys are then populated into dictionaries, with additional pages allocated to dictionaries as the keys @@ -235,8 +236,9 @@ /// salt/encrypted password combos. In the case of a rubber-hose attack, the attacker cannot know precisely /// when to stop the beatings, as the few remaining entries could be dummies with absolutely no meaning. /// 2. A single salt entry. In this approach, a single shared salt is used by /all/ passwords, and there is no -/// encrypted password list stored; instead, the the password as presented is directly used to decrypt -/// the Basis page table and if no valid entries are found we may conclude that the password provided has a typo. +/// encrypted password list stored; instead, the the password as presented is directly used to decrypt the +/// Basis page table and if no valid entries are found we may conclude that the password provided has a +/// typo. /// /// The advantage of (1) is that the usage of salt falls precisely within the traditional cryptographic /// specification of bcrypt(). The disadvantages of (1) include: one still has to have a method to match @@ -259,16 +261,17 @@ /// once, and then we can immediately start checking the Basis page table for valid entries. The disadvantage /// of (2) is that we are re-using a salt across all of a given user's passwords. Note that the salt itself /// is from a TRNG, so between user devices, the salt performs its role. However, it means that an attacker -/// can generate a single rainbow table specific to a given user to reverse all of their passwords; and furthermore, -/// re-used passwords are trivially discoverable with the rainbow table. +/// can generate a single rainbow table specific to a given user to reverse all of their passwords; and +/// furthermore, re-used passwords are trivially discoverable with the rainbow table. /// /// Approach (1) is probably what most crypto purists would adopt: you, dear user, /should/ understand that -/// cryptography is worth the wait. However, a second principle states that "users always pick convenience over -/// security". A 15-second wait is an eternity in the UX world, and would act as an effective deterrent to any -/// user ever using the PD function because it is too slow. Thus for v1 of the PDDB, we're going to try approach -/// (2), where a common salt is used across all the PDDB passwords, but the salt is /not/ re-used between devices. -/// This choice diminishes the overall security of the system in the case that a user chooses weak passwords, or re-uses -/// passwords, but in exchange for a great improvement in responsiveness of the implementation. +/// cryptography is worth the wait. However, a second principle states that "users always pick convenience +/// over security". A 15-second wait is an eternity in the UX world, and would act as an effective deterrent +/// to any user ever using the PD function because it is too slow. Thus for v1 of the PDDB, we're going to try +/// approach (2), where a common salt is used across all the PDDB passwords, but the salt is /not/ re-used +/// between devices. This choice diminishes the overall security of the system in the case that a user chooses +/// weak passwords, or re-uses passwords, but in exchange for a great improvement in responsiveness of the +/// implementation. /// /// The final implementation uses a slight mod on (2), where the 128-bit common salt stored on disk is XOR'd /// with the user-provided "basis name". Users are of course allowed to pick crappy names for their basis, and @@ -314,9 +317,9 @@ /// /// When writing to disk, a write-then-erase method is used: /// 1. Required blocks for the update are taken out of the .FastSpace pool -/// 2. Detached data sections of structural records are written first (only database structural records can have detached data; -/// all stored data have individual journal revision fields associated with them), and into blocks -/// taken out of the .FastSpace pool. +/// 2. Detached data sections of structural records are written first (only database structural records can +/// have detached data; all stored data have individual journal revision fields associated with them), +/// and into blocks taken out of the .FastSpace pool. /// 3. The head of the updated structural record is written, with the new latest journal rev noted. /// 4. The page table is updated using the procedure outlined below. /// 5. The old blocks are erased and overwritten with random data. @@ -324,18 +327,18 @@ /// /// When updating the page table, it's important not to lose an entire page's worth of entries in case /// power is lost when updating it. The following process is used to update a page table entry: -/// 0. At the end of the page table there is a circular buffer of blank pages (they are truly blank, -/// but this will not leak metadata about the contents of the PDDB, as it only does bookkeeping -/// on page table updates as a whole). This is known as the make-before-break buffer (mbbb). +/// 0. At the end of the page table there is a circular buffer of blank pages (they are truly blank, but +/// this will not leak metadata about the contents of the PDDB, as it only does bookkeeping on page table +/// updates as a whole). This is known as the make-before-break buffer (mbbb). /// 1. The previous mbbb entry is erased, if there is any, and the next entry is noted. /// 2. The new page table entry is written into the mbbb /// 3. The old page table entry in the page table itself is erased. /// 4. The old page table entry is populated with the updated page table records. /// /// Detecting and Repairing a broken page table: -/// The special case of long run's of 1's (more than 32 bits in a row) is checked during the page table scan. -/// If a long run of 1's is detected, then a suspected corrupted page table update is flagged, and for that page the data -/// is pulled from the mbbb record. +/// The special case of long run's of 1's (more than 32 bits in a row) is checked during the page table +/// scan. If a long run of 1's is detected, then a suspected corrupted page table update is flagged, and for +/// that page the data is pulled from the mbbb record. /// /// ## Precursor's Implementation-Specific Flash Memory Organization: /// @@ -358,11 +361,9 @@ /// ... /// 0x0008_5000 | data_phys_base - start of basis + dictionary + key data region /// ``` - -// historical note: 389 hours, 11 mins elapsed since the start of the PDDB coding, and the first attempt at a hardware test -- as evidenced by the uptime of the hardware validation unit. - +// historical note: 389 hours, 11 mins elapsed since the start of the PDDB coding, and the first attempt at a +// hardware test -- as evidenced by the uptime of the hardware validation unit. extern crate bitflags; -extern crate bitfield; mod api; use api::*; @@ -381,40 +382,39 @@ mod tests; #[allow(unused_imports)] use tests::*; -#[cfg(feature="pddb-flamegraph")] +#[cfg(feature = "pddb-flamegraph")] mod profiling; -use num_traits::*; -use xous::{send_message, Message, msg_blocking_scalar_unpack}; -use xous_ipc::Buffer; use core::cell::RefCell; -use std::rc::Rc; -use std::thread; -use std::collections::{HashMap, BTreeSet}; -use std::io::ErrorKind; use core::fmt::Write; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; - -use locales::t; - -use rkyv::{ - de::deserializers::AllocDeserializer, - ser::{Serializer, serializers::BufferSerializer}, - Deserialize, -}; use core::mem::size_of; use core::ops::Deref; +use std::collections::{BTreeSet, HashMap}; +use std::io::ErrorKind; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; -#[cfg(feature="perfcounter")] +use locales::t; +use num_traits::*; +use rkyv::{ + de::deserializers::AllocDeserializer, + ser::{serializers::BufferSerializer, Serializer}, + Deserialize, +}; +use xous::{msg_blocking_scalar_unpack, send_message, Message}; +use xous_ipc::Buffer; + +#[cfg(feature = "perfcounter")] const FILE_ID_SERVICES_PDDB_SRC_MAIN: u32 = 0; -#[cfg(feature="perfcounter")] +#[cfg(feature = "perfcounter")] const FILE_ID_SERVICES_PDDB_SRC_DICTIONARY: u32 = 1; #[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub(crate) struct BasisRequestPassword { - db_name: xous_ipc::String::<{crate::api::BASIS_NAME_LEN}>, - plaintext_pw: Option>, + db_name: xous_ipc::String<{ crate::api::BASIS_NAME_LEN }>, + plaintext_pw: Option>, } #[derive(Debug, PartialEq, Eq)] pub(crate) enum PasswordState { @@ -452,14 +452,9 @@ struct FileHandle { pub deleted: bool, } -fn main () -> ! { +fn main() -> ! { let stack_size = 1024 * 1024; - std::thread::Builder::new() - .stack_size(stack_size) - .spawn(wrapped_main) - .unwrap() - .join() - .unwrap() + std::thread::Builder::new().stack_size(stack_size).spawn(wrapped_main).unwrap().join().unwrap() } fn wrapped_main() -> ! { @@ -471,7 +466,8 @@ fn wrapped_main() -> ! { let pddb_sid = xns.register_name(api::SERVER_NAME_PDDB, None).expect("can't register server"); log::trace!("registered with NS -- {:?}", pddb_sid); - // shared entropy cache across all process-local services (it's more efficient to request entropy in blocks from the TRNG) + // shared entropy cache across all process-local services (it's more efficient to request entropy in + // blocks from the TRNG) let entropy = Rc::new(RefCell::new(TrngPool::new())); // for less-secured user prompts (everything but password entry) @@ -481,20 +477,14 @@ fn wrapped_main() -> ! { // other processes from crafting them. let pw_sid = xous::create_server().expect("couldn't create a server for the password UX handler"); let pw_cid = xous::connect(pw_sid).expect("couldn't connect to the password UX handler"); - let pw_handle = thread::spawn({ - move || { - password_ux_manager( - xous::connect(pddb_sid).unwrap(), - pw_sid - ) - } - }); + let pw_handle = thread::spawn({ move || password_ux_manager(xous::connect(pddb_sid).unwrap(), pw_sid) }); // OS-specific PDDB driver let mut pddb_os = PddbOs::new(Rc::clone(&entropy), pw_cid); // storage for the basis cache let mut basis_cache = BasisCache::new(); - // storage for the token lookup: given an ApiToken, return a dict/key/basis set. Basis can be None or specified. + // storage for the token lookup: given an ApiToken, return a dict/key/basis set. Basis can be None or + // specified. let mut token_dict = HashMap::::new(); // Process-indexed map of file descriptors to token records @@ -506,7 +496,8 @@ fn wrapped_main() -> ! { let is_mounted = is_mounted.clone(); move || { let xns = xous_names::XousNames::new().unwrap(); - let poller_sid = xns.register_name(api::SERVER_NAME_PDDB_POLLER, None).expect("can't register server"); + let poller_sid = + xns.register_name(api::SERVER_NAME_PDDB_POLLER, None).expect("can't register server"); loop { let msg = xous::receive_message(poller_sid).unwrap(); match FromPrimitive::from_usize(msg.body.id() & 0xffff) { @@ -538,13 +529,12 @@ fn wrapped_main() -> ! { }); // run the CI tests if the option has been selected - #[cfg(all( - not(target_os = "xous"), - feature = "ci" - ))] + #[cfg(all(not(target_os = "xous"), feature = "ci"))] ci_tests(&mut pddb_os).map_err(|e| log::error!("{}", e)).ok(); - if false { // this will re-init the PDDB and do a simple key query. Really useful only for early shake-down testing, eliminate this reminder stub once we have some confidence in the code + if false { + // this will re-init the PDDB and do a simple key query. Really useful only for early shake-down + // testing, eliminate this reminder stub once we have some confidence in the code hw_testcase(&mut pddb_os); } @@ -565,21 +555,31 @@ fn wrapped_main() -> ! { if flush_interval > PERIODIC_FLUSH_MS { // this runs once a day, and skips the scrub request when it runs flush_interval = 0; - send_message(my_cid, - Message::new_blocking_scalar(Opcode::FlushSpaceUpdate.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't send flush request"); + send_message( + my_cid, + Message::new_blocking_scalar( + Opcode::FlushSpaceUpdate.to_usize().unwrap(), + 0, + 0, + 0, + 0, + ), + ) + .expect("couldn't send flush request"); } else { // this is what actually runs every interval, to a first order - send_message(my_cid, - Message::new_scalar(Opcode::PeriodicScrub.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("couldn't send scrub request"); + send_message( + my_cid, + Message::new_scalar(Opcode::PeriodicScrub.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't send scrub request"); } } } } }); // main server loop - let mut key_list: Option::> = None; // storage for key lists + let mut key_list: Option> = None; // storage for key lists let mut key_token: Option<[u32; 4]> = None; let mut dict_list = Vec::::new(); // storage for dict lists let mut dict_token: Option<[u32; 4]> = None; @@ -604,11 +604,8 @@ fn wrapped_main() -> ! { const HEAP_GC_THRESH: usize = 1800 * 1024; // the larger limit is at 2048kiB, set this smaller const HEAP_GC_TARGET: usize = 1500 * 1024; // how much to try cleaning out in any one go. let new_limit = HEAP_LARGER_LIMIT; - let result = xous::rsyscall(xous::SysCall::AdjustProcessLimit( - xous::Limits::HeapMaximum as usize, - 0, - new_limit, - )); + let result = + xous::rsyscall(xous::SysCall::AdjustProcessLimit(xous::Limits::HeapMaximum as usize, 0, new_limit)); if let Ok(xous::Result::Scalar2(1, current_limit)) = result { xous::rsyscall(xous::SysCall::AdjustProcessLimit( @@ -624,12 +621,13 @@ fn wrapped_main() -> ! { let tt = ticktimer_server::Ticktimer::new().unwrap(); // turn on (or off) performance profiling, if the feature is enabled - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pddb_os.set_use_perf(true); // register a suspend/resume listener - let mut susres = susres::Susres::new(Some(susres::SuspendOrder::Early), &xns, - Opcode::SuspendResume as u32, my_cid).expect("couldn't create suspend/resume object"); + let mut susres = + susres::Susres::new(Some(susres::SuspendOrder::Early), &xns, Opcode::SuspendResume as u32, my_cid) + .expect("couldn't create suspend/resume object"); loop { let mut msg = xous::receive_message(pddb_sid).unwrap(); let op: Opcode = FromPrimitive::from_usize(msg.body.id() & 0xffff).unwrap_or(Opcode::InvalidOpcode); @@ -649,7 +647,8 @@ fn wrapped_main() -> ! { // IsMounted follows the same return code pattern as TryMount, because the return value // is stuck into the TryMount notification queue Opcode::IsMounted => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, { - if basis_cache.basis_count() > 0 { // if there's anything in the cache, we're mounted. + if basis_cache.basis_count() > 0 { + // if there's anything in the cache, we're mounted. xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); } else { mount_notifications.push(msg.sender); // defer response until later @@ -660,7 +659,8 @@ fn wrapped_main() -> ! { xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); #[cfg(target_os = "xous")] - if basis_cache.basis_count() > 0 { // if there's anything in the cache, we're mounted; by definition it was attempted + if basis_cache.basis_count() > 0 { + // if there's anything in the cache, we're mounted; by definition it was attempted xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); } else { attempt_notifications.push(msg.sender); // defer response until later @@ -669,14 +669,18 @@ fn wrapped_main() -> ! { // The return code from this is a scalar2 with the following meanings: // (code, count): // - code = 0 -> successful mount - // - code = 1 -> mount failed, for any reason other than too many retried PINs. `count` is the number of retries, if any. - // - code = 2 -> mount failed, because too many PINs were retried. `count` is the number of retries. - // If we need more nuance out of this routine, consider creating a custom public enum type to help marshall this. + // - code = 1 -> mount failed, for any reason other than too many retried PINs. `count` is the + // number of retries, if any. + // - code = 2 -> mount failed, because too many PINs were retried. `count` is the number of + // retries. + // If we need more nuance out of this routine, consider creating a custom public enum type to help + // marshall this. Opcode::TryMount => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, { let llio = llio::Llio::new(&xns); while !llio.is_ec_ready() { // spin-wait while the EC is not ready -- so that a PDDB mount does not interfere with - // on-going EC updates. Also causes the mount dialog to delay until the EC status has been checked. + // on-going EC updates. Also causes the mount dialog to delay until the EC status has been + // checked. tt.sleep_ms(1000).ok(); } if basis_cache.basis_count() > 0 { @@ -692,51 +696,83 @@ fn wrapped_main() -> ! { } else { match ensure_password(&modals, &mut pddb_os, pw_cid) { PasswordState::Correct => { - if try_mount_or_format(&modals, &mut pddb_os, &mut basis_cache, PasswordState::Correct, time_resetter, &mut basis_monitor_notifications) { + if try_mount_or_format( + &modals, + &mut pddb_os, + &mut basis_cache, + PasswordState::Correct, + time_resetter, + &mut basis_monitor_notifications, + ) { is_mounted.store(true, Ordering::SeqCst); for requester in mount_notifications.drain(..) { - xous::return_scalar2(requester, 0, 0).expect("couldn't return scalar"); + xous::return_scalar2(requester, 0, 0) + .expect("couldn't return scalar"); } - assert!(mount_notifications.len() == 0, "apparently I don't understand what drain() does"); + assert!( + mount_notifications.len() == 0, + "apparently I don't understand what drain() does" + ); log::info!("{}PDDB.MOUNTED,{}", xous::BOOKEND_START, xous::BOOKEND_END); xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); } else { xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar"); } - }, - PasswordState::Uninit => { - if try_mount_or_format(&modals, &mut pddb_os, &mut basis_cache, PasswordState::Uninit, time_resetter, &mut basis_monitor_notifications) { - for requester in mount_notifications.drain(..) { - xous::return_scalar2(requester, 0, 0).expect("couldn't return scalar"); - } - assert!(mount_notifications.len() == 0, "apparently I don't understand what drain() does"); - log::info!("{}PDDB.MOUNTED,{}", xous::BOOKEND_START, xous::BOOKEND_END); - xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); - is_mounted.store(true, Ordering::SeqCst); - } else { - xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar"); - } - }, - PasswordState::ForcedAbort(failcount) => { - xous::return_scalar2(msg.sender, 2, - // failcount is a u64, but on u32-archs, this gets truncated going to usize. clip to u32::MAX. - failcount.min(u32::MAX as u64) as usize - ).expect("couldn't return scalar"); } - PasswordState::Incorrect(failcount) => xous::return_scalar2(msg.sender, 1, - failcount.min(u32::MAX as u64) as usize - ).expect("couldn't return scalar"), + PasswordState::Uninit => { + if try_mount_or_format( + &modals, + &mut pddb_os, + &mut basis_cache, + PasswordState::Uninit, + time_resetter, + &mut basis_monitor_notifications, + ) { + for requester in mount_notifications.drain(..) { + xous::return_scalar2(requester, 0, 0) + .expect("couldn't return scalar"); + } + assert!( + mount_notifications.len() == 0, + "apparently I don't understand what drain() does" + ); + log::info!("{}PDDB.MOUNTED,{}", xous::BOOKEND_START, xous::BOOKEND_END); + xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar"); + is_mounted.store(true, Ordering::SeqCst); + } else { + xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar"); + } + } + PasswordState::ForcedAbort(failcount) => { + xous::return_scalar2( + msg.sender, + 2, + // failcount is a u64, but on u32-archs, this gets truncated going to + // usize. clip to u32::MAX. + failcount.min(u32::MAX as u64) as usize, + ) + .expect("couldn't return scalar"); + } + PasswordState::Incorrect(failcount) => { + xous::return_scalar2(msg.sender, 1, failcount.min(u32::MAX as u64) as usize) + .expect("couldn't return scalar") + } } - // get a handle to the GAM and inform it that main menu should be allowed. The handle is dropped when this routine finishes. + // get a handle to the GAM and inform it that main menu should be allowed. The handle + // is dropped when this routine finishes. let gam = gam::Gam::new(&xns).unwrap(); gam.allow_mainmenu().expect("couldn't allow main menu activation"); // setup the heap initial_heap = heap_usage(); latest_heap = initial_heap; latest_cache = basis_cache.cache_size(); - log::info!("PDDB post-mount caching stats: {} heap, {} cache", latest_heap, latest_cache); + log::info!( + "PDDB post-mount caching stats: {} heap, {} cache", + latest_heap, + latest_cache + ); scrub_run.store(true, Ordering::SeqCst); - #[cfg(feature="pddb-flamegraph")] + #[cfg(feature = "pddb-flamegraph")] profiling::do_query_work(); } } @@ -758,15 +794,26 @@ fn wrapped_main() -> ! { && ((latest_heap - initial_heap) > HEAP_GC_THRESH)) // this line is mostly so this triggers occasionally in hosted mode where heap usage is faked; // in practice heap threshold will always hit before cache threshold - || (current_cache > HEAP_GC_THRESH) { - log::info!("PDDB trim threshold reached: {} heap, {} cache", latest_heap, basis_cache.cache_size()); + || (current_cache > HEAP_GC_THRESH) + { + log::info!( + "PDDB trim threshold reached: {} heap, {} cache", + latest_heap, + basis_cache.cache_size() + ); let pruned = basis_cache.cache_prune(&mut pddb_os, HEAP_GC_TARGET); latest_heap = heap_usage(); - log::info!("{} pruned, now: {} heap, {} cache", pruned, latest_heap, basis_cache.cache_size()) + log::info!( + "{} pruned, now: {} heap, {} cache", + pruned, + latest_heap, + basis_cache.cache_size() + ) } } Opcode::ListBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut list_ipc = buffer.to_original::().unwrap(); let basis_list = basis_cache.basis_list(); for (src, dst) in basis_list.iter().zip(list_ipc.list.iter_mut()) { @@ -804,7 +851,8 @@ fn wrapped_main() -> ! { } } Opcode::LatestBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut mgmt = buffer.to_original::().unwrap(); if let Some(name) = basis_cache.basis_latest() { mgmt.name.clear(); @@ -820,7 +868,9 @@ fn wrapped_main() -> ! { mem.offset = None; if let Some(name) = basis_cache.basis_latest() { // Safety: `u8` contains no undefined values - for (src, dest) in unsafe { name.as_bytes().iter().zip(mem.buf.as_slice_mut().iter_mut()) } { + for (src, dest) in + unsafe { name.as_bytes().iter().zip(mem.buf.as_slice_mut().iter_mut()) } + { *dest = *src; } mem.offset = xous::MemorySize::new(name.len().min(mem.buf.len())); @@ -831,29 +881,36 @@ fn wrapped_main() -> ! { unimplemented!() } Opcode::CreateBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut mgmt = buffer.to_original::().unwrap(); match mgmt.code { PddbRequestCode::Create => { - let request = BasisRequestPassword { - db_name: mgmt.name, - plaintext_pw: None, - }; + let request = BasisRequestPassword { db_name: mgmt.name, plaintext_pw: None }; let mut buf = Buffer::into_buf(request).unwrap(); buf.lend_mut(pw_cid, PwManagerOpcode::RequestPassword.to_u32().unwrap()).unwrap(); let ret = buf.to_original::().unwrap(); if let Some(pw) = ret.plaintext_pw { - match basis_cache.basis_create(&mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8"), pw.as_str().expect("password was not valid utf-8")) { + match basis_cache.basis_create( + &mut pddb_os, + mgmt.name.as_str().expect("name is not valid utf-8"), + pw.as_str().expect("password was not valid utf-8"), + ) { Ok(_) => { - log::info!("{}PDDB.CREATEOK,{},{}", xous::BOOKEND_START, mgmt.name.as_str().unwrap(), xous::BOOKEND_END); + log::info!( + "{}PDDB.CREATEOK,{},{}", + xous::BOOKEND_START, + mgmt.name.as_str().unwrap(), + xous::BOOKEND_END + ); mgmt.code = PddbRequestCode::NoErr - }, + } Err(e) => match e.kind() { ErrorKind::AlreadyExists => { mgmt.code = PddbRequestCode::DuplicateEntry; } _ => mgmt.code = PddbRequestCode::InternalError, - } + }, } } else { mgmt.code = PddbRequestCode::InternalError; @@ -866,39 +923,63 @@ fn wrapped_main() -> ! { buffer.replace(mgmt).unwrap(); } Opcode::OpenBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut mgmt = buffer.to_original::().unwrap(); match mgmt.code { PddbRequestCode::Open => { let mut finished = false; while !finished { - let request = BasisRequestPassword { - db_name: mgmt.name, - plaintext_pw: None, - }; + let request = BasisRequestPassword { db_name: mgmt.name, plaintext_pw: None }; let mut buf = Buffer::into_buf(request).unwrap(); buf.lend_mut(pw_cid, PwManagerOpcode::RequestPassword.to_u32().unwrap()).unwrap(); let ret = buf.to_original::().unwrap(); if let Some(pw) = ret.plaintext_pw { if let Some(basis) = basis_cache.basis_unlock( - &mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8"), pw.as_str().expect("password was not valid utf-8"), - mgmt.policy.unwrap_or(BasisRetentionPolicy::Persist) + &mut pddb_os, + mgmt.name.as_str().expect("name is not valid utf-8"), + pw.as_str().expect("password was not valid utf-8"), + mgmt.policy.unwrap_or(BasisRetentionPolicy::Persist), ) { if basis_cache.basis_contains(&basis.name) { - basis_cache.basis_unmount(&mut pddb_os, &basis.name).expect("couldn't unmount previously mounted basis of same name"); - modals.show_notification(t!("pddb.unmount_previous", locales::LANG), None).expect("notification failed"); + basis_cache + .basis_unmount(&mut pddb_os, &basis.name) + .expect("couldn't unmount previously mounted basis of same name"); + modals + .show_notification( + t!("pddb.unmount_previous", locales::LANG), + None, + ) + .expect("notification failed"); } basis_cache.basis_add(basis); finished = true; - log::info!("{}PDDB.UNLOCKOK,{},{}", xous::BOOKEND_START, mgmt.name.as_str().unwrap(), xous::BOOKEND_END); + log::info!( + "{}PDDB.UNLOCKOK,{},{}", + xous::BOOKEND_START, + mgmt.name.as_str().unwrap(), + xous::BOOKEND_END + ); if basis_monitor_notifications.len() > 0 { - notify_basis_change(&mut basis_monitor_notifications, basis_cache.basis_list()); + notify_basis_change( + &mut basis_monitor_notifications, + basis_cache.basis_list(), + ); } mgmt.code = PddbRequestCode::NoErr; } else { - log::info!("{}PDDB.BADPASS,{},{}", xous::BOOKEND_START, mgmt.name.as_str().unwrap(), xous::BOOKEND_END); - modals.add_list_item(t!("pddb.yes", locales::LANG)).expect("couldn't build radio item list"); - modals.add_list_item(t!("pddb.no", locales::LANG)).expect("couldn't build radio item list"); + log::info!( + "{}PDDB.BADPASS,{},{}", + xous::BOOKEND_START, + mgmt.name.as_str().unwrap(), + xous::BOOKEND_END + ); + modals + .add_list_item(t!("pddb.yes", locales::LANG)) + .expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.no", locales::LANG)) + .expect("couldn't build radio item list"); match modals.get_radiobutton(t!("pddb.badpass", locales::LANG)) { Ok(response) => { if response.as_str() == t!("pddb.yes", locales::LANG) { @@ -928,22 +1009,28 @@ fn wrapped_main() -> ! { buffer.replace(mgmt).unwrap(); } Opcode::CloseBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut mgmt = buffer.to_original::().unwrap(); notify_of_disconnect(&mut pddb_os, &token_dict, &mut basis_cache); match mgmt.code { PddbRequestCode::Close => { - match basis_cache.basis_unmount(&mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8")) { + match basis_cache + .basis_unmount(&mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8")) + { Ok(_) => { mgmt.code = PddbRequestCode::NoErr; if basis_monitor_notifications.len() > 0 { - notify_basis_change(&mut basis_monitor_notifications, basis_cache.basis_list()); + notify_basis_change( + &mut basis_monitor_notifications, + basis_cache.basis_list(), + ); } } Err(e) => match e.kind() { ErrorKind::NotFound => mgmt.code = PddbRequestCode::NotFound, _ => mgmt.code = PddbRequestCode::InternalError, - } + }, } } _ => { @@ -953,17 +1040,20 @@ fn wrapped_main() -> ! { buffer.replace(mgmt).unwrap(); } Opcode::DeleteBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut mgmt = buffer.to_original::().unwrap(); notify_of_disconnect(&mut pddb_os, &token_dict, &mut basis_cache); match mgmt.code { PddbRequestCode::Delete => { - match basis_cache.basis_delete(&mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8")) { + match basis_cache + .basis_delete(&mut pddb_os, mgmt.name.as_str().expect("name is not valid utf-8")) + { Ok(_) => mgmt.code = PddbRequestCode::NoErr, Err(e) => match e.kind() { ErrorKind::NotFound => mgmt.code = PddbRequestCode::NotFound, _ => mgmt.code = PddbRequestCode::InternalError, - } + }, } } _ => { @@ -973,10 +1063,16 @@ fn wrapped_main() -> ! { buffer.replace(mgmt).unwrap(); } Opcode::KeyRequest => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 3, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 3, + std::line!(), + ); for basis in basis_cache.access_list().iter() { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req: PddbKeyRequest = buffer.to_original::().unwrap(); let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) @@ -986,66 +1082,107 @@ fn wrapped_main() -> ! { let dict = req.dict.as_str().expect("dict utf-8 decode error"); let key = req.key.as_str().expect("key utf-8 decode error"); log::debug!("get: {:?} {}", bname, key); - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 3, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 3, + std::line!(), + ); if basis_cache.dict_attributes(&mut pddb_os, dict, bname).is_err() { if req.create_dict { match basis_cache.dict_add(&mut pddb_os, dict, bname) { Ok(_) => (), - Err(e) => { - match e.kind() { - std::io::ErrorKind::OutOfMemory => {req.result = PddbRequestCode::NoFreeSpace; buffer.replace(req).unwrap(); continue} - std::io::ErrorKind::NotFound => {req.result = PddbRequestCode::NotMounted; buffer.replace(req).unwrap(); continue} - _ => {req.result = PddbRequestCode::InternalError; buffer.replace(req).unwrap(); continue} + Err(e) => match e.kind() { + std::io::ErrorKind::OutOfMemory => { + req.result = PddbRequestCode::NoFreeSpace; + buffer.replace(req).unwrap(); + continue; } - } + std::io::ErrorKind::NotFound => { + req.result = PddbRequestCode::NotMounted; + buffer.replace(req).unwrap(); + continue; + } + _ => { + req.result = PddbRequestCode::InternalError; + buffer.replace(req).unwrap(); + continue; + } + }, } } else { req.result = PddbRequestCode::NotFound; - buffer.replace(req).unwrap(); continue + buffer.replace(req).unwrap(); + continue; } } - let alloc_hint = if let Some(hint) = req.alloc_hint {Some(hint as usize)} else {None}; - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 3, std::line!()); + let alloc_hint = if let Some(hint) = req.alloc_hint { Some(hint as usize) } else { None }; + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 3, + std::line!(), + ); if basis_cache.key_attributes(&mut pddb_os, dict, key, bname).is_err() { if !req.create_key { req.result = PddbRequestCode::NotFound; - buffer.replace(req).unwrap(); continue + buffer.replace(req).unwrap(); + continue; } else { // create an empty key placeholder let empty: [u8; 0] = []; - match basis_cache.key_update(&mut pddb_os, - dict, key, &empty, None, alloc_hint, bname, + match basis_cache.key_update( + &mut pddb_os, + dict, + key, + &empty, + None, + alloc_hint, + bname, // don't truncate if we've been given an explicit size hint. - alloc_hint.is_none() + alloc_hint.is_none(), ) { - Ok(_) => {}, + Ok(_) => {} Err(e) => { log::error!("Couldn't allocate key: {:?}", e); match e.kind() { - std::io::ErrorKind::NotFound => req.result = PddbRequestCode::NotMounted, - std::io::ErrorKind::OutOfMemory => req.result = PddbRequestCode::NoFreeSpace, + std::io::ErrorKind::NotFound => { + req.result = PddbRequestCode::NotMounted + } + std::io::ErrorKind::OutOfMemory => { + req.result = PddbRequestCode::NoFreeSpace + } _ => req.result = PddbRequestCode::InternalError, } - buffer.replace(req).unwrap(); continue + buffer.replace(req).unwrap(); + continue; } } } } - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 3, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 3, + std::line!(), + ); // at this point, we have established a basis/dict/key tuple. let token: ApiToken = [pddb_os.trng_u32(), pddb_os.trng_u32(), pddb_os.trng_u32()]; let cid = if let Some(cb_sid) = req.cb_sid { - Some(xous::connect(xous::SID::from_array(cb_sid)).expect("couldn't connect for callback")) + Some( + xous::connect(xous::SID::from_array(cb_sid)) + .expect("couldn't connect for callback"), + ) } else { None }; let token_record = TokenRecord { dict: String::from(dict), key: String::from(key), - basis: if let Some(name) = bname {Some(String::from(name))} else {None}, + basis: if let Some(name) = bname { Some(String::from(name)) } else { None }, conn: cid, alloc_hint, }; @@ -1055,13 +1192,23 @@ fn wrapped_main() -> ! { buffer.replace(req).unwrap(); break; // if we got here, entry was found, stop searching } - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 3, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 3, + std::line!(), + ); } Opcode::OpenKeyStd => { if let Some(mem) = msg.body.memory_message_mut() { mem.offset = None; - if let Err(e) = libstd::open_key(mem, &mut pddb_os, &mut basis_cache, fd_mapping.entry(msg.sender.pid()).or_default()) { + if let Err(e) = libstd::open_key( + mem, + &mut pddb_os, + &mut basis_cache, + fd_mapping.entry(msg.sender.pid()).or_default(), + ) { mem.offset = xous::MemoryAddress::new(e as usize); } } @@ -1075,7 +1222,8 @@ fn wrapped_main() -> ! { if let Some(conn_to_remove) = rec.conn { let mut still_needs_cid = false; for r in token_dict.values() { - // check through the remaining dictionary values to see if they have a connection that is the same as our number + // check through the remaining dictionary values to see if they have a connection + // that is the same as our number if let Some(existing_conn) = r.conn { if existing_conn == conn_to_remove { still_needs_cid = true; @@ -1085,14 +1233,19 @@ fn wrapped_main() -> ! { } // if nobody else had my connection number, disconnect it. if !still_needs_cid { - unsafe{xous::disconnect(conn_to_remove).expect("couldn't disconnect from callback server")}; + unsafe { + xous::disconnect(conn_to_remove) + .expect("couldn't disconnect from callback server") + }; } } else { - // if there was no/never a connection allocated, there's no connection to remove. do nothing. + // if there was no/never a connection allocated, there's no connection to remove. do + // nothing. } xous::return_scalar(msg.sender, PddbRetcode::Ok as usize).expect("couldn't ack KeyDrop"); } else { - xous::return_scalar(msg.sender, PddbRetcode::BasisLost as usize).expect("couldn't ack KeyDrop"); + xous::return_scalar(msg.sender, PddbRetcode::BasisLost as usize) + .expect("couldn't ack KeyDrop"); } }), @@ -1106,19 +1259,17 @@ fn wrapped_main() -> ! { } else { fd_mapping.remove(&msg.sender.pid()); xous::return_scalar2(msg.sender, 0, 0) - }.ok(); + } + .ok(); } } } Opcode::DeleteKey => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req: PddbKeyRequest = buffer.to_original::().unwrap(); - let bname = if req.basis_specified { - Some(req.basis.as_str().unwrap()) - } else { - None - }; + let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) } else { None }; let dict = req.dict.as_str().expect("dict utf-8 decode error"); let key = req.key.as_str().expect("key utf-8 decode error"); match basis_cache.key_remove(&mut pddb_os, dict, key, bname, false) { @@ -1152,17 +1303,16 @@ fn wrapped_main() -> ! { } req.result = PddbRequestCode::NoErr; } - Err(e) => { - match e.kind() { - std::io::ErrorKind::NotFound => req.result = PddbRequestCode::NotFound, - _ => req.result = PddbRequestCode::InternalError, - } - } + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => req.result = PddbRequestCode::NotFound, + _ => req.result = PddbRequestCode::InternalError, + }, } buffer.replace(req).unwrap(); } Opcode::DictBulkDelete => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); let mut key_list = Vec::::new(); // the [u8] data is structured as a packed list of u8-len + u8 data slice. The max length of @@ -1177,7 +1327,9 @@ fn wrapped_main() -> ! { req.retcode = PddbRetcode::InternalError; break; } - let key = String::from(std::str::from_utf8(&req.data[index..index+strlen]).unwrap_or("UTF8 error")); + let key = String::from( + std::str::from_utf8(&req.data[index..index + strlen]).unwrap_or("UTF8 error"), + ); key_list.push(key); index += strlen; } @@ -1187,18 +1339,14 @@ fn wrapped_main() -> ! { } log::info!("Deleting key list: {:?}", key_list); let start = tt.elapsed_ms(); - let bname = if req.basis_specified { - Some(req.basis.as_str().unwrap()) - } else { - None - }; + let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) } else { None }; let dict = req.dict.as_str().expect("dict utf-8 decode error"); match basis_cache.key_list_remove(&mut pddb_os, dict, key_list, bname) { Ok(_) => req.retcode = PddbRetcode::Ok, Err(e) => match e.kind() { std::io::ErrorKind::NotFound => req.retcode = PddbRetcode::AccessDenied, _ => req.retcode = PddbRetcode::InternalError, - } + }, } log::info!("Bulk delete finished in {}ms", tt.elapsed_ms() - start); buffer.replace(req).ok(); @@ -1206,19 +1354,17 @@ fn wrapped_main() -> ! { Opcode::DeleteKeyStd => { if let Some(mem) = msg.body.memory_message_mut() { mem.offset = None; - if let Err(err) = libstd::delete_key(mem, &mut pddb_os, &mut basis_cache, &mut fd_mapping) { + if let Err(err) = libstd::delete_key(mem, &mut pddb_os, &mut basis_cache, &mut fd_mapping) + { mem.offset = xous::MemoryAddress::new(err as usize); } } } Opcode::DeleteDict => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req: PddbKeyRequest = buffer.to_original::().unwrap(); - let bname = if req.basis_specified { - Some(req.basis.as_str().unwrap()) - } else { - None - }; + let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) } else { None }; let dict = req.dict.as_str().expect("dict utf-8 decode error"); log::debug!("attempting to remove dict {} basis {:?}", dict, bname); match basis_cache.dict_remove(&mut pddb_os, dict, bname, false) { @@ -1252,12 +1398,10 @@ fn wrapped_main() -> ! { } req.result = PddbRequestCode::NoErr; } - Err(e) => { - match e.kind() { - std::io::ErrorKind::NotFound => req.result = PddbRequestCode::NotFound, - _ => req.result = PddbRequestCode::InternalError, - } - } + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => req.result = PddbRequestCode::NotFound, + _ => req.result = PddbRequestCode::InternalError, + }, } buffer.replace(req).unwrap(); } @@ -1278,15 +1422,17 @@ fn wrapped_main() -> ! { } } Opcode::KeyAttributes => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); if let Some(token_record) = token_dict.get(&req.token) { - let bname = if let Some(name) = &token_record.basis { - Some(name.as_str()) - } else { - None - }; - match basis_cache.key_attributes(&mut pddb_os, &token_record.dict, &token_record.key, bname) { + let bname = if let Some(name) = &token_record.basis { Some(name.as_str()) } else { None }; + match basis_cache.key_attributes( + &mut pddb_os, + &token_record.dict, + &token_record.key, + bname, + ) { Ok(attr) => { buffer.replace(PddbKeyAttrIpc::from_attributes(attr, req.token)).unwrap(); } @@ -1298,9 +1444,15 @@ fn wrapped_main() -> ! { } } Opcode::KeyCountInDict => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 0, std::line!()); - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 0, + std::line!(), + ); + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); if key_token.is_some() { log::debug!("key list already in progress"); @@ -1309,14 +1461,10 @@ fn wrapped_main() -> ! { continue; } key_token = Some(req.token); - let bname = if req.basis_specified { - Some(req.basis.as_str().unwrap()) - } else { - None - }; + let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) } else { None }; let dict = req.dict.as_str().expect("dict utf-8 decode error"); log::debug!("counting keys in dict {} basis {:?}", dict, bname); - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 0, std::line!()); key_list = match basis_cache.key_list(&mut pddb_os, dict, bname) { Ok((list, key_count, found_key_count)) => { @@ -1336,13 +1484,24 @@ fn wrapped_main() -> ! { } }; buffer.replace(req).unwrap(); - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 0, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 0, + std::line!(), + ); } Opcode::ListKeyV2 => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 1, std::line!()); - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 1, + std::line!(), + ); + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); if let Some(token) = key_token { if req.token != token { @@ -1351,20 +1510,20 @@ fn wrapped_main() -> ! { if let Some(klist) = &mut key_list { let mut index = 0; loop { - let keyname = - if let Some(keyname) = klist.iter().next() { - keyname.to_string() - } else { - key_token = None; - req.end = true; - req.retcode = PddbRetcode::Ok; - break; - }; + let keyname = if let Some(keyname) = klist.iter().next() { + keyname.to_string() + } else { + key_token = None; + req.end = true; + req.retcode = PddbRetcode::Ok; + break; + }; if keyname.len() + 1 + index < MAX_PDDBKLISTLEN { assert!(keyname.len() < u8::MAX as usize); // this should always be true due to other limits in the PDDB req.data[index] = keyname.len() as u8; index += 1; - req.data[index..index + keyname.len()].copy_from_slice(keyname.as_bytes()); + req.data[index..index + keyname.len()] + .copy_from_slice(keyname.as_bytes()); index += keyname.len(); klist.remove(&keyname); } else { @@ -1385,8 +1544,13 @@ fn wrapped_main() -> ! { req.retcode = PddbRetcode::AccessDenied; } buffer.replace(req).unwrap(); - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 1, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 1, + std::line!(), + ); } Opcode::ListKeyStd => { if let Some(mem) = msg.body.memory_message_mut() { @@ -1397,7 +1561,8 @@ fn wrapped_main() -> ! { } } Opcode::DictCountInBasis => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); if dict_token.is_some() { req.code = PddbRequestCode::AccessDenied; @@ -1406,18 +1571,15 @@ fn wrapped_main() -> ! { } dict_token = Some(req.token); dict_list.clear(); - let bname = if req.basis_specified { - Some(req.basis.as_str().unwrap()) - } else { - None - }; + let bname = if req.basis_specified { Some(req.basis.as_str().unwrap()) } else { None }; let list = basis_cache.dict_list(&mut pddb_os, bname); if list.len() > 0 { req.index = list.len() as u32; for dict in list { dict_list.push(dict); } - } else { // no dicts to list, reset the state + } else { + // no dicts to list, reset the state dict_token = None; dict_list.clear(); } @@ -1425,7 +1587,8 @@ fn wrapped_main() -> ! { buffer.replace(req).unwrap(); } Opcode::GetDictNameAtIndex => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut req = buffer.to_original::().unwrap(); if let Some(token) = dict_token { if req.token != token { @@ -1434,7 +1597,8 @@ fn wrapped_main() -> ! { if req.index >= dict_list.len() as u32 { req.code = PddbRequestCode::InternalError; } else { - req.dict = xous_ipc::String::::from_str(&dict_list[req.index as usize]); + req.dict = + xous_ipc::String::::from_str(&dict_list[req.index as usize]); req.code = PddbRequestCode::NoErr; // the last index requested must be the highest one! if req.index == dict_list.len() as u32 - 1 { @@ -1457,66 +1621,97 @@ fn wrapped_main() -> ! { } } Opcode::ReadKey => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 4, std::line!()); - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 4, + std::line!(), + ); + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let pbuf = PddbBuf::from_slice_mut(buffer.as_mut()); // direct translation, no serialization necessary for performance let token = pbuf.token; if let Some(rec) = token_dict.get(&token) { for basis in basis_cache.access_list().iter() { // let temp = if let Some (name) = &rec.basis {Some(name)} else {Some(basis)}; - // log::debug!("read (spec: {:?}){:?} {} len {} pos {}", rec.basis, temp, rec.key, pbuf.len, pbuf.position); - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 4, std::line!()); - match basis_cache.key_read(&mut pddb_os, - &rec.dict, &rec.key, - &mut pbuf.data[..pbuf.len as usize], Some(pbuf.position as usize), - // this is a bit inefficient because if a specific basis is specified *and* the key does not exist, - // it'll retry the same basis for a number of times equal to the number of bases open. - // However, usually, there's only 1-2 bases open, and usually, if you specify a basis, - // the key will be a hit, so, we let it stand. - if let Some (name) = &rec.basis {Some(&name)} else {Some(basis)}) { + // log::debug!("read (spec: {:?}){:?} {} len {} pos {}", rec.basis, temp, rec.key, + // pbuf.len, pbuf.position); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 4, + std::line!(), + ); + match basis_cache.key_read( + &mut pddb_os, + &rec.dict, + &rec.key, + &mut pbuf.data[..pbuf.len as usize], + Some(pbuf.position as usize), + // this is a bit inefficient because if a specific basis is specified *and* the + // key does not exist, it'll retry the same basis for + // a number of times equal to the number of bases open. + // However, usually, there's only 1-2 bases open, and usually, if you specify a + // basis, the key will be a hit, so, we let it stand. + if let Some(name) = &rec.basis { Some(&name) } else { Some(basis) }, + ) { Ok(readlen) => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 4, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 4, + std::line!(), + ); pbuf.len = readlen as u16; pbuf.retcode = PddbRetcode::Ok; break; } Err(e) => match e.kind() { std::io::ErrorKind::NotFound => pbuf.retcode = PddbRetcode::BasisLost, - std::io::ErrorKind::UnexpectedEof => pbuf.retcode = PddbRetcode::UnexpectedEof, + std::io::ErrorKind::UnexpectedEof => { + pbuf.retcode = PddbRetcode::UnexpectedEof + } std::io::ErrorKind::OutOfMemory => pbuf.retcode = PddbRetcode::DiskFull, _ => pbuf.retcode = PddbRetcode::InternalError, - } + }, } } } else { pbuf.retcode = PddbRetcode::BasisLost; } // we don't need a "replace" operation because all ops happen in-place - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 4, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 4, + std::line!(), + ); } // Optimized bulk data read handler. See lib.rs for documentation. Opcode::DictBulkRead => { const BULKREAD_TIMEOUT_MS: u64 = 5000; - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 5, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 5, + std::line!(), + ); let range = msg.body.memory_message_mut().unwrap(); - let buf = unsafe { core::slice::from_raw_parts_mut( - range.buf.as_mut_ptr(), - range.buf.len(), - )}; + let buf = unsafe { core::slice::from_raw_parts_mut(range.buf.as_mut_ptr(), range.buf.len()) }; // unpack the descriptor - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 5, std::line!()); let pos = range.offset.map(|o| o.get()).unwrap_or_default(); let r = unsafe { rkyv::archived_value::(buf, pos) }; let bulk_descriptor = r.deserialize(&mut AllocDeserializer).unwrap(); // check for a timeout; retire state if we did timeout - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 5, std::line!()); let mut timed_out = false; if let Some(state) = &bulkread_state { @@ -1528,7 +1723,7 @@ fn wrapped_main() -> ! { bulkread_state = None; } // handle state initialization, if no call was previously initiated - #[cfg(feature="perfcounter")] + #[cfg(feature = "perfcounter")] pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 5, std::line!()); let mut first_call = false; if bulkread_state.is_none() { @@ -1541,32 +1736,44 @@ fn wrapped_main() -> ! { Some(bulk_descriptor.basis.as_str().unwrap()) } else { None - } + }, ) { - Ok((list, _, _)) => list.into_iter().rev().collect(), // reverse order so Vec can just pop and get "first" item + Ok((list, _, _)) => list.into_iter().rev().collect(), /* reverse order so Vec can + * just pop and get "first" + * item */ Err(e) => { match e.kind() { - std::io::ErrorKind::NotFound => { - buf[..4].copy_from_slice(&(PddbBulkReadCode::NotFound as u32).to_le_bytes()) - } - _ => { - buf[..4].copy_from_slice(&(PddbBulkReadCode::InternalError as u32).to_le_bytes()) - } + std::io::ErrorKind::NotFound => buf[..4] + .copy_from_slice(&(PddbBulkReadCode::NotFound as u32).to_le_bytes()), + _ => buf[..4] + .copy_from_slice(&(PddbBulkReadCode::InternalError as u32).to_le_bytes()), } - // this will cause the return record to just have the error code copied to it. The rest is invalid. + // this will cause the return record to just have the error code copied to it. The + // rest is invalid. continue; } }; let state = BulkReadState { - token: [pddb_os.trng_u32(), pddb_os.trng_u32(), pddb_os.trng_u32(), pddb_os.trng_u32()], + token: [ + pddb_os.trng_u32(), + pddb_os.trng_u32(), + pddb_os.trng_u32(), + pddb_os.trng_u32(), + ], is_basis_specified: bulk_descriptor.basis_specified, - basis: if bulk_descriptor.basis_specified{ bulk_descriptor.basis.to_string() } else { String::new() }, + basis: if bulk_descriptor.basis_specified { + bulk_descriptor.basis.to_string() + } else { + String::new() + }, dict: bulk_descriptor.dict.to_string(), total_keys: key_list.len(), key_list, buf_starting_key_index: 0, last_time: tt.elapsed_ms(), - read_limit: bulk_descriptor.bulk_limit.expect("bulk limit must be specified for bulk read calls"), + read_limit: bulk_descriptor + .bulk_limit + .expect("bulk limit must be specified for bulk read calls"), read_total: 0, }; bulkread_state = Some(state); @@ -1574,8 +1781,13 @@ fn wrapped_main() -> ! { let mut finished = false; // handle data packing into the structure if let Some(state) = &mut bulkread_state { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 5, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 5, + std::line!(), + ); // check that the token matches, if this isn't a first call to the function if !first_call { if state.token != bulk_descriptor.token { @@ -1593,143 +1805,200 @@ fn wrapped_main() -> ! { let (header_buf, mut buf) = buf.split_at_mut(size_of::()); enum SerializeResult<'a> { Success(usize, usize, &'a mut [u8], String, &'a mut [u8]), - Failure(String) + Failure(String), } loop { if buf.len() < size_of::() * 2 { // not enough space to hold our header records, break and get a new buf break; } - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_STARTBLOCK, 6, std::line!()); - let ser_result: SerializeResult = - if let Some(key_name) = state.key_list.pop() { - let attr = match basis_cache.key_attributes(&mut pddb_os, - &state.dict, - &key_name, - if state.is_basis_specified{Some(&state.basis)} else {None} - ) { - Ok(attr) => attr, - Err(e) => { - modals.show_notification( - &format!("Error: key not found during bulk read:\n{:?}\n{:?}:{}:{}", + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_STARTBLOCK, + 6, + std::line!(), + ); + let ser_result: SerializeResult = if let Some(key_name) = state.key_list.pop() { + let attr = match basis_cache.key_attributes( + &mut pddb_os, + &state.dict, + &key_name, + if state.is_basis_specified { Some(&state.basis) } else { None }, + ) { + Ok(attr) => attr, + Err(e) => { + modals + .show_notification( + &format!( + "Error: key not found during bulk read:\n{:?}\n{:?}:{}:{}", e, - if state.is_basis_specified{Some(&state.basis)} else {None}, + if state.is_basis_specified { + Some(&state.basis) + } else { + None + }, &state.dict, &key_name, - ), - None).ok(); - continue; - } - }; - if attr.len < state.read_limit - state.read_total { - let mut d = vec![0u8; attr.len]; - match basis_cache.key_read( - &mut pddb_os, - &state.dict, - &key_name, - &mut d, - None, - Some(&attr.basis), - ) { - Ok(readlen) => { - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 6, std::line!()); - assert!(readlen == attr.len, "Bulk read key length did not match expected length"); - let rec = PddbKeyRecord { - name: key_name.to_string(), - len: attr.len, - reserved: attr.reserved, - age: attr.age, - index: attr.index, - basis: attr.basis, - data: if d.len() > 0 { Some(d) } else { None } - }; - state.read_total += attr.len; // commit the read length early - let (prebuf, sbuf) = buf.split_at_mut(size_of::()*2); - let mut serializer = BufferSerializer::new(sbuf); - let len = size_of::(); - match serializer.serialize_value(&rec) { - Ok(pos) => SerializeResult::Success(pos, len, serializer.into_inner(), key_name, prebuf), - Err(_) => SerializeResult::Failure(key_name) - } - } - Err(e) => { - panic!("Error reading previously attributed key {}: {:?}", &key_name, e); + ), + None, + ) + .ok(); + continue; + } + }; + if attr.len < state.read_limit - state.read_total { + let mut d = vec![0u8; attr.len]; + match basis_cache.key_read( + &mut pddb_os, + &state.dict, + &key_name, + &mut d, + None, + Some(&attr.basis), + ) { + Ok(readlen) => { + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 6, + std::line!(), + ); + assert!( + readlen == attr.len, + "Bulk read key length did not match expected length" + ); + let rec = PddbKeyRecord { + name: key_name.to_string(), + len: attr.len, + reserved: attr.reserved, + age: attr.age, + index: attr.index, + basis: attr.basis, + data: if d.len() > 0 { Some(d) } else { None }, + }; + state.read_total += attr.len; // commit the read length early + let (prebuf, sbuf) = buf.split_at_mut(size_of::() * 2); + let mut serializer = BufferSerializer::new(sbuf); + let len = size_of::(); + match serializer.serialize_value(&rec) { + Ok(pos) => SerializeResult::Success( + pos, + len, + serializer.into_inner(), + key_name, + prebuf, + ), + Err(_) => SerializeResult::Failure(key_name), } } - } else { - log::info!("hit size limit: limit {} total {}", state.read_limit, state.read_total); - // report the key, but with no data - let rec = PddbKeyRecord { - name: key_name.to_string(), - len: attr.len, - reserved: attr.reserved, - age: attr.age, - index: attr.index, - basis: attr.basis, - data: None, - }; - let (pre_buf, buf) = buf.split_at_mut(size_of::()*2); - let mut serializer = BufferSerializer::new(buf); - let len = size_of::(); - match serializer.serialize_value(&rec) { - Ok(pos) => SerializeResult::Success(pos, len, serializer.into_inner(), key_name, pre_buf), - Err(_) => SerializeResult::Failure(key_name) + Err(e) => { + panic!( + "Error reading previously attributed key {}: {:?}", + &key_name, e + ); } } } else { - // no more keys; we're done. - finished = true; - break; - }; + log::info!( + "hit size limit: limit {} total {}", + state.read_limit, + state.read_total + ); + // report the key, but with no data + let rec = PddbKeyRecord { + name: key_name.to_string(), + len: attr.len, + reserved: attr.reserved, + age: attr.age, + index: attr.index, + basis: attr.basis, + data: None, + }; + let (pre_buf, buf) = buf.split_at_mut(size_of::() * 2); + let mut serializer = BufferSerializer::new(buf); + let len = size_of::(); + match serializer.serialize_value(&rec) { + Ok(pos) => SerializeResult::Success( + pos, + len, + serializer.into_inner(), + key_name, + pre_buf, + ), + Err(_) => SerializeResult::Failure(key_name), + } + } + } else { + // no more keys; we're done. + finished = true; + break; + }; - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_NONE, 6, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_NONE, + 6, + std::line!(), + ); match ser_result { SerializeResult::Success(pos, len, sbuf, _key_name, pre_buf) => { log::debug!("packing message of {}({})", len + pos, pos); // data can fit, copy it into the buffer. state.buf_starting_key_index += 1; header.len += 1; - // read length increment was handled when the data was copied into the serialization buffer. + // read length increment was handled when the data was copied into the + // serialization buffer. pre_buf[..4].copy_from_slice( //&(sbuf.len() as u32).to_le_bytes() - &((len + pos) as u32).to_le_bytes() - ); - pre_buf[4..8].copy_from_slice( - &(pos as u32).to_le_bytes() + &((len + pos) as u32).to_le_bytes(), ); + pre_buf[4..8].copy_from_slice(&(pos as u32).to_le_bytes()); (_, buf) = sbuf.split_at_mut(len + pos); } SerializeResult::Failure(key_name) => { - log::debug!("ran out of space filling buffer, pushing {} back into the queue", key_name); + log::debug!( + "ran out of space filling buffer, pushing {} back into the queue", + key_name + ); // data didn't fit, quit with finished = false; state.key_list.push(key_name); break; } } - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 6, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 6, + std::line!(), + ); } if finished { header.code = PddbBulkReadCode::Last as u32; } else { header.code = PddbBulkReadCode::Streaming as u32; } - header_buf.copy_from_slice( - header.deref() - ); + header_buf.copy_from_slice(header.deref()); // update the last access time state.last_time = tt.elapsed_ms(); } else { - log::warn!("This should be unreachable, the state should always be initialized by this point"); + log::warn!( + "This should be unreachable, the state should always be initialized by this point" + ); } if finished { bulkread_state = None; } - #[cfg(feature="perfcounter")] - pddb_os.perf_entry(FILE_ID_SERVICES_PDDB_SRC_MAIN, perflib::PERFMETA_ENDBLOCK, 5, std::line!()); + #[cfg(feature = "perfcounter")] + pddb_os.perf_entry( + FILE_ID_SERVICES_PDDB_SRC_MAIN, + perflib::PERFMETA_ENDBLOCK, + 5, + std::line!(), + ); } Opcode::SeekKeyStd => { @@ -1737,11 +2006,19 @@ fn wrapped_main() -> ! { if let Some(scalar) = msg.body.scalar_message() { // NOTE: endian swap for compatibility with `std` let seek_by = (((scalar.arg3 as u32) as u64) << 32) | ((scalar.arg2 as u32) as u64); - let result = libstd::seek_key(scalar.arg1, seek_by, fd_mapping.entry(msg.sender.pid()).or_default(), fd); + let result = libstd::seek_key( + scalar.arg1, + seek_by, + fd_mapping.entry(msg.sender.pid()).or_default(), + fd, + ); if msg.body.is_blocking() { match result { - Ok(offset) => xous::return_scalar2(msg.sender, offset as usize, (offset >> 32) as usize).ok(), - Err(e) =>xous::return_scalar(msg.sender, e as usize).ok(), + Ok(offset) => { + xous::return_scalar2(msg.sender, offset as usize, (offset >> 32) as usize) + .ok() + } + Err(e) => xous::return_scalar(msg.sender, e as usize).ok(), }; } } @@ -1751,25 +2028,36 @@ fn wrapped_main() -> ! { let fd = (msg.body.id() >> 16) & 0xffff; if let Some(mem) = msg.body.memory_message_mut() { mem.offset = None; - if let Err(e) = libstd::read_key(mem, &mut pddb_os, &mut basis_cache, fd_mapping.entry(msg.sender.pid()).or_default(), fd) { + if let Err(e) = libstd::read_key( + mem, + &mut pddb_os, + &mut basis_cache, + fd_mapping.entry(msg.sender.pid()).or_default(), + fd, + ) { mem.offset = xous::MemoryAddress::new(e as usize); } } } Opcode::WriteKey => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let pbuf = PddbBuf::from_slice_mut(buffer.as_mut()); // direct translation, no serialization necessary for performance let token = pbuf.token; if let Some(rec) = token_dict.get(&token) { for basis in basis_cache.access_list().iter() { - let temp = if let Some (name) = &rec.basis {Some(name)} else {Some(basis)}; + let temp = if let Some(name) = &rec.basis { Some(name) } else { Some(basis) }; log::debug!("write (spec: {:?}){:?} {}", rec.basis, temp, rec.key); - match basis_cache.key_update(&mut pddb_os, - &rec.dict, &rec.key, - &pbuf.data[..pbuf.len as usize], Some(pbuf.position as usize), - rec.alloc_hint, if let Some (name) = &rec.basis {Some(&name)} else {Some(basis)}, - false + match basis_cache.key_update( + &mut pddb_os, + &rec.dict, + &rec.key, + &pbuf.data[..pbuf.len as usize], + Some(pbuf.position as usize), + rec.alloc_hint, + if let Some(name) = &rec.basis { Some(&name) } else { Some(basis) }, + false, ) { Ok(_) => { pbuf.retcode = PddbRetcode::Ok; @@ -1777,10 +2065,12 @@ fn wrapped_main() -> ! { } Err(e) => match e.kind() { std::io::ErrorKind::NotFound => pbuf.retcode = PddbRetcode::BasisLost, - std::io::ErrorKind::UnexpectedEof => pbuf.retcode = PddbRetcode::UnexpectedEof, + std::io::ErrorKind::UnexpectedEof => { + pbuf.retcode = PddbRetcode::UnexpectedEof + } std::io::ErrorKind::OutOfMemory => pbuf.retcode = PddbRetcode::DiskFull, _ => pbuf.retcode = PddbRetcode::InternalError, - } + }, } } } else { @@ -1796,7 +2086,13 @@ fn wrapped_main() -> ! { let fd = (msg.body.id() >> 16) & 0xffff; if let Some(mem) = msg.body.memory_message_mut() { mem.offset = None; - if let Err(e) = libstd::write_key(mem, &mut pddb_os, &mut basis_cache, fd_mapping.entry(msg.sender.pid()).or_default(), fd) { + if let Err(e) = libstd::write_key( + mem, + &mut pddb_os, + &mut basis_cache, + fd_mapping.entry(msg.sender.pid()).or_default(), + fd, + ) { mem.offset = xous::MemoryAddress::new(e as usize); } } @@ -1806,10 +2102,17 @@ fn wrapped_main() -> ! { match basis_cache.sync(&mut pddb_os, None, if cleanup == 1 { true } else { false }) { Ok(_) => xous::return_scalar(msg.sender, PddbRetcode::Ok.to_usize().unwrap()).unwrap(), Err(e) => match e.kind() { - std::io::ErrorKind::OutOfMemory => xous::return_scalar(msg.sender, PddbRetcode::DiskFull.to_usize().unwrap()).unwrap(), - std::io::ErrorKind::NotFound => xous::return_scalar(msg.sender, PddbRetcode::BasisLost.to_usize().unwrap()).unwrap(), - _ => xous::return_scalar(msg.sender, PddbRetcode::InternalError.to_usize().unwrap()).unwrap(), - } + std::io::ErrorKind::OutOfMemory => { + xous::return_scalar(msg.sender, PddbRetcode::DiskFull.to_usize().unwrap()) + .unwrap() + } + std::io::ErrorKind::NotFound => { + xous::return_scalar(msg.sender, PddbRetcode::BasisLost.to_usize().unwrap()) + .unwrap() + } + _ => xous::return_scalar(msg.sender, PddbRetcode::InternalError.to_usize().unwrap()) + .unwrap(), + }, }; }), @@ -1821,25 +2124,29 @@ fn wrapped_main() -> ! { note.push_str("\n"); } modals.show_notification(¬e, None).expect("couldn't show basis list"); - }, + } Opcode::MenuChangePin => { if basis_cache.basis_count() == 0 { - modals.show_notification(t!("pddb.changepin.mountfirst", locales::LANG), None) + modals + .show_notification(t!("pddb.changepin.mountfirst", locales::LANG), None) .expect("couldn't show notification"); continue; } match pddb_os.pddb_change_pin(&modals) { - Ok(_) => modals.show_notification(t!("pddb.changepin.success", locales::LANG), None) - .expect("couldn't show notification"), + Ok(_) => modals + .show_notification(t!("pddb.changepin.success", locales::LANG), None) + .expect("couldn't show notification"), Err(e) => { log::error!("Error changing PIN: {:?}", e); - modals.show_notification(t!("pddb.changepin.nochange", locales::LANG), None) - .expect("couldn't show notification"); + modals + .show_notification(t!("pddb.changepin.nochange", locales::LANG), None) + .expect("couldn't show notification"); } } - }, + } Opcode::RekeyPddb => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let rekey_op = buffer.to_original::().unwrap(); let result = basis_cache.rekey(&mut pddb_os, rekey_op); buffer.replace(result).unwrap(); @@ -1853,10 +2160,19 @@ fn wrapped_main() -> ! { xous::return_scalar(msg.sender, 1).ok(); } Opcode::Prune => { - log::info!("PDDB prune manual request: {} heap, {} cache", latest_heap, basis_cache.cache_size()); + log::info!( + "PDDB prune manual request: {} heap, {} cache", + latest_heap, + basis_cache.cache_size() + ); let pruned = basis_cache.cache_prune(&mut pddb_os, HEAP_GC_TARGET); latest_heap = heap_usage(); - log::info!("{} pruned, now: {} heap, {} cache", pruned, latest_heap, basis_cache.cache_size()); + log::info!( + "{} pruned, now: {} heap, {} cache", + pruned, + latest_heap, + basis_cache.cache_size() + ); xous::return_scalar(msg.sender, 1).ok(); } #[cfg(not(target_os = "xous"))] @@ -1866,14 +2182,17 @@ fn wrapped_main() -> ! { match dbg.request { DebugRequest::Dump => { log::info!("dumping pddb to {}", dbg.dump_name.as_str().unwrap()); - #[cfg(not(feature="autobasis"))] + #[cfg(not(feature = "autobasis"))] pddb_os.dbg_dump(Some(dbg.dump_name.as_str().unwrap().to_string()), None); - #[cfg(feature="autobasis")] + #[cfg(feature = "autobasis")] { let export_extra = pddb_os.dbg_extra(); - pddb_os.dbg_dump(Some(dbg.dump_name.as_str().unwrap().to_string()), Some(&export_extra)); + pddb_os.dbg_dump( + Some(dbg.dump_name.as_str().unwrap().to_string()), + Some(&export_extra), + ); } - }, + } DebugRequest::Remount => { log::info!("attempting remount"); basis_cache = BasisCache::new(); // this effectively erases the PDDB from memory @@ -1883,7 +2202,7 @@ fn wrapped_main() -> ! { } else { log::info!("remount failed"); } - }, + } DebugRequest::Prune => { log::info!("initiating prune"); basis_cache.cache_prune(&mut pddb_os, 262144); @@ -1894,26 +2213,24 @@ fn wrapped_main() -> ! { } } } - #[cfg(all(feature="pddbtest", feature="autobasis"))] + #[cfg(all(feature = "pddbtest", feature = "autobasis"))] Opcode::BasisTesting => xous::msg_scalar_unpack!(msg, op, valid, _, _, { let mut config: [Option; 32] = [None::; 32]; for i in 0..32 { if ((1 << i) & valid) != 0 { - config[i] = Some( - ((1 << i) & op) != 0 - ) + config[i] = Some(((1 << i) & op) != 0) } } pddb_os.basis_testing(&mut basis_cache, &config); }), #[allow(unused_variables)] Opcode::InternalTest => xous::msg_blocking_scalar_unpack!(msg, a0, a1, a2, a3, { - #[cfg(feature="hwtest")] + #[cfg(feature = "hwtest")] { let errs = pddb_os.stresstest_read(a0 as u32, a1 as u32); xous::return_scalar2(msg.sender, errs as usize, 0).ok(); } - #[cfg(not(feature="hwtest"))] + #[cfg(not(feature = "hwtest"))] xous::return_scalar2(msg.sender, 0, 0).ok(); }), Opcode::TryUnmount => { @@ -1930,11 +2247,15 @@ fn wrapped_main() -> ! { basis_cache.basis_unmount(&mut pddb_os, &basis).expect("can't unmount extra bases"); } if basis_cache.basis_list().len() != 1 { - log::warn!("Couldn't unmount extra bases before unmounting the system basis. Failing unmount operation!"); + log::warn!( + "Couldn't unmount extra bases before unmounting the system basis. Failing unmount operation!" + ); xous::return_scalar(msg.sender, 0).unwrap(); } // finally, unmount the system basis - basis_cache.basis_unmount(&mut pddb_os, PDDB_DEFAULT_SYSTEM_BASIS).expect("can't unmount system basis"); + basis_cache + .basis_unmount(&mut pddb_os, PDDB_DEFAULT_SYSTEM_BASIS) + .expect("can't unmount system basis"); if basis_monitor_notifications.len() > 0 { notify_basis_change(&mut basis_monitor_notifications, basis_cache.basis_list()); } @@ -1947,14 +2268,13 @@ fn wrapped_main() -> ! { xous::return_scalar(msg.sender, 0).unwrap(); } } - Opcode::PddbHalt => { - loop { - log::info!("PDDB operation halted. No new PDDB requests will be honored!"); - tt.sleep_ms(10_000).unwrap(); - } - } + Opcode::PddbHalt => loop { + log::info!("PDDB operation halted. No new PDDB requests will be honored!"); + tt.sleep_ms(10_000).unwrap(); + }, Opcode::ComputeBackupHashes => { - let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let result = pddb_os.checksums(Some(&modals)); buffer.replace(result).unwrap(); } @@ -1962,10 +2282,11 @@ fn wrapped_main() -> ! { log::warn!("quitting the PDDB server"); send_message( pw_cid, - Message::new_blocking_scalar(PwManagerOpcode::Quit.to_usize().unwrap(), 0, 0, 0, 0) - ).unwrap(); + Message::new_blocking_scalar(PwManagerOpcode::Quit.to_usize().unwrap(), 0, 0, 0, 0), + ) + .unwrap(); xous::return_scalar(msg.sender, 0).unwrap(); - break + break; } Opcode::UncacheAndAskPassword => { // lock_and_ensure_password(&modals, &mut pddb_os, pw_cid); @@ -1989,9 +2310,7 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous: log::info!("Requesting login password"); loop { match pddb_os.try_login() { - PasswordState::Correct => { - return PasswordState::Correct - } + PasswordState::Correct => return PasswordState::Correct, PasswordState::Incorrect(failcount) => { pddb_os.clear_password(); // clear the bad password entry log::info!("{}PDDB.BADPW,{}", xous::BOOKEND_START, xous::BOOKEND_END); @@ -2000,19 +2319,19 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous: return PasswordState::ForcedAbort(failcount); } else { // check if the user wants to re-try or not. - modals.add_list_item(t!("pddb.yes", locales::LANG)).expect("couldn't build radio item list"); - modals.add_list_item(t!("pddb.no", locales::LANG)).expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.yes", locales::LANG)) + .expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.no", locales::LANG)) + .expect("couldn't build radio item list"); let fail_string = format!( "{}\n{}", t!("pddb.badpass", locales::LANG), - t!("pddb.failed_attempts", locales::LANG) - .replace("{fails}", &failcount.to_string()) + t!("pddb.failed_attempts", locales::LANG).replace("{fails}", &failcount.to_string()) ); - let prompt = if failcount == 0 { - t!("pddb.badpass", locales::LANG) - } else { - &fail_string - }; + let prompt = + if failcount == 0 { t!("pddb.badpass", locales::LANG) } else { &fail_string }; match modals.get_radiobutton(prompt) { Ok(response) => { if response.as_str() == t!("pddb.yes", locales::LANG) { @@ -2029,12 +2348,12 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous: } PasswordState::Uninit => { // check for a migration event - #[cfg(feature="migration1")] + #[cfg(feature = "migration1")] { if pddb_os.migration_v1_to_v2(_pw_cid) == PasswordState::Correct { if pddb_os.try_login() == PasswordState::Correct { log::info!("Migration v1->v2 successful"); - return PasswordState::Correct + return PasswordState::Correct; } else { log::warn!("Migration v1->v2 succeeded, but somehow the v2 remount failed."); } @@ -2052,7 +2371,7 @@ fn try_mount_or_format( basis_cache: &mut BasisCache, pw_state: PasswordState, time_resetter: xous::CID, - basis_monitor_notifications: &mut Vec:: + basis_monitor_notifications: &mut Vec, ) -> bool { log::info!("Attempting to mount the PDDB"); if pw_state == PasswordState::Correct { @@ -2064,13 +2383,13 @@ fn try_mount_or_format( notify_basis_change(basis_monitor_notifications, basis_cache.basis_list()); } modals.dynamic_notification_close().unwrap(); - return true + return true; } modals.dynamic_notification_close().unwrap(); } // correct password but no mount -> offer to format; uninit -> offer to format if pw_state == PasswordState::Correct || pw_state == PasswordState::Uninit { - #[cfg(any(feature = "precursor", feature = "renode", feature="test-rekey"))] + #[cfg(any(feature = "precursor", feature = "renode", feature = "test-rekey"))] { log::debug!("PDDB did not mount; requesting format"); modals.add_list_item(t!("pddb.okay", locales::LANG)).expect("couldn't build radio item list"); @@ -2093,8 +2412,12 @@ fn try_mount_or_format( if do_format { let fast: bool; if false { - modals.add_list_item(t!("pddb.no", locales::LANG)).expect("couldn't build radio item list"); - modals.add_list_item(t!("pddb.yes", locales::LANG)).expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.no", locales::LANG)) + .expect("couldn't build radio item list"); + modals + .add_list_item(t!("pddb.yes", locales::LANG)) + .expect("couldn't build radio item list"); match modals.get_radiobutton(t!("pddb.devbypass", locales::LANG)) { Ok(response) => { if response.as_str() == t!("pddb.yes", locales::LANG) { @@ -2113,16 +2436,19 @@ fn try_mount_or_format( pddb_os.pddb_format(fast, Some(&modals)).expect("couldn't format PDDB"); - // reset the RTC at the point of PDDB format. It is done now because at this point we know that - // no time offset keys can exist in the PDDB, and as a measure of good hygiene we want to restart - // our RTC counter from a random duration between epoch and 10 years to give some deniability about - // how long the device has been in use. - let _ = xous::send_message(time_resetter, + // reset the RTC at the point of PDDB format. It is done now because at this point we know + // that no time offset keys can exist in the PDDB, and as a measure of good + // hygiene we want to restart our RTC counter from a random duration between + // epoch and 10 years to give some deniability about how long the device has + // been in use. + let _ = xous::send_message( + time_resetter, xous::Message::new_blocking_scalar( 0, // the ID is "hard coded" using enumerated discriminants - 0, 0, 0, 0 - ) - ).expect("couldn't reset time"); + 0, 0, 0, 0, + ), + ) + .expect("couldn't reset time"); modals.dynamic_notification(Some(t!("pddb.waitmount", locales::LANG)), None).unwrap(); if let Some(sys_basis) = pddb_os.pddb_mount() { log::info!("PDDB mount operation finished successfully"); @@ -2144,15 +2470,17 @@ fn try_mount_or_format( false } } - #[cfg(not(any(feature = "precursor", feature = "renode", feature="test-rekey")))] + #[cfg(not(any(feature = "precursor", feature = "renode", feature = "test-rekey")))] { pddb_os.pddb_format(false, Some(&modals)).expect("couldn't format PDDB"); - let _ = xous::send_message(time_resetter, + let _ = xous::send_message( + time_resetter, xous::Message::new_blocking_scalar( 0, // the ID is "hard coded" using enumerated discriminants - 0, 0, 0, 0 - ) - ).expect("couldn't reset time"); + 0, 0, 0, 0, + ), + ) + .expect("couldn't reset time"); pddb_os.dbg_dump(Some("full".to_string()), None); if let Some(sys_basis) = pddb_os.pddb_mount() { log::info!("PDDB mount operation finished successfully"); @@ -2172,14 +2500,16 @@ fn try_mount_or_format( } } -// Test cases that have been coded to run directly on hardware (that is, they do not require host-OS debug features) +// Test cases that have been coded to run directly on hardware (that is, they do not require host-OS debug +// features) #[allow(dead_code)] pub(crate) fn manual_testcase(hw: &mut PddbOs) { log::info!("Initializing disk..."); hw.pddb_format(true, None).unwrap(); log::info!("Done initializing disk"); - // it's a vector because order is important: by default access to keys/dicts go into the latest entry first, and then recurse to the earliest + // it's a vector because order is important: by default access to keys/dicts go into the latest entry + // first, and then recurse to the earliest let mut basis_cache = BasisCache::new(); log::info!("Attempting to mount the PDDB"); @@ -2194,37 +2524,83 @@ pub(crate) fn manual_testcase(hw: &mut PddbOs) { // add a "system settings" dictionary to the default basis log::info!("adding 'system settings' dictionary"); basis_cache.dict_add(hw, "system settings", None).expect("couldn't add system settings dictionary"); - basis_cache.key_update(hw, "system settings", "wifi/wpa_keys/Kosagi", "my_wpa_key_here".as_bytes(), None, None, None, false).expect("couldn't add a key"); + basis_cache + .key_update( + hw, + "system settings", + "wifi/wpa_keys/Kosagi", + "my_wpa_key_here".as_bytes(), + None, + None, + None, + false, + ) + .expect("couldn't add a key"); let mut readback = [0u8; 15]; match basis_cache.key_read(hw, "system settings", "wifi/wpa_keys/Kosagi", &mut readback, None, None) { Ok(readsize) => { log::info!("read back {} bytes", readsize); log::info!("read data: {}", String::from_utf8_lossy(&readback)); - }, + } Err(e) => { log::info!("couldn't read data: {:?}", e); } } - basis_cache.key_update(hw, "system settings", "wifi/wpa_keys/e4200", "12345678".as_bytes(), None, None, None, false).expect("couldn't add a key"); + basis_cache + .key_update( + hw, + "system settings", + "wifi/wpa_keys/e4200", + "12345678".as_bytes(), + None, + None, + None, + false, + ) + .expect("couldn't add a key"); // add a "big" key let mut bigdata = [0u8; 5000]; for (i, d) in bigdata.iter_mut().enumerate() { *d = i as u8; } - basis_cache.key_update(hw, "system settings", "big_pool1", &bigdata, None, None, None, false).expect("couldn't add a key"); + basis_cache + .key_update(hw, "system settings", "big_pool1", &bigdata, None, None, None, false) + .expect("couldn't add a key"); basis_cache.dict_add(hw, "test_dict_2", None).expect("couldn't add test dictionary 2"); - basis_cache.key_update(hw, "test_dict_2", "test key in dict 2", "some data".as_bytes(), None, Some(128), None, false).expect("couldn't add a key to second dict"); + basis_cache + .key_update( + hw, + "test_dict_2", + "test key in dict 2", + "some data".as_bytes(), + None, + Some(128), + None, + false, + ) + .expect("couldn't add a key to second dict"); - basis_cache.key_update(hw, "system settings", "wifi/wpa_keys/e4200", "ABC".as_bytes(), Some(2), None, None, false).expect("couldn't update e4200 key"); + basis_cache + .key_update( + hw, + "system settings", + "wifi/wpa_keys/e4200", + "ABC".as_bytes(), + Some(2), + None, + None, + false, + ) + .expect("couldn't update e4200 key"); log::info!("test readback of wifi/wpa_keys/e4200"); match basis_cache.key_read(hw, "system settings", "wifi/wpa_keys/e4200", &mut readback, None, None) { Ok(readsize) => { log::info!("read back {} bytes", readsize); log::info!("read data: {}", String::from_utf8_lossy(&readback)); - }, + } Err(e) => { log::info!("couldn't read data: {:?}", e); } @@ -2253,7 +2629,7 @@ pub(crate) fn hw_testcase(pddb_os: &mut PddbOs) { Ok(readsize) => { log::info!("read back {} bytes", readsize); log::info!("read data: {}", String::from_utf8_lossy(&readback)); - }, + } Err(e) => { log::info!("couldn't read data: {:?}", e); } @@ -2263,7 +2639,11 @@ pub(crate) fn hw_testcase(pddb_os: &mut PddbOs) { pddb_os.dbg_dump(Some("manual".to_string()), None); } -fn notify_of_disconnect(pddb_os: &mut PddbOs, token_dict: &HashMap::, basis_cache: &mut BasisCache) { +fn notify_of_disconnect( + pddb_os: &mut PddbOs, + token_dict: &HashMap, + basis_cache: &mut BasisCache, +) { // 1. search to see if any of the active tokens are are in our token_dict // 2. notify them of the disconnect, if there is a callback set. for (api_key, entry) in token_dict.iter() { @@ -2271,35 +2651,54 @@ fn notify_of_disconnect(pddb_os: &mut PddbOs, token_dict: &HashMap:: { - match send_message(cb, Message::new_scalar( - pddb::CbOp::Change.to_usize().unwrap(), - api_key[0] as _, - api_key[1] as _, - api_key[2] as _, - 0, - )) { + match send_message( + cb, + Message::new_scalar( + pddb::CbOp::Change.to_usize().unwrap(), + api_key[0] as _, + api_key[1] as _, + api_key[2] as _, + 0, + ), + ) { Err(e) => { - log::warn!("Callback on {}:{} for basis removal failed: {:?}", &entry.dict, &entry.key, e); - }, + log::warn!( + "Callback on {}:{} for basis removal failed: {:?}", + &entry.dict, + &entry.key, + e + ); + } _ => { - log::debug!("Callback on {}:{} for basis removal success", &entry.dict, &entry.key); + log::debug!( + "Callback on {}:{} for basis removal success", + &entry.dict, + &entry.key + ); } } - }, + } Err(_) => { - // do nothing. It's probably not right that a key doesn't exist that we don't have in our records, but don't crash the system. - log::warn!("Disconnect basis inconsistent state, {}:{} not found", &entry.dict, &entry.key); + // do nothing. It's probably not right that a key doesn't exist that we don't have in our + // records, but don't crash the system. + log::warn!( + "Disconnect basis inconsistent state, {}:{} not found", + &entry.dict, + &entry.key + ); } } } } } -fn notify_basis_change(basis_monitor_notifications: &mut Vec::, basis_list: Vec::) { +fn notify_basis_change( + basis_monitor_notifications: &mut Vec, + basis_list: Vec, +) { for mut sender in basis_monitor_notifications.drain(..) { - let mut response = unsafe { - Buffer::from_memory_message_mut(sender.body.memory_message_mut().unwrap()) - }; + let mut response = + unsafe { Buffer::from_memory_message_mut(sender.body.memory_message_mut().unwrap()) }; let mut list_ipc = response.to_original::().unwrap(); for (src, dst) in basis_list.iter().zip(list_ipc.list.iter_mut()) { dst.clear(); @@ -2311,7 +2710,9 @@ fn notify_basis_change(basis_monitor_notifications: &mut Vec:: usize { - match xous::rsyscall(xous::SysCall::IncreaseHeap(0, xous::MemoryFlags::R)).expect("couldn't get heap size") { + match xous::rsyscall(xous::SysCall::IncreaseHeap(0, xous::MemoryFlags::R)) + .expect("couldn't get heap size") + { xous::Result::MemoryRange(m) => { let usage = m.len(); usage @@ -2319,7 +2720,7 @@ pub(crate) fn heap_usage() -> usize { _ => { log::error!("Couldn't measure heap usage"); 0 - }, + } } } @@ -2333,7 +2734,7 @@ struct BulkReadState { /// the dictionary to read dict: String, /// list of keys in the dictionary. Entries are removed as they are serialized. - key_list: Vec::, + key_list: Vec, /// total number of keys to send (e.g. initial key_list.len()) total_keys: usize, /// Each buffer can hold multiple keys; a single buffer may be re-used several diff --git a/services/pddb/src/menu.rs b/services/pddb/src/menu.rs index 2072cce65..49289ed9a 100644 --- a/services/pddb/src/menu.rs +++ b/services/pddb/src/menu.rs @@ -1,30 +1,27 @@ use gam::*; -use num_traits::*; use locales::t; -use crate::api::*; +use num_traits::*; use xous_ipc::String; +use crate::api::*; + pub(crate) fn pddb_menu(conn: xous::CID) { let mut menu_items = Vec::::new(); - menu_items.push( - MenuItem { - name: String::from_str(t!("pddb.menu.listbasis", locales::LANG)), - action_conn: Some(conn), - action_opcode: Opcode::MenuListBasis.to_u32().unwrap(), - action_payload: MenuPayload::Scalar([0, 0, 0, 0]), - close_on_select: true, - } - ); - menu_items.push( - MenuItem { - name: String::from_str(t!("pddb.menu.change_unlock_pin", locales::LANG)), - action_conn: Some(conn), - action_opcode: Opcode::MenuChangePin.to_u32().unwrap(), - action_payload: MenuPayload::Scalar([0, 0, 0, 0]), - close_on_select: true, - } - ); + menu_items.push(MenuItem { + name: String::from_str(t!("pddb.menu.listbasis", locales::LANG)), + action_conn: Some(conn), + action_opcode: Opcode::MenuListBasis.to_u32().unwrap(), + action_payload: MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }); + menu_items.push(MenuItem { + name: String::from_str(t!("pddb.menu.change_unlock_pin", locales::LANG)), + action_conn: Some(conn), + action_opcode: Opcode::MenuChangePin.to_u32().unwrap(), + action_payload: MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }); menu_items.push(MenuItem { name: String::from_str(t!("mainmenu.closemenu", locales::LANG)), action_conn: None, @@ -34,4 +31,4 @@ pub(crate) fn pddb_menu(conn: xous::CID) { }); menu_matic(menu_items, PDDB_MENU_NAME, None); -} \ No newline at end of file +} diff --git a/services/pddb/src/profiling.rs b/services/pddb/src/profiling.rs index 5363fdb99..8626feee2 100644 --- a/services/pddb/src/profiling.rs +++ b/services/pddb/src/profiling.rs @@ -1,6 +1,6 @@ -use std::thread; #[allow(unused_imports)] -use std::io::{Error, ErrorKind, Write, Read}; +use std::io::{Error, ErrorKind, Read, Write}; +use std::thread; const PROFILING_DICT: &'static str = "pdict"; const NUMKEYS: usize = 200; @@ -21,9 +21,7 @@ pub fn do_query_work() { let key_count = match pddb.list_keys(PROFILING_DICT, None) { Ok(kl) => kl.len(), - Err(_e) => { - 0 - } + Err(_e) => 0, }; // populate the dictionary if we're short the number of keys we want to have @@ -31,15 +29,7 @@ pub fn do_query_work() { if key_count < NUMKEYS { for _ in 0..NUMKEYS - key_count { let guid = gen_guid(&mut trng); - match pddb.get( - PROFILING_DICT, - &guid, - None, - true, - true, - Some(256), - None:: - ) { + match pddb.get(PROFILING_DICT, &guid, None, true, true, Some(256), None::) { Ok(mut key) => { let mut rdata = [0u8; 192]; trng.fill_bytes(&mut rdata); @@ -63,4 +53,4 @@ pub fn do_query_work() { log::info!("quitting"); }); -} \ No newline at end of file +} diff --git a/services/pddb/src/tests.rs b/services/pddb/src/tests.rs index 7a4e5010f..5f084b401 100644 --- a/services/pddb/src/tests.rs +++ b/services/pddb/src/tests.rs @@ -1,18 +1,28 @@ -use rand_chacha::ChaCha8Rng; -use rand_chacha::rand_core::RngCore; -use rand_chacha::rand_core::SeedableRng; -use crate::*; use core::sync::atomic::{AtomicU64, Ordering}; use std::collections::{BTreeSet, HashSet}; use std::io::Result; +use rand_chacha::rand_core::RngCore; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; + +use crate::*; + const UPPER_BOUND: usize = 9000; const LOWER_BOUND: usize = 12; // needs to be big enough to compute murmur3 hash + hold checksum static RNG_LOCAL_STATE: AtomicU64 = AtomicU64::new(3); -fn gen_key(dictname: &str, keynum: usize, lower_size_bound: usize, upper_size_bound: usize) -> (String, Vec::) { - let mut rng = ChaCha8Rng::seed_from_u64(RNG_LOCAL_STATE.load(Ordering::SeqCst) + xous::TESTING_RNG_SEED.load(core::sync::atomic::Ordering::SeqCst)); +fn gen_key( + dictname: &str, + keynum: usize, + lower_size_bound: usize, + upper_size_bound: usize, +) -> (String, Vec) { + let mut rng = ChaCha8Rng::seed_from_u64( + RNG_LOCAL_STATE.load(Ordering::SeqCst) + + xous::TESTING_RNG_SEED.load(core::sync::atomic::Ordering::SeqCst), + ); // we want roughly half our keys to be in the small bin, and half in the large bin let keylen = if rng.next_u32() < (u32::MAX / 2) { ((rng.next_u64() as usize) % (VPAGE_SIZE - lower_size_bound)) + lower_size_bound @@ -25,12 +35,13 @@ fn gen_key(dictname: &str, keynum: usize, lower_size_bound: usize, upper_size_bo // but it helps the test checking program check things. let keyname = format!("sanitycheck|{}|key{}|len{}", dictname, keynum, keylen); let mut keydata = Vec::::new(); - for i in 0..keylen-4 { + for i in 0..keylen - 4 { // the data starts with a number equal to the key number, and increments from there keydata.push((keynum + i) as u8); } // a checksum is appended to each run of data - // copy the stored data, and pad it out to a multiple of word lengths with 0's so we can compute the checksum + // copy the stored data, and pad it out to a multiple of word lengths with 0's so we can compute the + // checksum let mut checkdata = Vec::::new(); for &b in &keydata { checkdata.push(b); @@ -44,14 +55,17 @@ fn gen_key(dictname: &str, keynum: usize, lower_size_bound: usize, upper_size_bo (keyname, keydata) } -pub(crate) fn create_basis_testcase(hw: &mut PddbOs, basis_cache: &mut BasisCache, - maybe_num_dicts: Option, maybe_num_keys: Option, maybe_key_sizes: Option<(usize, usize)>, +pub(crate) fn create_basis_testcase( + hw: &mut PddbOs, + basis_cache: &mut BasisCache, + maybe_num_dicts: Option, + maybe_num_keys: Option, + maybe_key_sizes: Option<(usize, usize)>, maybe_extra_reserved: Option, ) -> Result<()> { - hw.pddb_format(false, None).unwrap(); let sys_basis = hw.pddb_mount().expect("couldn't mount system basis"); - basis_cache.basis_add(sys_basis); + basis_cache.basis_add(sys_basis); let num_dicts = maybe_num_dicts.unwrap_or(4); let num_keys = maybe_num_keys.unwrap_or(34); @@ -72,14 +86,20 @@ pub(crate) fn create_basis_testcase(hw: &mut PddbOs, basis_cache: &mut BasisCach let (keyname, keydata) = gen_key(&dictname, keynum, key_lower_bound, key_upper_bound); // now we're ready to write it out if maybe_extra_reserved.is_none() { - // we do this slightly funky way instead of just passing Some(0) because we want to test the "None" path explicitly - basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, - None, - None, false)?; + // we do this slightly funky way instead of just passing Some(0) because we want to test the + // "None" path explicitly + basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, None, None, false)?; } else { - basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, + basis_cache.key_update( + hw, + &dictname, + &keyname, + &keydata, + None, Some(keydata.len() + extra_reserved), - None, false)?; + None, + false, + )?; } } } @@ -87,9 +107,14 @@ pub(crate) fn create_basis_testcase(hw: &mut PddbOs, basis_cache: &mut BasisCach } /// Delete & add dictionary consistency check -pub(crate) fn delete_add_dict_consistency(hw: &mut PddbOs, basis_cache: &mut BasisCache, - maybe_num_dicts: Option, maybe_num_keys: Option, maybe_key_sizes: Option<(usize, usize)>, - maybe_extra_reserved: Option, basis_name: Option<&str> +pub(crate) fn delete_add_dict_consistency( + hw: &mut PddbOs, + basis_cache: &mut BasisCache, + maybe_num_dicts: Option, + maybe_num_keys: Option, + maybe_key_sizes: Option<(usize, usize)>, + maybe_extra_reserved: Option, + basis_name: Option<&str>, ) -> Result<()> { let evict_count = maybe_num_dicts.unwrap_or(1); let num_keys = maybe_num_keys.unwrap_or(36); @@ -113,7 +138,7 @@ pub(crate) fn delete_add_dict_consistency(hw: &mut PddbOs, basis_cache: &mut Bas if evicted < evict_count { log::debug!("evicting dict {}", evict_dict); match basis_cache.dict_remove(hw, evict_dict, basis_name, false) { - Ok(_) => {}, + Ok(_) => {} Err(e) => log::error!("Error evicting dictionary {}: {:?}", evict_dict, e), } } else { @@ -128,14 +153,20 @@ pub(crate) fn delete_add_dict_consistency(hw: &mut PddbOs, basis_cache: &mut Bas log::debug!("adding {}:{}", dictname, keyname); // now we're ready to write it out if maybe_extra_reserved.is_none() { - // we do this slightly funky way instead of just passing Some(0) because we want to test the "None" path explicitly - basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, - None, - basis_name, false)?; + // we do this slightly funky way instead of just passing Some(0) because we want to test the + // "None" path explicitly + basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, None, basis_name, false)?; } else { - basis_cache.key_update(hw, &dictname, &keyname, &keydata, None, + basis_cache.key_update( + hw, + &dictname, + &keyname, + &keydata, + None, Some(keydata.len() + extra_reserved), - basis_name, false)?; + basis_name, + false, + )?; } } } @@ -143,9 +174,13 @@ pub(crate) fn delete_add_dict_consistency(hw: &mut PddbOs, basis_cache: &mut Bas } /// patch data check -pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, - maybe_patch_offset: Option, maybe_patch_data: Option, extend: bool) -> Result<()> { - +pub(crate) fn patch_test( + hw: &mut PddbOs, + basis_cache: &mut BasisCache, + maybe_patch_offset: Option, + maybe_patch_data: Option, + extend: bool, +) -> Result<()> { let patch_offset = maybe_patch_offset.unwrap_or(5); let patch_data = maybe_patch_data.unwrap_or("patched!".to_string()); @@ -172,15 +207,33 @@ pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, let key_list = key_list_unord; for key in key_list.iter() { log::debug!("updating {}:{}", dict, key); - // this actually does something a bit more complicated on a multi-basis system than you'd think: - // it will get the union of all key names, and then patch the *latest open basis* only with new data. - // note: if the key didn't already exist in the latest open basis, it's added, with just that patch data in it. - // to override this behavior, you'd want to specify a _specific_ basis, but for testing purposes, we only have one - // so we're doing this way that would lead to surprising results if it were copied as template code elsewhere. - match basis_cache.key_update(hw, dict, key, patch_data.as_bytes(), Some(patch_offset), None, None, false) { + // this actually does something a bit more complicated on a multi-basis system than you'd + // think: it will get the union of all key names, and then patch the *latest + // open basis* only with new data. note: if the key didn't already exist in + // the latest open basis, it's added, with just that patch data in it. + // to override this behavior, you'd want to specify a _specific_ basis, but for testing + // purposes, we only have one so we're doing this way that would lead to + // surprising results if it were copied as template code elsewhere. + match basis_cache.key_update( + hw, + dict, + key, + patch_data.as_bytes(), + Some(patch_offset), + None, + None, + false, + ) { Ok(_) => (), Err(e) => { - log::error!("couldn't update key {}:{} with {} bytes data offset {}: {:?}", dict, key, patch_data.as_bytes().len(), patch_offset, e); + log::error!( + "couldn't update key {}:{} with {} bytes data offset {}: {:?}", + dict, + key, + patch_data.as_bytes().len(), + patch_offset, + e + ); panic!("error updating patch key"); } } @@ -192,7 +245,7 @@ pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, if !extend { // now re-compute the checksum let mut checkdata = Vec::::new(); - for &b in &patchbuf[..readlen-4] { + for &b in &patchbuf[..readlen - 4] { checkdata.push(b); } log::trace!("checkdata len: {}", checkdata.len()); @@ -200,7 +253,16 @@ pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, checkdata.push(0); } let checksum = murmur3_32(&checkdata, 0); - basis_cache.key_update(hw, dict, key, &checksum.to_le_bytes(), Some(readlen-4), None, None, false)?; + basis_cache.key_update( + hw, + dict, + key, + &checksum.to_le_bytes(), + Some(readlen - 4), + None, + None, + false, + )?; } else { // this gloms a new checksum onto the existing record, adding 4 new bytes. let mut checkdata = Vec::::new(); @@ -212,7 +274,16 @@ pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, checkdata.push(0); } let checksum = murmur3_32(&checkdata, 0); - basis_cache.key_update(hw, dict, key, &checksum.to_le_bytes(), Some(readlen), None, None, false)?; + basis_cache.key_update( + hw, + dict, + key, + &checksum.to_le_bytes(), + Some(readlen), + None, + None, + false, + )?; } } } @@ -220,8 +291,12 @@ pub(crate) fn patch_test(hw: &mut PddbOs, basis_cache: &mut BasisCache, Ok(()) } -pub(crate) fn delete_pattern(hw: &mut PddbOs, basis_cache: &mut BasisCache, - maybe_num_dicts: Option, maybe_num_keys: Option, maybe_key_sizes: Option<(usize, usize)>, +pub(crate) fn delete_pattern( + hw: &mut PddbOs, + basis_cache: &mut BasisCache, + maybe_num_dicts: Option, + maybe_num_keys: Option, + maybe_key_sizes: Option<(usize, usize)>, maybe_extra_reserved: Option, ) -> Result<()> { let evict_count = maybe_num_dicts.unwrap_or(2); @@ -250,9 +325,10 @@ pub(crate) fn delete_pattern(hw: &mut PddbOs, basis_cache: &mut BasisCache, key_list.insert(s); } for (index, key) in key_list.iter().enumerate() { - if index % 2 == (evict_iter % 2) { // exercise odd/even patterns + if index % 2 == (evict_iter % 2) { + // exercise odd/even patterns log::info!("deleting {}:{}", evict_dict, key); - basis_cache.key_remove(hw, evict_dict, key,None, false).unwrap(); + basis_cache.key_remove(hw, evict_dict, key, None, false).unwrap(); let da = basis_cache.dict_attributes(hw, evict_dict, None).unwrap(); log::info!("{:?}", da); } @@ -288,18 +364,23 @@ pub(crate) fn delete_pattern(hw: &mut PddbOs, basis_cache: &mut BasisCache, log::warn!("rng collision on keygen: {}, deleting key", keyname); basis_cache.key_remove(hw, dictname, &keyname, None, false)?; } - _ => { - } + _ => {} } if maybe_extra_reserved.is_none() { - // we do this slightly funky way instead of just passing Some(0) because we want to test the "None" path explicitly - basis_cache.key_update(hw, dictname, &keyname, &keydata, None, - None, - None, false)?; + // we do this slightly funky way instead of just passing Some(0) because we want to test the + // "None" path explicitly + basis_cache.key_update(hw, dictname, &keyname, &keydata, None, None, None, false)?; } else { - basis_cache.key_update(hw, dictname, &keyname, &keydata, None, + basis_cache.key_update( + hw, + dictname, + &keyname, + &keydata, + None, Some(keydata.len() + extra_reserved), - None, false)?; + None, + false, + )?; } } } @@ -360,7 +441,11 @@ pub(crate) fn list_all(hw: &mut PddbOs, basis_cache: &mut BasisCache) { let da = match basis_cache.dict_attributes(hw, &dict, None) { Ok(da) => da, Err(e) => { - log::warn!("dictionary {} not in sub-basis (this is normal for multi-basis systems, {:?}", dict, e); + log::warn!( + "dictionary {} not in sub-basis (this is normal for multi-basis systems, {:?}", + dict, + e + ); continue; } }; @@ -370,7 +455,10 @@ pub(crate) fn list_all(hw: &mut PddbOs, basis_cache: &mut BasisCache) { for key in key_list.iter() { let attrs = match basis_cache.key_attributes(hw, dict, key, None) { Ok(a) => a, - Err(e) => {log::debug!("key not in basis, searching another basis, {:?}", e); continue}, + Err(e) => { + log::debug!("key not in basis, searching another basis, {:?}", e); + continue; + } }; log::debug!("{}:{}=>{:?}", dict, key, attrs); sanity_count += 1; @@ -398,14 +486,16 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { const EXTRA_BASIS_PW: &'static str = "some password blah blah"; log::set_max_level(log::LevelFilter::Info); - log::info!("Seed for this run: {}", xous::TESTING_RNG_SEED.load(core::sync::atomic::Ordering::SeqCst)); + log::info!( + "Seed for this run: {}", + xous::TESTING_RNG_SEED.load(core::sync::atomic::Ordering::SeqCst) + ); pddb_os.test_reset(); log::info!("Creating `basecase1e`"); log::set_max_level(log::LevelFilter::Info); let mut basis_cache = BasisCache::new(); - create_basis_testcase(pddb_os, &mut basis_cache, None, - None, None, Some(32))?; + create_basis_testcase(pddb_os, &mut basis_cache, None, None, None, Some(32))?; log::info!("Saving `basecase1e` to local host"); pddb_os.dbg_dump(Some("basecase1e".to_string()), None); let extra_basis_key = pddb_os.basis_derive_key(EXTRA_BASIS, EXTRA_BASIS_PW); @@ -413,23 +503,18 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { for (&src, dst) in EXTRA_BASIS.as_bytes().iter().zip(name.iter_mut()) { *dst = src; } - let extra_export = KeyExport { - basis_name: name, - key: extra_basis_key.data, - pt_key: extra_basis_key.pt, - }; - let mut export: Vec:: = Vec::new(); + let extra_export = + KeyExport { basis_name: name, key: extra_basis_key.data, pt_key: extra_basis_key.pt }; + let mut export: Vec = Vec::new(); export.push(extra_export); log::info!("Building a second basis"); - basis_cache.basis_create(pddb_os, - EXTRA_BASIS, EXTRA_BASIS_PW).expect("couldn't build test basis"); + basis_cache.basis_create(pddb_os, EXTRA_BASIS, EXTRA_BASIS_PW).expect("couldn't build test basis"); log::info!("heap usage: {}", heap_usage()); test_prune(pddb_os, &mut basis_cache); log::info!("Doing delete/add consistency with data extension"); - delete_add_dict_consistency(pddb_os, &mut basis_cache, None, - None, None, None, None)?; + delete_add_dict_consistency(pddb_os, &mut basis_cache, None, None, None, None, None)?; test_prune(pddb_os, &mut basis_cache); log::info!("Saving `dachecke` to local host"); pddb_os.dbg_dump(Some("dachecke".to_string()), None); @@ -454,22 +539,19 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { pddb_os.dbg_dump(Some("patterne2".to_string()), None); log::info!("Doing delete/add consistency with data extension 2"); - delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(3), - Some(50), None, None, None)?; + delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(3), Some(50), None, None, None)?; log::info!("Saving `dachecke2` to local host"); pddb_os.dbg_dump(Some("dachecke2".to_string()), None); test_prune(pddb_os, &mut basis_cache); log::info!("Doing delete/add consistency with data extension 3"); - delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(3), - Some(50), None, None, None)?; + delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(3), Some(50), None, None, None)?; log::info!("Saving `dachecke3` to local host"); pddb_os.dbg_dump(Some("dachecke3".to_string()), None); test_prune(pddb_os, &mut basis_cache); log::info!("Doing delete/add consistency with data extension 4"); - delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(6), - Some(50), None, None, None)?; + delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(6), Some(50), None, None, None)?; log::info!("Saving `dachecke4` to local host"); pddb_os.dbg_dump(Some("dachecke4".to_string()), None); test_prune(pddb_os, &mut basis_cache); @@ -491,14 +573,22 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { } log::info!("Mounting the second basis"); - if let Some(basis2) = basis_cache.basis_unlock(pddb_os, - EXTRA_BASIS, EXTRA_BASIS_PW, BasisRetentionPolicy::Persist) { + if let Some(basis2) = + basis_cache.basis_unlock(pddb_os, EXTRA_BASIS, EXTRA_BASIS_PW, BasisRetentionPolicy::Persist) + { basis_cache.basis_add(basis2); } log::set_max_level(log::LevelFilter::Info); log::info!("Adding keys to Basis2"); - delete_add_dict_consistency(pddb_os, &mut basis_cache, Some(3), - Some(15), None, None, Some(EXTRA_BASIS))?; + delete_add_dict_consistency( + pddb_os, + &mut basis_cache, + Some(3), + Some(15), + None, + None, + Some(EXTRA_BASIS), + )?; log::info!("Saving `basis2` to local host"); test_prune(pddb_os, &mut basis_cache); pddb_os.dbg_dump(Some("basis2".to_string()), Some(&export)); @@ -543,11 +633,15 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { remount_list.insert(key.to_string()); } } - assert!(remount_list.difference(&pre_list).count() == 0, "remounted list is not identical to the original list"); + assert!( + remount_list.difference(&pre_list).count() == 0, + "remounted list is not identical to the original list" + ); log::info!("Mounting the second basis again"); - if let Some(basis2) = basis_cache.basis_unlock(pddb_os, - EXTRA_BASIS, EXTRA_BASIS_PW, BasisRetentionPolicy::Persist) { + if let Some(basis2) = + basis_cache.basis_unlock(pddb_os, EXTRA_BASIS, EXTRA_BASIS_PW, BasisRetentionPolicy::Persist) + { basis_cache.basis_add(basis2); } let mut merge2_list = HashSet::::new(); @@ -557,7 +651,10 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { merge2_list.insert(key.to_string()); } } - assert!(merge2_list.difference(&merge_list).count() == 0, "merged list is different from the original list after remount"); + assert!( + merge2_list.difference(&merge_list).count() == 0, + "merged list is different from the original list after remount" + ); list_all(pddb_os, &mut basis_cache); log::info!("CI done"); @@ -567,11 +664,11 @@ pub(crate) fn ci_tests(pddb_os: &mut PddbOs) -> Result<()> { } fn test_prune(hw: &mut PddbOs, basis_cache: &mut BasisCache) { - const TARGET_SIZE: usize = 150*1024; + const TARGET_SIZE: usize = 150 * 1024; let cache_size = basis_cache.cache_size(); if cache_size > TARGET_SIZE { log::info!("size is {}, requesting prune of {}", cache_size, cache_size - TARGET_SIZE); log::info!("pruned {}", basis_cache.cache_prune(hw, cache_size - TARGET_SIZE)); } log::info!("size is now {}", basis_cache.cache_size()); -} \ No newline at end of file +} diff --git a/services/pddb/src/ux.rs b/services/pddb/src/ux.rs index 5bf49954f..786feca89 100644 --- a/services/pddb/src/ux.rs +++ b/services/pddb/src/ux.rs @@ -1,13 +1,10 @@ -use xous::{SID, CID}; - +use gam::modal::*; +use locales::t; +use num_traits::*; use xous::msg_scalar_unpack; +use xous::{CID, SID}; use xous_ipc::Buffer; -use num_traits::*; - -use gam::modal::*; - -use locales::t; use crate::BasisRequestPassword; /* Conclusions: @@ -59,8 +56,8 @@ pub(crate) fn password_ux_manager( // the CID of the main loop, as a backchannel for async callbacks. _main_cid: CID, // the SID we're to use for our loop handler, for getting requests - ux_sid: SID) { - + ux_sid: SID, +) { let ux_cid = xous::connect(ux_sid).unwrap(); // create a thread that just handles the redrawing requests // build the core data structure here @@ -74,16 +71,17 @@ pub(crate) fn password_ux_manager( ); password_action.reset_action_payloads(1, None); - let mut pddb_modal = - Modal::new( - gam::PDDB_MODAL_NAME, - ActionType::TextEntry(password_action.clone()), - Some(t!("pddb.password", locales::LANG)), - None, - gam::SYSTEM_STYLE, - 8 - ); - pddb_modal.spawn_helper(ux_sid, pddb_modal.sid, + let mut pddb_modal = Modal::new( + gam::PDDB_MODAL_NAME, + ActionType::TextEntry(password_action.clone()), + Some(t!("pddb.password", locales::LANG)), + None, + gam::SYSTEM_STYLE, + 8, + ); + pddb_modal.spawn_helper( + ux_sid, + pddb_modal.sid, PwManagerOpcode::ModalRedraw.to_u32().unwrap(), PwManagerOpcode::ModalKeypress.to_u32().unwrap(), PwManagerOpcode::ModalDrop.to_u32().unwrap(), @@ -104,20 +102,32 @@ pub(crate) fn password_ux_manager( }; pddb_modal.modify( Some(ActionType::TextEntry(password_action.clone())), - Some(t!("pddb.password", locales::LANG)), false, - Some(format!("{}'{}'", t!("pddb.password_for", locales::LANG), db_name.as_str().unwrap()).as_str()), false, None + Some(t!("pddb.password", locales::LANG)), + false, + Some( + format!("{}'{}'", t!("pddb.password_for", locales::LANG), db_name.as_str().unwrap()) + .as_str(), + ), + false, + None, + ); + log::info!( + "{}PDDB.REQPW,{},{}", + xous::BOOKEND_START, + db_name.as_str().unwrap(), + xous::BOOKEND_END ); - log::info!("{}PDDB.REQPW,{},{}", xous::BOOKEND_START, db_name.as_str().unwrap(), xous::BOOKEND_END); pddb_modal.activate(); dr = Some(msg); - - }, + } Some(PwManagerOpcode::PwReturn) => { let mut buf = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; let pw = buf.to_original::().unwrap(); if let Some(mut response) = dr.take() { - let mut buffer = unsafe { Buffer::from_memory_message_mut(response.body.memory_message_mut().unwrap()) }; + let mut buffer = unsafe { + Buffer::from_memory_message_mut(response.body.memory_message_mut().unwrap()) + }; let mut request = buffer.to_original::().unwrap(); request.plaintext_pw = Some(xous_ipc::String::from_str(pw.first().as_str())); // return the password to the caller @@ -129,10 +139,10 @@ pub(crate) fn password_ux_manager( pw.first().volatile_clear(); // ensure the data is destroyed after sending to the keys enclave buf.volatile_clear(); - }, + } Some(PwManagerOpcode::ModalRedraw) => { pddb_modal.redraw(); - }, + } Some(PwManagerOpcode::ModalKeypress) => msg_scalar_unpack!(msg, k1, k2, k3, k4, { let keys = [ core::char::from_u32(k1 as u32).unwrap_or('\u{0000}'), @@ -142,14 +152,15 @@ pub(crate) fn password_ux_manager( ]; pddb_modal.key_event(keys); }), - Some(PwManagerOpcode::ModalDrop) => { // this guy should never quit, it's a core OS service + Some(PwManagerOpcode::ModalDrop) => { + // this guy should never quit, it's a core OS service panic!("Password modal for PDDB quit unexpectedly"); - }, + } Some(PwManagerOpcode::Quit) => { log::warn!("received quit on PDDB password UX renderer loop"); xous::return_scalar(msg.sender, 0).unwrap(); break; - }, + } None => { log::error!("couldn't convert opcode");