diff --git a/libraries/persistent_store/src/format.rs b/libraries/persistent_store/src/format.rs index 1fa6521..a34e6ec 100644 --- a/libraries/persistent_store/src/format.rs +++ b/libraries/persistent_store/src/format.rs @@ -15,68 +15,104 @@ // TODO(ia0): Remove when the module is used. #![allow(dead_code)] -use crate::bitfield::*; -use crate::{Storage, StorageIndex, StoreError, StoreResult}; +#[macro_use] +mod bitfield; + +use self::bitfield::*; +use crate::{usize_to_nat, Nat, Storage, StorageIndex, StoreError, StoreResult}; use alloc::vec::Vec; use core::cmp::min; +use core::convert::TryFrom; +/// Internal representation of a word in flash. +/// +/// Currently, the store only supports storages where a word is 32 bits. type WORD = u32; +/// Abstract representation of a word in flash. +/// +/// This type is kept abstract to avoid possible confusion with `Nat` if they happen to have the +/// same representation. This is because they have different semantics, `Nat` represents natural +/// numbers while `Word` represents sequences of bits (and thus has no arithmetic). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Word(WORD); + +/// Byte slice representation of a word in flash. +/// +/// The slice is in little-endian representation. +pub type WordSlice = [u8; core::mem::size_of::()]; + +impl Word { + /// Converts a byte slice into a word. + /// + /// # Panics + /// + /// Panics if `slice.len() != WORD_SIZE`. + pub fn from_slice(slice: &[u8]) -> Word { + Word(WORD::from_le_bytes(::try_from(slice).unwrap())) + } + + /// Converts a word into a byte slice. + pub fn as_slice(self) -> WordSlice { + self.0.to_le_bytes() + } +} + /// Size of a word in bytes. /// /// Currently, the store only supports storages where a word is 4 bytes. -const WORD_SIZE: usize = core::mem::size_of::(); +const WORD_SIZE: Nat = core::mem::size_of::() as Nat; /// Minimum number of words per page. /// /// Currently, the store only supports storages where pages have at least 8 words. -const MIN_NUM_WORDS_PER_PAGE: usize = 8; +const MIN_NUM_WORDS_PER_PAGE: Nat = 8; /// Maximum size of a page in bytes. /// /// Currently, the store only supports storages where pages are between 8 and 1024 [words]. /// /// [words]: constant.WORD_SIZE.html -const MAX_PAGE_SIZE: usize = 4096; +const MAX_PAGE_SIZE: Nat = 4096; /// Maximum number of erase cycles. /// /// Currently, the store only supports storages where the maximum number of erase cycles fits on 16 /// bits. -const MAX_ERASE_CYCLE: usize = 65535; +const MAX_ERASE_CYCLE: Nat = 65535; /// Minimum number of pages. /// /// Currently, the store only supports storages with at least 3 pages. -const MIN_NUM_PAGES: usize = 3; +const MIN_NUM_PAGES: Nat = 3; /// Maximum page index. /// /// Thus the maximum number of pages is one more than this number. Currently, the store only /// supports storages where the number of pages is between 3 and 64. -const MAX_PAGE_INDEX: usize = 63; +const MAX_PAGE_INDEX: Nat = 63; /// Maximum key index. /// /// Thus the number of keys is one more than this number. Currently, the store only supports 4096 /// keys. -const MAX_KEY_INDEX: usize = 4095; +const MAX_KEY_INDEX: Nat = 4095; /// Maximum length in bytes of a user payload. /// /// Currently, the store only supports values smaller than 1024 bytes. -const MAX_VALUE_LEN: usize = 1023; +const MAX_VALUE_LEN: Nat = 1023; /// Maximum number of updates per transaction. /// /// Currently, the store only supports transactions with at most 31 updates. -const MAX_UPDATES: usize = 31; +const MAX_UPDATES: Nat = 31; /// Maximum number of words per virtual page. -const MAX_VIRT_PAGE_SIZE: usize = div_ceil(MAX_PAGE_SIZE, WORD_SIZE) - CONTENT_WORD; +const MAX_VIRT_PAGE_SIZE: Nat = div_ceil(MAX_PAGE_SIZE, WORD_SIZE) - CONTENT_WORD; /// Word with all bits set to one. -const ERASED_WORD: WORD = !(0 as WORD); +const ERASED_WORD: Word = Word(!(0 as WORD)); /// Helpers for a given storage configuration. #[derive(Clone, Debug)] @@ -88,7 +124,7 @@ pub struct Format { /// - Words divide a page evenly. /// - There are at least 8 words in a page. /// - There are at most `MAX_PAGE_SIZE` bytes in a page. - page_size: usize, + page_size: Nat, /// The number of pages in the storage. /// @@ -96,14 +132,14 @@ pub struct Format { /// /// - There are at least 3 pages. /// - There are at most `MAX_PAGE_INDEX + 1` pages. - num_pages: usize, + num_pages: Nat, /// The maximum number of times a page can be erased. /// /// # Invariant /// /// - A page can be erased at most `MAX_ERASE_CYCLE` times. - max_page_erases: usize, + max_page_erases: Nat, } impl Format { @@ -115,9 +151,9 @@ impl Format { pub fn new(storage: &S) -> Option { if Format::is_storage_supported(storage) { Some(Format { - page_size: storage.page_size(), - num_pages: storage.num_pages(), - max_page_erases: storage.max_page_erases(), + page_size: usize_to_nat(storage.page_size()), + num_pages: usize_to_nat(storage.num_pages()), + max_page_erases: usize_to_nat(storage.max_page_erases()), }) } else { None @@ -143,11 +179,11 @@ impl Format { /// [`MAX_PAGE_INDEX`]: constant.MAX_PAGE_INDEX.html /// [`MAX_ERASE_CYCLE`]: constant.MAX_ERASE_CYCLE.html fn is_storage_supported(storage: &S) -> bool { - let word_size = storage.word_size(); - let page_size = storage.page_size(); - let num_pages = storage.num_pages(); - let max_word_writes = storage.max_word_writes(); - let max_page_erases = storage.max_page_erases(); + let word_size = usize_to_nat(storage.word_size()); + let page_size = usize_to_nat(storage.page_size()); + let num_pages = usize_to_nat(storage.num_pages()); + let max_word_writes = usize_to_nat(storage.max_word_writes()); + let max_page_erases = usize_to_nat(storage.max_page_erases()); word_size == WORD_SIZE && page_size % word_size == 0 && (MIN_NUM_WORDS_PER_PAGE * word_size <= page_size && page_size <= MAX_PAGE_SIZE) @@ -157,45 +193,45 @@ impl Format { } /// The size of a word in bytes. - pub fn word_size(&self) -> usize { + pub fn word_size(&self) -> Nat { WORD_SIZE } /// The size of a page in bytes. /// /// We have `MIN_NUM_WORDS_PER_PAGE * self.word_size() <= self.page_size() <= MAX_PAGE_SIZE`. - pub fn page_size(&self) -> usize { + pub fn page_size(&self) -> Nat { self.page_size } /// The number of pages in the storage, denoted by `N`. /// /// We have `MIN_NUM_PAGES <= N <= MAX_PAGE_INDEX + 1`. - pub fn num_pages(&self) -> usize { + pub fn num_pages(&self) -> Nat { self.num_pages } /// The maximum page index. /// /// We have `2 <= self.max_page() <= MAX_PAGE_INDEX`. - pub fn max_page(&self) -> usize { + pub fn max_page(&self) -> Nat { self.num_pages - 1 } /// The maximum number of times a page can be erased, denoted by `E`. /// /// We have `E <= MAX_ERASE_CYCLE`. - pub fn max_page_erases(&self) -> usize { + pub fn max_page_erases(&self) -> Nat { self.max_page_erases } /// The maximum key. - pub fn max_key(&self) -> usize { + pub fn max_key(&self) -> Nat { MAX_KEY_INDEX } /// The maximum number of updates per transaction. - pub fn max_updates(&self) -> usize { + pub fn max_updates(&self) -> Nat { MAX_UPDATES } @@ -204,7 +240,7 @@ impl Format { /// A virtual page is stored in a physical page after the page header. /// /// We have `MIN_NUM_WORDS_PER_PAGE - 2 <= Q <= MAX_VIRT_PAGE_SIZE`. - pub fn virt_page_size(&self) -> usize { + pub fn virt_page_size(&self) -> Nat { self.page_size() / self.word_size() - CONTENT_WORD } @@ -212,7 +248,7 @@ impl Format { /// /// We have `(MIN_NUM_WORDS_PER_PAGE - 3) * self.word_size() <= self.max_value_len() <= /// MAX_VALUE_LEN`. - pub fn max_value_len(&self) -> usize { + pub fn max_value_len(&self) -> Nat { min( (self.virt_page_size() - 1) * self.word_size(), MAX_VALUE_LEN, @@ -225,7 +261,7 @@ impl Format { /// virtual page. This happens because entries may overlap up to 2 virtual pages. /// /// We have `MIN_NUM_WORDS_PER_PAGE - 3 <= M < Q`. - pub fn max_prefix_len(&self) -> usize { + pub fn max_prefix_len(&self) -> Nat { self.bytes_to_words(self.max_value_len()) } @@ -239,7 +275,7 @@ impl Format { /// - `V >= (N - 1) * (Q - 1) - (Q - 1)` from `V` definition /// /// [`M`]: struct.Format.html#method.max_prefix_len - pub fn virt_size(&self) -> usize { + pub fn virt_size(&self) -> Nat { (self.num_pages() - 1) * (self.virt_page_size() - 1) - self.max_prefix_len() } @@ -253,7 +289,7 @@ impl Format { /// - `(N - 2) * (Q - 1) - N = (N - 2) * (Q - 2) - 2` by calculus /// /// [`V`]: struct.Format.html#method.virt_size - pub fn total_capacity(&self) -> usize { + pub fn total_capacity(&self) -> Nat { // From the virtual capacity, we reserve N - 1 words for `Erase` entries and 1 word for a // `Clear` entry. self.virt_size() - self.num_pages() @@ -270,18 +306,21 @@ impl Format { /// /// The init info of the page must be provided to know where the first entry of the page /// starts. - pub fn page_head(&self, init: InitInfo, page: usize) -> Position { + pub fn page_head(&self, init: InitInfo, page: Nat) -> Position { Position::new(self, init.cycle, page, init.prefix) } /// Returns the storage index of the init info of a page. - pub fn index_init(&self, page: usize) -> StorageIndex { + pub fn index_init(&self, page: Nat) -> StorageIndex { let byte = INIT_WORD * self.word_size(); - StorageIndex { page, byte } + StorageIndex { + page: page as usize, + byte: byte as usize, + } } /// Parses the init info of a page from its storage representation. - pub fn parse_init(&self, word: WORD) -> StoreResult> { + pub fn parse_init(&self, word: Word) -> StoreResult> { Ok(if word == ERASED_WORD { WordState::Erased } else if WORD_CHECKSUM.get(word)? != 0 { @@ -297,22 +336,25 @@ impl Format { } /// Builds the storage representation of an init info. - pub fn build_init(&self, init: InitInfo) -> [u8; WORD_SIZE] { + pub fn build_init(&self, init: InitInfo) -> WordSlice { let mut word = ERASED_WORD; INIT_CYCLE.set(&mut word, init.cycle); INIT_PREFIX.set(&mut word, init.prefix); WORD_CHECKSUM.set(&mut word, 0); - word.to_ne_bytes() + word.as_slice() } /// Returns the storage index of the compact info of a page. - pub fn index_compact(&self, page: usize) -> StorageIndex { + pub fn index_compact(&self, page: Nat) -> StorageIndex { let byte = COMPACT_WORD * self.word_size(); - StorageIndex { page, byte } + StorageIndex { + page: page as usize, + byte: byte as usize, + } } /// Parses the compact info of a page from its storage representation. - pub fn parse_compact(&self, word: WORD) -> StoreResult> { + pub fn parse_compact(&self, word: Word) -> StoreResult> { Ok(if word == ERASED_WORD { WordState::Erased } else if WORD_CHECKSUM.get(word)? != 0 { @@ -327,15 +369,15 @@ impl Format { } /// Builds the storage representation of a compact info. - pub fn build_compact(&self, compact: CompactInfo) -> [u8; WORD_SIZE] { + pub fn build_compact(&self, compact: CompactInfo) -> WordSlice { let mut word = ERASED_WORD; COMPACT_TAIL.set(&mut word, compact.tail); WORD_CHECKSUM.set(&mut word, 0); - word.to_ne_bytes() + word.as_slice() } /// Builds the storage representation of an internal entry. - pub fn build_internal(&self, internal: InternalEntry) -> [u8; WORD_SIZE] { + pub fn build_internal(&self, internal: InternalEntry) -> WordSlice { let mut word = ERASED_WORD; match internal { InternalEntry::Erase { page } => { @@ -356,11 +398,11 @@ impl Format { } } WORD_CHECKSUM.set(&mut word, 0); - word.to_ne_bytes() + word.as_slice() } /// Parses the first word of an entry from its storage representation. - pub fn parse_word(&self, word: WORD) -> StoreResult> { + pub fn parse_word(&self, word: Word) -> StoreResult> { let valid = if ID_PADDING.check(word) { ParsedWord::Padding(Padding { length: 0 }) } else if ID_HEADER.check(word) { @@ -418,32 +460,35 @@ impl Format { } /// Builds the storage representation of a user entry. - pub fn build_user(&self, key: usize, value: &[u8]) -> Vec { - let length = value.len(); + pub fn build_user(&self, key: Nat, value: &[u8]) -> Vec { + let length = usize_to_nat(value.len()); let word_size = self.word_size(); let footer = self.bytes_to_words(length); - let mut result = vec![0xff; (1 + footer) * word_size]; - result[word_size..][..length].copy_from_slice(value); + let mut result = vec![0xff; ((1 + footer) * word_size) as usize]; + result[word_size as usize..][..length as usize].copy_from_slice(value); let mut word = ERASED_WORD; ID_HEADER.set(&mut word); - if footer > 0 && is_erased(&result[footer * word_size..]) { + if footer > 0 && is_erased(&result[(footer * word_size) as usize..]) { HEADER_FLIPPED.set(&mut word); *result.last_mut().unwrap() = 0x7f; } HEADER_LENGTH.set(&mut word, length); HEADER_KEY.set(&mut word, key); - HEADER_CHECKSUM.set(&mut word, count_zeros(&result[footer * word_size..])); - result[..word_size].copy_from_slice(&word.to_ne_bytes()); + HEADER_CHECKSUM.set( + &mut word, + count_zeros(&result[(footer * word_size) as usize..]), + ); + result[..word_size as usize].copy_from_slice(&word.as_slice()); result } /// Sets the padding bit in the first word of a user entry. - pub fn set_padding(&self, word: &mut WORD) { + pub fn set_padding(&self, word: &mut Word) { ID_PADDING.set(word); } /// Sets the deleted bit in the first word of a user entry. - pub fn set_deleted(&self, word: &mut WORD) { + pub fn set_deleted(&self, word: &mut Word) { HEADER_DELETED.set(word); } @@ -452,21 +497,21 @@ impl Format { /// # Preconditions /// /// - `bytes + self.word_size()` does not overflow. - pub fn bytes_to_words(&self, bytes: usize) -> usize { + pub fn bytes_to_words(&self, bytes: Nat) -> Nat { div_ceil(bytes, self.word_size()) } } /// The word index of the init info in a page. -const INIT_WORD: usize = 0; +const INIT_WORD: Nat = 0; /// The word index of the compact info in a page. -const COMPACT_WORD: usize = 1; +const COMPACT_WORD: Nat = 1; /// The word index of the content of a page. /// /// Since a page is at least 8 words, there is always at least 6 words of content. -const CONTENT_WORD: usize = 2; +const CONTENT_WORD: Nat = 2; /// The checksum for a single word. /// @@ -619,27 +664,36 @@ bitfield! { /// /// Then the position of a word is `(c*N + p)*Q + w`. This position monotonically increases and /// represents the consumed lifetime of the storage. +/// +/// This type is kept abstract to avoid possible confusion with `Nat` and `Word` if they happen to +/// have the same representation. Here is an overview of their semantics: +/// +/// | Name | Semantics | Arithmetic operations | Bit-wise operations | +/// | ---------- | --------------------------- | --------------------- | ------------------- | +/// | `Nat` | Natural numbers | Yes (no overflow) | No | +/// | `Word` | Word in flash | No | Yes | +/// | `Position` | Position in virtual storage | Yes (no overflow) | No | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Position(usize); +pub struct Position(Nat); -impl core::ops::Add for Position { +impl core::ops::Add for Position { type Output = Position; - fn add(self, delta: usize) -> Position { + fn add(self, delta: Nat) -> Position { Position(self.0 + delta) } } impl core::ops::Sub for Position { - type Output = usize; + type Output = Nat; - fn sub(self, base: Position) -> usize { + fn sub(self, base: Position) -> Nat { self.0 - base.0 } } -impl core::ops::AddAssign for Position { - fn add_assign(&mut self, delta: usize) { +impl core::ops::AddAssign for Position { + fn add_assign(&mut self, delta: Nat) { self.0 += delta; } } @@ -651,12 +705,12 @@ impl Position { /// - Its word index in its page. /// - Its page index in the storage. /// - The number of times that page was erased. - pub fn new(format: &Format, cycle: usize, page: usize, word: usize) -> Position { + pub fn new(format: &Format, cycle: Nat, page: Nat, word: Nat) -> Position { Position((cycle * format.num_pages() + page) * format.virt_page_size() + word) } /// Accesses the underlying position as a natural number. - pub fn get(self) -> usize { + pub fn get(self) -> Nat { self.0 } @@ -665,7 +719,10 @@ impl Position { let page = self.page(format); let word = CONTENT_WORD + self.word(format); let byte = word * format.word_size(); - StorageIndex { page, byte } + StorageIndex { + page: page as usize, + byte: byte as usize, + } } /// Returns the beginning of the current virtual page. @@ -681,17 +738,17 @@ impl Position { } /// Returns the number of times the current page was erased. - pub fn cycle(self, format: &Format) -> usize { + pub fn cycle(self, format: &Format) -> Nat { (self.0 / format.virt_page_size()) / format.num_pages() } /// Returns the current page index. - pub fn page(self, format: &Format) -> usize { + pub fn page(self, format: &Format) -> Nat { (self.0 / format.virt_page_size()) % format.num_pages() } /// Returns the current word index in the page. - pub fn word(self, format: &Format) -> usize { + pub fn word(self, format: &Format) -> Nat { self.0 % format.virt_page_size() } } @@ -711,16 +768,16 @@ pub enum WordState { /// Information for an initialized page. pub struct InitInfo { /// The number of times this page has been erased. - pub cycle: usize, + pub cycle: Nat, /// The word index of the first entry in this virtual page. - pub prefix: usize, + pub prefix: Nat, } /// Information for a page being compacted. pub struct CompactInfo { /// The distance in words between head and tail at compaction. - pub tail: usize, + pub tail: Nat, } /// The first word of an entry. @@ -740,7 +797,7 @@ pub enum ParsedWord { #[derive(Debug)] pub struct Padding { /// The number of following padding words after the first word of the padding entry. - pub length: usize, + pub length: Nat, } /// Header of a user entry. @@ -750,13 +807,13 @@ pub struct Header { pub flipped: bool, /// The length in bytes of the user data. - pub length: usize, + pub length: Nat, /// The key of the user entry. - pub key: usize, + pub key: Nat, /// The checksum of the user entry. - pub checksum: usize, + pub checksum: Nat, } impl Header { @@ -775,13 +832,13 @@ pub enum InternalEntry { /// Indicates that a page should be erased. Erase { /// The page to be erased. - page: usize, + page: Nat, }, /// Indicates that user entries with high key should be deleted. Clear { /// The minimum key a user entry should have to be deleted. - min_key: usize, + min_key: Nat, }, /// Marks the start of a transaction. @@ -790,7 +847,7 @@ pub enum InternalEntry { /// entries. Marker { /// The number of updates in the transaction. - count: usize, + count: Nat, }, /// Indicates that a user entry should be removed. @@ -799,7 +856,7 @@ pub enum InternalEntry { /// already atomic. Remove { /// The key of the user entry to be removed. - key: usize, + key: Nat, }, } @@ -815,7 +872,7 @@ pub fn is_erased(slice: &[u8]) -> bool { /// # Preconditions /// /// - `x + m` does not overflow. -const fn div_ceil(x: usize, m: usize) -> usize { +const fn div_ceil(x: Nat, m: Nat) -> Nat { (x + m - 1) / m } @@ -825,7 +882,7 @@ mod tests { #[test] fn size_of_format() { - assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::(), 12); } #[test] @@ -921,6 +978,18 @@ mod tests { assert_eq!(LEN_REMOVE.pos, 17); } + #[test] + fn word_from_slice_ok() { + assert_eq!( + Word::from_slice(&[0x04, 0x03, 0x02, 0x01]), + Word(0x01020304) + ); + assert_eq!( + Word::from_slice(&[0x1e, 0x3c, 0x78, 0xf0]), + Word(0xf0783c1e) + ); + } + #[test] fn is_erased_ok() { assert!(is_erased(&[])); diff --git a/libraries/persistent_store/src/bitfield.rs b/libraries/persistent_store/src/format/bitfield.rs similarity index 79% rename from libraries/persistent_store/src/bitfield.rs rename to libraries/persistent_store/src/format/bitfield.rs index 09e0ad7..2cffc4b 100644 --- a/libraries/persistent_store/src/bitfield.rs +++ b/libraries/persistent_store/src/format/bitfield.rs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Helps manipulate bit fields in 32-bits words. +//! Helps manipulate words as bit fields. +//! +//! This module assumes that `Word` and `Nat` are both represented as `u32`. -use crate::{StoreError, StoreResult}; +use crate::format::Word; +use crate::{Nat, StoreError, StoreResult}; /// Represents a bit field. /// @@ -25,16 +28,16 @@ use crate::{StoreError, StoreResult}; /// - The bit field must fit in a 32-bits word: `pos + len < 32`. pub struct Field { /// The position of the bit field. - pub pos: usize, + pub pos: Nat, /// The length of the bit field. - pub len: usize, + pub len: Nat, } impl Field { /// Reads the value of a bit field. - pub fn get(&self, word: u32) -> usize { - ((word >> self.pos) & self.mask()) as usize + pub fn get(&self, word: Word) -> Nat { + (word.0 >> self.pos) & self.mask() } /// Sets the value of a bit field. @@ -43,12 +46,11 @@ impl Field { /// /// - The value must fit in the bit field: `num_bits(value) < self.len`. /// - The value must only change bits from 1 to 0: `self.get(*word) & value == value`. - pub fn set(&self, word: &mut u32, value: usize) { - let value = value as u32; + pub fn set(&self, word: &mut Word, value: Nat) { debug_assert_eq!(value & self.mask(), value); let mask = !(self.mask() << self.pos); - *word &= mask | (value << self.pos); - debug_assert_eq!(self.get(*word), value as usize); + word.0 &= mask | (value << self.pos); + debug_assert_eq!(self.get(*word), value); } /// Returns a bit mask the length of the bit field. @@ -70,17 +72,17 @@ pub struct ConstField { pub field: Field, /// The constant value. - pub value: usize, + pub value: Nat, } impl ConstField { /// Checks that the bit field has its value. - pub fn check(&self, word: u32) -> bool { + pub fn check(&self, word: Word) -> bool { self.field.get(word) == self.value } /// Sets the bit field to its value. - pub fn set(&self, word: &mut u32) { + pub fn set(&self, word: &mut Word) { self.field.set(word, self.value); } } @@ -92,18 +94,18 @@ impl ConstField { /// - The bit must fit in a 32-bits word: `pos < 32`. pub struct Bit { /// The position of the bit. - pub pos: usize, + pub pos: Nat, } impl Bit { /// Returns whether the value of the bit is zero. - pub fn get(&self, word: u32) -> bool { - word & (1 << self.pos) == 0 + pub fn get(&self, word: Word) -> bool { + word.0 & (1 << self.pos) == 0 } /// Sets the value of the bit to zero. - pub fn set(&self, word: &mut u32) { - *word &= !(1 << self.pos); + pub fn set(&self, word: &mut Word) { + word.0 &= !(1 << self.pos); } } @@ -123,9 +125,9 @@ impl Checksum { /// # Errors /// /// Returns `InvalidStorage` if the external increment would be negative. - pub fn get(&self, word: u32) -> StoreResult { + pub fn get(&self, word: Word) -> StoreResult { let checksum = self.field.get(word); - let zeros = word.count_zeros() as usize - (self.field.len - checksum.count_ones() as usize); + let zeros = word.0.count_zeros() - (self.field.len - checksum.count_ones()); checksum .checked_sub(zeros) .ok_or(StoreError::InvalidStorage) @@ -139,9 +141,9 @@ impl Checksum { /// self.field.mask()`. /// - The checksum value should fit in the checksum bit field: `num_bits(word.count_zeros() + /// value) < self.field.len`. - pub fn set(&self, word: &mut u32, value: usize) { - debug_assert_eq!(self.field.get(*word), self.field.mask() as usize); - self.field.set(word, word.count_zeros() as usize + value); + pub fn set(&self, word: &mut Word, value: Nat) { + debug_assert_eq!(self.field.get(*word), self.field.mask()); + self.field.set(word, word.0.count_zeros() + value); } } @@ -153,7 +155,7 @@ impl Checksum { #[cfg(any(doc, test))] pub struct Length { /// The position of the next available bit. - pub pos: usize, + pub pos: Nat, } /// Helps defining contiguous bit fields. @@ -266,13 +268,13 @@ macro_rules! bitfield_impl { } /// Counts the number of bits equal to zero in a byte slice. -pub fn count_zeros(slice: &[u8]) -> usize { - slice.iter().map(|&x| x.count_zeros() as usize).sum() +pub fn count_zeros(slice: &[u8]) -> Nat { + slice.iter().map(|&x| x.count_zeros()).sum() } /// Returns the number of bits necessary to represent a number. -pub const fn num_bits(x: usize) -> usize { - 8 * core::mem::size_of::() - x.leading_zeros() as usize +pub const fn num_bits(x: Nat) -> Nat { + 8 * core::mem::size_of::() as Nat - x.leading_zeros() } #[cfg(test)] @@ -282,14 +284,14 @@ mod tests { #[test] fn field_ok() { let field = Field { pos: 3, len: 5 }; - assert_eq!(field.get(0x00000000), 0); - assert_eq!(field.get(0x00000007), 0); - assert_eq!(field.get(0x00000008), 1); - assert_eq!(field.get(0x000000f8), 0x1f); - assert_eq!(field.get(0x0000ff37), 6); - let mut word = 0xffffffff; + assert_eq!(field.get(Word(0x00000000)), 0); + assert_eq!(field.get(Word(0x00000007)), 0); + assert_eq!(field.get(Word(0x00000008)), 1); + assert_eq!(field.get(Word(0x000000f8)), 0x1f); + assert_eq!(field.get(Word(0x0000ff37)), 6); + let mut word = Word(0xffffffff); field.set(&mut word, 3); - assert_eq!(word, 0xffffff1f); + assert_eq!(word, Word(0xffffff1f)); } #[test] @@ -298,25 +300,25 @@ mod tests { field: Field { pos: 3, len: 5 }, value: 9, }; - assert!(!field.check(0x00000000)); - assert!(!field.check(0x0000ffff)); - assert!(field.check(0x00000048)); - assert!(field.check(0x0000ff4f)); - let mut word = 0xffffffff; + assert!(!field.check(Word(0x00000000))); + assert!(!field.check(Word(0x0000ffff))); + assert!(field.check(Word(0x00000048))); + assert!(field.check(Word(0x0000ff4f))); + let mut word = Word(0xffffffff); field.set(&mut word); - assert_eq!(word, 0xffffff4f); + assert_eq!(word, Word(0xffffff4f)); } #[test] fn bit_ok() { let bit = Bit { pos: 3 }; - assert!(bit.get(0x00000000)); - assert!(bit.get(0xfffffff7)); - assert!(!bit.get(0x00000008)); - assert!(!bit.get(0xffffffff)); - let mut word = 0xffffffff; + assert!(bit.get(Word(0x00000000))); + assert!(bit.get(Word(0xfffffff7))); + assert!(!bit.get(Word(0x00000008))); + assert!(!bit.get(Word(0xffffffff))); + let mut word = Word(0xffffffff); bit.set(&mut word); - assert_eq!(word, 0xfffffff7); + assert_eq!(word, Word(0xfffffff7)); } #[test] @@ -324,15 +326,15 @@ mod tests { let field = Checksum { field: Field { pos: 3, len: 5 }, }; - assert_eq!(field.get(0x00000000), Err(StoreError::InvalidStorage)); - assert_eq!(field.get(0xffffffff), Ok(31)); - assert_eq!(field.get(0xffffff07), Ok(0)); - assert_eq!(field.get(0xffffff0f), Ok(1)); - assert_eq!(field.get(0x00ffff67), Ok(4)); - assert_eq!(field.get(0x7fffff07), Err(StoreError::InvalidStorage)); - let mut word = 0x0fffffff; + assert_eq!(field.get(Word(0x00000000)), Err(StoreError::InvalidStorage)); + assert_eq!(field.get(Word(0xffffffff)), Ok(31)); + assert_eq!(field.get(Word(0xffffff07)), Ok(0)); + assert_eq!(field.get(Word(0xffffff0f)), Ok(1)); + assert_eq!(field.get(Word(0x00ffff67)), Ok(4)); + assert_eq!(field.get(Word(0x7fffff07)), Err(StoreError::InvalidStorage)); + let mut word = Word(0x0fffffff); field.set(&mut word, 4); - assert_eq!(word, 0x0fffff47); + assert_eq!(word, Word(0x0fffff47)); } #[test] diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index f1c1653..beb183b 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -348,8 +348,6 @@ #[macro_use] extern crate alloc; -#[macro_use] -mod bitfield; mod buffer; mod format; #[cfg(feature = "std")] @@ -362,3 +360,25 @@ pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage}; pub use self::model::{StoreModel, StoreOperation}; pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult}; pub use self::store::{StoreError, StoreRatio, StoreResult, StoreUpdate}; + +/// Internal representation of natural numbers. +/// +/// In Rust natural numbers are represented as `usize`. However, internally we represent them as +/// `u32`. This is done to preserve semantics across different targets. This is useful when tests +/// run with `usize = u64` while the actual target has `usize = u32`. +/// +/// To avoid too many conversions between `usize` and `Nat` which are necessary when interfacing +/// with Rust, `usize` is used instead of `Nat` in code meant only for tests. +/// +/// Currently, the store only supports targets with `usize = u32`. +type Nat = u32; + +/// Returns the internal representation of a Rust natural number. +/// +/// # Panics +/// +/// Panics if the conversion overflows. +fn usize_to_nat(x: usize) -> Nat { + use core::convert::TryFrom; + Nat::try_from(x).unwrap() +} diff --git a/libraries/persistent_store/src/model.rs b/libraries/persistent_store/src/model.rs index 2677265..d3c718a 100644 --- a/libraries/persistent_store/src/model.rs +++ b/libraries/persistent_store/src/model.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::format::Format; -use crate::{StoreError, StoreRatio, StoreResult, StoreUpdate}; +use crate::{usize_to_nat, StoreError, StoreRatio, StoreResult, StoreUpdate}; use std::collections::{HashMap, HashSet}; /// Models the mutable operations of a store. @@ -79,14 +79,14 @@ impl StoreModel { /// Returns the capacity according to the model. pub fn capacity(&self) -> StoreRatio { let total = self.format.total_capacity(); - let used: usize = self.content.values().map(|x| self.entry_size(x)).sum(); + let used = usize_to_nat(self.content.values().map(|x| self.entry_size(x)).sum()); StoreRatio { used, total } } /// Applies a transaction. fn transaction(&mut self, updates: Vec) -> StoreResult<()> { // Fail if too many updates. - if updates.len() > self.format.max_updates() { + if updates.len() > self.format.max_updates() as usize { return Err(StoreError::InvalidArgument); } // Fail if an update is invalid. @@ -130,7 +130,7 @@ impl StoreModel { /// Applies a clear operation. fn clear(&mut self, min_key: usize) -> StoreResult<()> { - if min_key > self.format.max_key() { + if min_key > self.format.max_key() as usize { return Err(StoreError::InvalidArgument); } self.content.retain(|&k, _| k < min_key); @@ -155,14 +155,14 @@ impl StoreModel { /// Returns the word capacity of an entry. fn entry_size(&self, value: &[u8]) -> usize { - 1 + self.format.bytes_to_words(value.len()) + 1 + self.format.bytes_to_words(usize_to_nat(value.len())) as usize } /// Returns whether an update is valid. fn update_valid(&self, update: &StoreUpdate) -> bool { - update.key() <= self.format.max_key() + update.key() <= self.format.max_key() as usize && update .value() - .map_or(true, |x| x.len() <= self.format.max_value_len()) + .map_or(true, |x| x.len() <= self.format.max_value_len() as usize) } } diff --git a/libraries/persistent_store/src/storage.rs b/libraries/persistent_store/src/storage.rs index 55df177..becd900 100644 --- a/libraries/persistent_store/src/storage.rs +++ b/libraries/persistent_store/src/storage.rs @@ -37,9 +37,13 @@ pub type StorageResult = Result; /// Abstracts a flash storage. pub trait Storage { /// The size of a word in bytes. + /// + /// A word is the smallest unit of writable flash. fn word_size(&self) -> usize; /// The size of a page in bytes. + /// + /// A page is the smallest unit of erasable flash. fn page_size(&self) -> usize; /// The number of pages in the storage. diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index 473310b..ab0d66f 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::StorageError; +use crate::{Nat, StorageError}; use alloc::vec::Vec; /// Errors returned by store operations. @@ -78,26 +78,26 @@ pub type StoreResult = Result; #[derive(Copy, Clone, PartialEq, Eq)] pub struct StoreRatio { /// How much of the metric is used. - pub(crate) used: usize, + pub(crate) used: Nat, /// How much of the metric can be used at most. - pub(crate) total: usize, + pub(crate) total: Nat, } impl StoreRatio { /// How much of the metric is used. pub fn used(self) -> usize { - self.used + self.used as usize } /// How much of the metric can be used at most. pub fn total(self) -> usize { - self.total + self.total as usize } /// How much of the metric is remaining. pub fn remaining(self) -> usize { - self.total - self.used + (self.total - self.used) as usize } }