From 9778ea7fd2adb9a1445a10f3e7b15edd3b0fdd5b Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 29 Oct 2020 17:46:01 +0100 Subject: [PATCH 1/6] Introduce distinct integer types This PR does the following things: - Give incompatible representations for integers with different semantics: - `usize` is used for natural numbers for the public API. - `Nat` is used internally for natural numbers (essentially a stable `usize`). - `Word` is used for sequences of bits representing words in flash. - `Position` is used for word positions in the virtual storage. - Only use fixed size integers to preserve overflow behavior between targets. - Use little-endian representation instead of native representation for `Word`. Alternatives: - Run tests and fuzzing on 32-bits architecture (or some compatibility mode). This approach would have better readability than the current solution (less conversions at public API). However it would require additional setup and might not be viable long-term by restricting machines on which fuzzing is possible. - Accept the behavior difference for tests and fuzzing. This approach would also have better readability. However checking for arithmetic overflow (and other `usize` related concerns like memory size) is more important. --- libraries/persistent_store/src/format.rs | 247 +++++++++++------- .../src/{ => format}/bitfield.rs | 112 ++++---- libraries/persistent_store/src/lib.rs | 24 +- libraries/persistent_store/src/model.rs | 14 +- libraries/persistent_store/src/storage.rs | 4 + libraries/persistent_store/src/store.rs | 12 +- 6 files changed, 254 insertions(+), 159 deletions(-) rename libraries/persistent_store/src/{ => format}/bitfield.rs (79%) 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 } } From 233c15b20cd38faac6f55ea4add562d779537dae Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Mon, 2 Nov 2020 10:18:16 +0100 Subject: [PATCH 2/6] Add new store (without tests yet) --- libraries/persistent_store/src/format.rs | 3 - libraries/persistent_store/src/lib.rs | 4 +- libraries/persistent_store/src/store.rs | 1128 +++++++++++++++++++++- 3 files changed, 1130 insertions(+), 5 deletions(-) diff --git a/libraries/persistent_store/src/format.rs b/libraries/persistent_store/src/format.rs index a34e6ec..712d8cd 100644 --- a/libraries/persistent_store/src/format.rs +++ b/libraries/persistent_store/src/format.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO(ia0): Remove when the module is used. -#![allow(dead_code)] - #[macro_use] mod bitfield; diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index beb183b..27bdcd1 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -359,7 +359,9 @@ pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage}; #[cfg(feature = "std")] pub use self::model::{StoreModel, StoreOperation}; pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult}; -pub use self::store::{StoreError, StoreRatio, StoreResult, StoreUpdate}; +pub use self::store::{ + Store, StoreError, StoreHandle, StoreIter, StoreRatio, StoreResult, StoreUpdate, +}; /// Internal representation of natural numbers. /// diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index ab0d66f..ce34892 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -12,8 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Nat, StorageError}; +use crate::format::*; +#[cfg(feature = "std")] +pub use crate::model::{StoreModel, StoreOperation}; +#[cfg(feature = "std")] +pub use crate::BufferStorage; +use crate::{usize_to_nat, Nat, Storage, StorageError, StorageIndex}; use alloc::vec::Vec; +use core::cmp::{max, min, Ordering}; +#[cfg(feature = "std")] +use std::collections::HashSet; /// Errors returned by store operations. #[derive(Debug, PartialEq, Eq)] @@ -101,6 +109,38 @@ impl StoreRatio { } } +/// Safe pointer to an entry. +/// +/// A store handle stays valid at least until the next mutable operation. Store operations taking a +/// handle as argument always verify that the handle is still valid. +#[derive(Clone, Debug)] +pub struct StoreHandle { + /// The key of the entry. + key: Nat, + + /// The position of the entry. + pos: Position, + + /// The length in bytes of the value. + len: Nat, +} + +impl StoreHandle { + /// Returns the key of the entry. + pub fn get_key(&self) -> usize { + self.key as usize + } + + /// Returns the value of the entry. + /// + /// # Errors + /// + /// Returns `InvalidArgument` if the entry has been deleted or compacted. + pub fn get_value(&self, store: &Store) -> StoreResult> { + store.get_value(self) + } +} + /// Represents an update to the store as part of a transaction. #[derive(Clone, Debug)] pub enum StoreUpdate { @@ -128,3 +168,1089 @@ impl StoreUpdate { } } } + +/// Implements a store with a map interface over a storage. +#[derive(Clone)] +pub struct Store { + /// The underlying storage. + storage: S, + + /// The storage configuration. + format: Format, +} + +impl Store { + /// Resumes or initializes a store for a given storage. + /// + /// If the storage is completely erased, it is initialized. Otherwise, a possible interrupted + /// operation is recovered by being either completed or rolled-back. In case of error, the + /// storage ownership is returned. + /// + /// # Errors + /// + /// Returns `InvalidArgument` if the storage is not supported. + pub fn new(storage: S) -> Result, (StoreError, S)> { + let format = match Format::new(&storage) { + None => return Err((StoreError::InvalidArgument, storage)), + Some(x) => x, + }; + let mut store = Store { storage, format }; + if let Err(error) = store.recover() { + return Err((error, store.storage)); + } + Ok(store) + } + + /// Iterates over the entries. + pub fn iter<'a>(&'a self) -> StoreResult> { + StoreIter::new(self) + } + + /// Returns the current capacity in words. + /// + /// The capacity represents the size of what is stored. + pub fn capacity(&self) -> StoreResult { + let total = self.format.total_capacity(); + let mut used = 0; + let mut pos = self.head()?; + let end = pos + self.format.virt_size(); + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Tail => break, + ParsedEntry::Padding => (), + ParsedEntry::User(_) => used += pos - entry_pos, + _ => return Err(StoreError::InvalidStorage), + } + } + Ok(StoreRatio { used, total }) + } + + /// Returns the current lifetime in words. + /// + /// The lifetime represents the age of the storage. The limit is an over-approximation by at + /// most the maximum length of a value (the actual limit depends on the length of the prefix of + /// the first physical page once all its erase cycles have been used). + pub fn lifetime(&self) -> StoreResult { + let total = self.format.total_lifetime().get(); + let used = self.tail()?.get(); + Ok(StoreRatio { used, total }) + } + + /// Applies a sequence of updates as a single transaction. + /// + /// # Errors + /// + /// Returns `InvalidArgument` in the following circumstances: + /// - There are too many updates. + /// - The updates overlap, i.e. their keys are not disjoint. + /// - The updates are invalid, e.g. key out of bound or value too long. + pub fn transaction(&mut self, updates: &[StoreUpdate]) -> StoreResult<()> { + let count = usize_to_nat(updates.len()); + if count == 0 { + return Ok(()); + } + if count == 1 { + match updates[0] { + StoreUpdate::Insert { key, ref value } => return self.insert(key, value), + StoreUpdate::Remove { key } => return self.remove(key), + } + } + if count > self.format.max_updates() { + return Err(StoreError::InvalidArgument); + } + // Compute how much capacity (including transient) we need. + let mut sorted_keys = Vec::with_capacity(count as usize); + let mut word_capacity = 1 + count; + for update in updates { + let key = usize_to_nat(update.key()); + if key > self.format.max_key() { + return Err(StoreError::InvalidArgument); + } + if let Some(value) = update.value() { + let value_len = usize_to_nat(value.len()); + if value_len > self.format.max_value_len() { + return Err(StoreError::InvalidArgument); + } + word_capacity += self.format.bytes_to_words(value_len); + } + match sorted_keys.binary_search(&key) { + Ok(_) => return Err(StoreError::InvalidArgument), + Err(pos) => sorted_keys.insert(pos, key), + } + } + // Reserve the capacity. + self.reserve(word_capacity)?; + // Write the marker entry. + let marker = self.tail()?; + let entry = self.format.build_internal(InternalEntry::Marker { count }); + self.write_slice(marker, &entry)?; + self.init_page(marker, marker)?; + // Write the updates. + let mut tail = marker + 1; + for update in updates { + let length = match *update { + StoreUpdate::Insert { key, ref value } => { + let entry = self.format.build_user(usize_to_nat(key), value); + let word_size = self.format.word_size(); + let footer = usize_to_nat(entry.len()) / word_size - 1; + self.write_slice(tail, &entry[..(footer * word_size) as usize])?; + self.write_slice(tail + footer, &entry[(footer * word_size) as usize..])?; + footer + } + StoreUpdate::Remove { key } => { + let key = usize_to_nat(key); + let remove = self.format.build_internal(InternalEntry::Remove { key }); + self.write_slice(tail, &remove)?; + 0 + } + }; + self.init_page(tail, tail + length)?; + tail += 1 + length; + } + // Apply the transaction. + self.transaction_apply(&sorted_keys, marker) + } + + /// Removes multiple entries as part of a single transaction. + /// + /// Entries with a key larger or equal to `min_key` are deleted. + pub fn clear(&mut self, min_key: usize) -> StoreResult<()> { + let min_key = usize_to_nat(min_key); + if min_key > self.format.max_key() { + return Err(StoreError::InvalidArgument); + } + let clear = self.format.build_internal(InternalEntry::Clear { min_key }); + // We always have one word available. We can't use `reserve` because this is internal + // capacity, not user capacity. + while self.immediate_capacity()? < 1 { + self.compact()?; + } + let tail = self.tail()?; + self.write_slice(tail, &clear)?; + self.clear_delete(tail) + } + + /// Compacts the store once if needed. + /// + /// If the immediate capacity is at least `length` words, then nothing is modified. Otherwise, + /// one page is compacted. + pub fn prepare(&mut self, length: usize) -> Result<(), StoreError> { + if self.capacity()?.remaining() < length { + return Err(StoreError::NoCapacity); + } + if self.immediate_capacity()? < length as isize { + self.compact()?; + } + Ok(()) + } + + /// Recovers a possible interrupted operation. + /// + /// If the storage is completely erased, it is initialized. + pub fn recover(&mut self) -> StoreResult<()> { + self.recover_initialize()?; + self.recover_erase()?; + self.recover_compaction()?; + self.recover_operation()?; + Ok(()) + } + + /// Returns the value of an entry given its key. + pub fn find(&self, key: usize) -> StoreResult>> { + Ok(match self.find_handle(key)? { + None => None, + Some(handle) => Some(self.get_value(&handle)?), + }) + } + + /// Returns a handle to an entry given its key. + pub fn find_handle(&self, key: usize) -> StoreResult> { + let key = usize_to_nat(key); + for handle in self.iter()? { + let handle = handle?; + if handle.key == key { + return Ok(Some(handle)); + } + } + Ok(None) + } + + /// Inserts an entry in the store. + /// + /// If an entry for the same key is already present, it is replaced. + pub fn insert(&mut self, key: usize, value: &[u8]) -> StoreResult<()> { + // NOTE: This (and transaction) could take a position hint on the value to delete. + let key = usize_to_nat(key); + let value_len = usize_to_nat(value.len()); + if key > self.format.max_key() || value_len > self.format.max_value_len() { + return Err(StoreError::InvalidArgument); + } + let entry = self.format.build_user(key, value); + let entry_len = usize_to_nat(entry.len()); + self.reserve(entry_len / self.format.word_size())?; + let tail = self.tail()?; + let word_size = self.format.word_size(); + let footer = entry_len / word_size - 1; + self.write_slice(tail, &entry[..(footer * word_size) as usize])?; + self.write_slice(tail + footer, &entry[(footer * word_size) as usize..])?; + self.insert_init(tail, footer, key) + } + + /// Removes an entry given its key. + /// + /// This is not an error if there is no entry for this key. + pub fn remove(&mut self, key: usize) -> StoreResult<()> { + let key = usize_to_nat(key); + if key > self.format.max_key() { + return Err(StoreError::InvalidArgument); + } + self.delete_keys(&[key], self.tail()?) + } + + /// Removes an entry given a handle. + pub fn remove_handle(&mut self, handle: &StoreHandle) -> StoreResult<()> { + self.check_handle(handle)?; + self.delete_pos(handle.pos, self.format.bytes_to_words(handle.len)) + } + + /// Returns the maximum length in bytes of a value. + pub fn max_value_length(&self) -> usize { + self.format.max_value_len() as usize + } + + /// Returns the value of an entry given its handle. + fn get_value(&self, handle: &StoreHandle) -> StoreResult> { + self.check_handle(handle)?; + let mut pos = handle.pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::User(header) => { + let mut result = self.read_slice(handle.pos + 1, header.length); + if header.flipped { + let last_byte = result.len() - 1; + result[last_byte] = 0xff; + } + Ok(result) + } + ParsedEntry::Padding => Err(StoreError::InvalidArgument), + _ => Err(StoreError::InvalidStorage), + } + } + + /// Initializes the storage if completely erased or partially initialized. + fn recover_initialize(&mut self) -> StoreResult<()> { + let word_size = self.format.word_size(); + for page in 0..self.format.num_pages() { + let (init, rest) = self.read_page(page).split_at(word_size as usize); + if (page > 0 && !is_erased(init)) || !is_erased(rest) { + return Ok(()); + } + } + let index = self.format.index_init(0); + let init_info = self.format.build_init(InitInfo { + cycle: 0, + prefix: 0, + }); + self.storage_write_slice(index, &init_info) + } + + /// Recovers a possible compaction interrupted while erasing the page. + fn recover_erase(&mut self) -> StoreResult<()> { + let mut pos = self.get_extremum_page_head(Ordering::Greater)?; + let end = pos.next_page(&self.format); + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Internal(InternalEntry::Erase { .. }) => { + return self.compact_erase(entry_pos) + } + ParsedEntry::Padding | ParsedEntry::User(_) => (), + _ => break, + } + } + Ok(()) + } + + /// Recovers a possible compaction interrupted while copying the entries. + fn recover_compaction(&mut self) -> StoreResult<()> { + let head_page = self.head()?.page(&self.format); + match self.parse_compact(head_page)? { + WordState::Erased => Ok(()), + WordState::Partial => self.compact(), + WordState::Valid(_) => self.compact_copy(), + } + } + + /// Recover a possible interrupted operation which is not a compaction. + fn recover_operation(&mut self) -> StoreResult<()> { + let mut pos = self.head()?; + let mut prev_pos = pos; + let end = pos + self.format.virt_size(); + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Tail => break, + ParsedEntry::User(_) => (), + ParsedEntry::Padding => { + self.wipe_span(entry_pos + 1, pos - entry_pos - 1)?; + } + ParsedEntry::Internal(InternalEntry::Erase { .. }) => { + return Err(StoreError::InvalidStorage); + } + ParsedEntry::Internal(InternalEntry::Clear { .. }) => { + return self.clear_delete(entry_pos); + } + ParsedEntry::Internal(InternalEntry::Marker { .. }) => { + return self.recover_transaction(entry_pos, end); + } + ParsedEntry::Internal(InternalEntry::Remove { .. }) => { + self.set_padding(entry_pos)?; + } + ParsedEntry::Partial => { + return self.recover_wipe_partial(entry_pos, pos - entry_pos - 1); + } + ParsedEntry::PartialUser => { + return self.recover_delete_user(entry_pos, pos - entry_pos - 1); + } + } + prev_pos = entry_pos; + } + pos = prev_pos; + if let ParsedEntry::User(header) = self.parse_entry(&mut pos)? { + self.insert_init(prev_pos, pos - prev_pos - 1, header.key)?; + } + Ok(()) + } + + /// Recovers a possible interrupted transaction. + fn recover_transaction(&mut self, marker: Position, end: Position) -> StoreResult<()> { + let mut pos = marker; + let count = match self.parse_entry(&mut pos)? { + ParsedEntry::Internal(InternalEntry::Marker { count }) => count, + _ => return Err(StoreError::InvalidStorage), + }; + let sorted_keys = self.recover_transaction_keys(count, pos, end)?; + match usize_to_nat(sorted_keys.len()).cmp(&count) { + Ordering::Less => (), + Ordering::Equal => return self.transaction_apply(&sorted_keys, marker), + Ordering::Greater => return Err(StoreError::InvalidStorage), + } + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Tail => break, + ParsedEntry::Padding => (), + ParsedEntry::User(_) => { + self.delete_pos(entry_pos, pos - entry_pos - 1)?; + } + ParsedEntry::Internal(InternalEntry::Remove { .. }) => { + self.set_padding(entry_pos)?; + } + ParsedEntry::Partial => { + self.recover_wipe_partial(entry_pos, pos - entry_pos - 1)?; + break; + } + ParsedEntry::PartialUser => { + self.recover_delete_user(entry_pos, pos - entry_pos - 1)?; + break; + } + ParsedEntry::Internal(InternalEntry::Erase { .. }) + | ParsedEntry::Internal(InternalEntry::Clear { .. }) + | ParsedEntry::Internal(InternalEntry::Marker { .. }) => { + return Err(StoreError::InvalidStorage); + } + } + } + self.init_page(marker, marker)?; + self.set_padding(marker)?; + Ok(()) + } + + /// Returns the domain of a possible interrupted transaction. + /// + /// The domain is returned as a sorted list of keys. + fn recover_transaction_keys( + &mut self, + count: Nat, + mut pos: Position, + end: Position, + ) -> StoreResult> { + let mut sorted_keys = Vec::with_capacity(count as usize); + let mut prev_pos = pos; + while pos < end { + let entry_pos = pos; + let key = match self.parse_entry(&mut pos)? { + ParsedEntry::Tail + | ParsedEntry::Padding + | ParsedEntry::Partial + | ParsedEntry::PartialUser => break, + ParsedEntry::User(header) => header.key, + ParsedEntry::Internal(InternalEntry::Remove { key }) => key, + ParsedEntry::Internal(_) => return Err(StoreError::InvalidStorage), + }; + match sorted_keys.binary_search(&key) { + Ok(_) => return Err(StoreError::InvalidStorage), + Err(pos) => sorted_keys.insert(pos, key), + } + prev_pos = entry_pos; + } + pos = prev_pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::User(_) | ParsedEntry::Internal(InternalEntry::Remove { .. }) => { + let length = pos - prev_pos - 1; + self.init_page(prev_pos, prev_pos + length)?; + } + _ => (), + } + Ok(sorted_keys) + } + + /// Completes a possible partial entry wipe. + fn recover_wipe_partial(&mut self, pos: Position, length: Nat) -> StoreResult<()> { + self.wipe_span(pos + 1, length)?; + self.init_page(pos, pos + length)?; + self.set_padding(pos)?; + Ok(()) + } + + /// Completes a possible partial entry deletion. + fn recover_delete_user(&mut self, pos: Position, length: Nat) -> StoreResult<()> { + self.init_page(pos, pos + length)?; + self.delete_pos(pos, length) + } + + /// Checks that a handle still points in the current window. + /// + /// In particular, the handle has not been compacted. + fn check_handle(&self, handle: &StoreHandle) -> StoreResult<()> { + if handle.pos < self.head()? { + Err(StoreError::InvalidArgument) + } else { + Ok(()) + } + } + + /// Compacts the store as needed. + /// + /// If there is at least `length` words of remaining capacity, pages are compacted until that + /// amount is immediately available. + fn reserve(&mut self, length: Nat) -> Result<(), StoreError> { + if self.capacity()?.remaining() < length as usize { + return Err(StoreError::NoCapacity); + } + while self.immediate_capacity()? < length as isize { + self.compact()?; + } + Ok(()) + } + + /// Continues an entry insertion after it has been written. + fn insert_init(&mut self, pos: Position, length: Nat, key: Nat) -> StoreResult<()> { + self.init_page(pos, pos + length)?; + self.delete_keys(&[key], pos)?; + Ok(()) + } + + /// Compacts one page. + fn compact(&mut self) -> StoreResult<()> { + let head = self.head()?; + if head.cycle(&self.format) >= self.format.max_page_erases() { + return Err(StoreError::NoLifetime); + } + let tail = max(self.tail()?, head.next_page(&self.format)); + let index = self.format.index_compact(head.page(&self.format)); + let compact_info = self.format.build_compact(CompactInfo { tail: tail - head }); + self.storage_write_slice(index, &compact_info)?; + self.compact_copy() + } + + /// Continues a compaction after its compact page info has been written. + fn compact_copy(&mut self) -> StoreResult<()> { + let mut head = self.head()?; + let page = head.page(&self.format); + let end = head.next_page(&self.format); + let mut tail = match self.parse_compact(page)? { + WordState::Valid(CompactInfo { tail }) => head + tail, + _ => return Err(StoreError::InvalidStorage), + }; + if tail < end { + return Err(StoreError::InvalidStorage); + } + while head < end { + let pos = head; + match self.parse_entry(&mut head)? { + ParsedEntry::Tail => break, + ParsedEntry::User(_) => (), + _ => continue, + }; + let length = head - pos; + // We have to copy the slice for 2 reasons: + // 1. We would need to work around the lifetime. This is possible using unsafe. + // 2. We can't pass a flash slice to the kernel. This should get fixed with + // https://github.com/tock/tock/issues/1274. + let entry = self.read_slice(pos, length * self.format.word_size()); + self.write_slice(tail, &entry)?; + self.init_page(tail, tail + (length - 1))?; + tail += length; + } + let erase = self.format.build_internal(InternalEntry::Erase { page }); + self.write_slice(tail, &erase)?; + self.init_page(tail, tail)?; + self.compact_erase(tail) + } + + /// Continues a compaction after its erase entry has been written. + fn compact_erase(&mut self, erase: Position) -> StoreResult<()> { + let page = match self.parse_entry(&mut erase.clone())? { + ParsedEntry::Internal(InternalEntry::Erase { page }) => page, + _ => return Err(StoreError::InvalidStorage), + }; + self.storage_erase_page(page)?; + let head = self.head()?; + let pos = head.page_begin(&self.format); + self.wipe_span(pos, head - pos)?; + self.set_padding(erase)?; + Ok(()) + } + + /// Continues a transaction after it has been written. + fn transaction_apply(&mut self, sorted_keys: &[Nat], marker: Position) -> StoreResult<()> { + self.delete_keys(&sorted_keys, marker)?; + self.set_padding(marker)?; + let end = self.head()? + self.format.virt_size(); + let mut pos = marker + 1; + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Tail => break, + ParsedEntry::User(_) => (), + ParsedEntry::Internal(InternalEntry::Remove { .. }) => { + self.set_padding(entry_pos)? + } + _ => return Err(StoreError::InvalidStorage), + } + } + Ok(()) + } + + /// Continues a clear operation after its internal entry has been written. + fn clear_delete(&mut self, clear: Position) -> StoreResult<()> { + self.init_page(clear, clear)?; + let min_key = match self.parse_entry(&mut clear.clone())? { + ParsedEntry::Internal(InternalEntry::Clear { min_key }) => min_key, + _ => return Err(StoreError::InvalidStorage), + }; + let mut pos = self.head()?; + let end = pos + self.format.virt_size(); + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Internal(InternalEntry::Clear { .. }) if entry_pos == clear => break, + ParsedEntry::User(header) if header.key >= min_key => { + self.delete_pos(entry_pos, pos - entry_pos - 1)?; + } + ParsedEntry::Padding | ParsedEntry::User(_) => (), + _ => return Err(StoreError::InvalidStorage), + } + } + self.set_padding(clear)?; + Ok(()) + } + + /// Deletes a set of entries up to a certain position. + fn delete_keys(&mut self, sorted_keys: &[Nat], end: Position) -> StoreResult<()> { + let mut pos = self.head()?; + while pos < end { + let entry_pos = pos; + match self.parse_entry(&mut pos)? { + ParsedEntry::Tail => break, + ParsedEntry::User(header) if sorted_keys.binary_search(&header.key).is_ok() => { + self.delete_pos(entry_pos, pos - entry_pos - 1)?; + } + ParsedEntry::Padding | ParsedEntry::User(_) => (), + _ => return Err(StoreError::InvalidStorage), + } + } + Ok(()) + } + + /// Deletes the entry at a given position. + fn delete_pos(&mut self, pos: Position, length: Nat) -> StoreResult<()> { + self.set_deleted(pos)?; + self.wipe_span(pos + 1, length)?; + Ok(()) + } + + /// Writes the init info of a page between 2 positions if needed. + /// + /// The positions should designate the first and last word of an entry. The init info of the + /// highest page is written in any of the following conditions: + /// - The entry starts at the beginning of a virtual page. + /// - The entry spans 2 pages. + fn init_page(&mut self, first: Position, last: Position) -> StoreResult<()> { + debug_assert!(first <= last); + debug_assert!(last - first < self.format.virt_page_size()); + let new_first = if first.word(&self.format) == 0 { + first + } else if first.page(&self.format) == last.page(&self.format) { + return Ok(()); + } else { + last + 1 + }; + let page = new_first.page(&self.format); + if let WordState::Valid(_) = self.parse_init(page)? { + return Ok(()); + } + let index = self.format.index_init(page); + let init_info = self.format.build_init(InitInfo { + cycle: new_first.cycle(&self.format), + prefix: new_first.word(&self.format), + }); + self.storage_write_slice(index, &init_info)?; + Ok(()) + } + + /// Sets the padding bit of a user header. + fn set_padding(&mut self, pos: Position) -> StoreResult<()> { + let mut word = Word::from_slice(self.read_word(pos)); + self.format.set_padding(&mut word); + self.write_slice(pos, &word.as_slice())?; + Ok(()) + } + + /// Sets the deleted bit of a user header. + fn set_deleted(&mut self, pos: Position) -> StoreResult<()> { + let mut word = Word::from_slice(self.read_word(pos)); + self.format.set_deleted(&mut word); + self.write_slice(pos, &word.as_slice())?; + Ok(()) + } + + /// Wipes a slice of words. + fn wipe_span(&mut self, pos: Position, length: Nat) -> StoreResult<()> { + let length = (length * self.format.word_size()) as usize; + self.write_slice(pos, &vec![0x00; length]) + } + + /// Returns an extremum page. + /// + /// With `Greater` returns the most recent page (or the tail). With `Less` returns the oldest + /// page (or the head). + fn get_extremum_page_head(&self, ordering: Ordering) -> StoreResult { + let mut best = None; + for page in 0..self.format.num_pages() { + let init = match self.parse_init(page)? { + WordState::Valid(x) => x, + _ => continue, + }; + let pos = self.format.page_head(init, page); + if best.map_or(true, |x| pos.cmp(&x) == ordering) { + best = Some(pos); + } + } + // There is always at least one initialized page. + best.ok_or(StoreError::InvalidStorage) + } + + /// Returns the number of words that can be written without compaction. + /// + /// This can be temporarily negative during compaction. + fn immediate_capacity(&self) -> StoreResult { + let tail = self.tail()?; + let end = self.head()? + self.format.virt_size(); + Ok(if tail > end { + -((tail - end) as isize) + } else { + (end - tail) as isize + }) + } + + /// Returns the position of the first word in the store. + pub(crate) fn head(&self) -> StoreResult { + self.get_extremum_page_head(Ordering::Less) + } + + /// Returns one past the position of the last word in the store. + pub(crate) fn tail(&self) -> StoreResult { + let mut pos = self.get_extremum_page_head(Ordering::Greater)?; + let end = pos.next_page(&self.format); + while pos < end { + if let ParsedEntry::Tail = self.parse_entry(&mut pos)? { + break; + } + } + Ok(pos) + } + + /// Parses the entry at a given position. + /// + /// The position is updated to point to the next entry. + fn parse_entry(&self, pos: &mut Position) -> StoreResult { + let valid = match self.parse_word(*pos)? { + WordState::Erased | WordState::Partial => return Ok(self.parse_partial(pos)), + WordState::Valid(x) => x, + }; + Ok(match valid { + ParsedWord::Padding(Padding { length }) => { + *pos += 1 + length; + ParsedEntry::Padding + } + ParsedWord::Header(header) if header.length > self.format.max_value_len() => { + self.parse_partial(pos) + } + ParsedWord::Header(header) => { + let length = self.format.bytes_to_words(header.length); + let footer = match length { + 0 => None, + _ => Some(self.read_word(*pos + length)), + }; + if header.check(footer) { + if header.key > self.format.max_key() { + return Err(StoreError::InvalidStorage); + } + *pos += 1 + length; + ParsedEntry::User(header) + } else if footer.map_or(true, |x| is_erased(x)) { + self.parse_partial(pos) + } else { + *pos += 1 + length; + ParsedEntry::PartialUser + } + } + ParsedWord::Internal(internal) => { + *pos += 1; + ParsedEntry::Internal(internal) + } + }) + } + + /// Parses a possible partial user entry. + /// + /// This does look ahead past the header and possible erased word in case words near the end of + /// the entry were written first. + fn parse_partial(&self, pos: &mut Position) -> ParsedEntry { + let mut length = None; + for i in 0..self.format.max_prefix_len() { + if !is_erased(self.read_word(*pos + i)) { + length = Some(i); + } + } + match length { + None => ParsedEntry::Tail, + Some(length) => { + *pos += 1 + length; + ParsedEntry::Partial + } + } + } + + /// Parses the init info of a page. + fn parse_init(&self, page: Nat) -> StoreResult> { + let index = self.format.index_init(page); + let word = self.storage_read_slice(index, self.format.word_size()); + self.format.parse_init(Word::from_slice(word)) + } + + /// Parses the compact info of a page. + fn parse_compact(&self, page: Nat) -> StoreResult> { + let index = self.format.index_compact(page); + let word = self.storage_read_slice(index, self.format.word_size()); + self.format.parse_compact(Word::from_slice(word)) + } + + /// Parses a word from the virtual storage. + fn parse_word(&self, pos: Position) -> StoreResult> { + self.format + .parse_word(Word::from_slice(self.read_word(pos))) + } + + /// Reads a slice from the virtual storage. + /// + /// The slice may span 2 pages. + fn read_slice(&self, pos: Position, length: Nat) -> Vec { + let mut result = Vec::with_capacity(length as usize); + let index = pos.index(&self.format); + let max_length = self.format.page_size() - usize_to_nat(index.byte); + result.extend_from_slice(self.storage_read_slice(index, min(length, max_length))); + if length > max_length { + // The slice spans the next page. + let index = pos.next_page(&self.format).index(&self.format); + result.extend_from_slice(self.storage_read_slice(index, length - max_length)); + } + result + } + + /// Reads a word from the virtual storage. + fn read_word(&self, pos: Position) -> &[u8] { + self.storage_read_slice(pos.index(&self.format), self.format.word_size()) + } + + /// Reads a physical page. + fn read_page(&self, page: Nat) -> &[u8] { + let index = StorageIndex { + page: page as usize, + byte: 0, + }; + self.storage_read_slice(index, self.format.page_size()) + } + + /// Reads a slice from the physical storage. + fn storage_read_slice(&self, index: StorageIndex, length: Nat) -> &[u8] { + // The only possible failures are if the slice spans multiple pages. + self.storage.read_slice(index, length as usize).unwrap() + } + + /// Writes a slice to the virtual storage. + /// + /// The slice may span 2 pages. + fn write_slice(&mut self, pos: Position, value: &[u8]) -> StoreResult<()> { + let index = pos.index(&self.format); + let max_length = (self.format.page_size() - usize_to_nat(index.byte)) as usize; + self.storage_write_slice(index, &value[..min(value.len(), max_length)])?; + if value.len() > max_length { + // The slice spans the next page. + let index = pos.next_page(&self.format).index(&self.format); + self.storage_write_slice(index, &value[max_length..])?; + } + Ok(()) + } + + /// Writes a slice to the physical storage. + /// + /// Only starts writing the slice from the first word that needs to be written (because it + /// differs from the current value). + fn storage_write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StoreResult<()> { + let word_size = self.format.word_size(); + debug_assert!(usize_to_nat(value.len()) % word_size == 0); + let slice = self.storage.read_slice(index, value.len())?; + // Skip as many words that don't need to be written as possible. + for start in (0..usize_to_nat(value.len())).step_by(word_size as usize) { + if is_write_needed( + &slice[start as usize..][..word_size as usize], + &value[start as usize..][..word_size as usize], + )? { + // We must write the remaining slice. + let index = StorageIndex { + page: index.page, + byte: (usize_to_nat(index.byte) + start) as usize, + }; + let value = &value[start as usize..]; + self.storage.write_slice(index, value)?; + break; + } + } + // There is nothing remaining to write. + Ok(()) + } + + /// Erases a page if not already erased. + fn storage_erase_page(&mut self, page: Nat) -> StoreResult<()> { + if !is_erased(self.read_page(page)) { + self.storage.erase_page(page as usize)?; + } + Ok(()) + } +} + +// Those functions are not meant for production. +#[cfg(feature = "std")] +impl Store { + /// Returns the storage configuration. + pub fn format(&self) -> &Format { + &self.format + } + + /// Accesses the storage. + pub fn storage(&self) -> &BufferStorage { + &self.storage + } + + /// Accesses the storage mutably. + pub fn storage_mut(&mut self) -> &mut BufferStorage { + &mut self.storage + } + + /// Extracts the storage. + pub fn into_storage(self) -> BufferStorage { + self.storage + } + + /// Returns the value of a possibly deleted entry. + /// + /// If the value has been partially compacted, only return the non-compacted part. Returns an + /// empty value if it has been fully compacted. + pub fn inspect_value(&self, handle: &StoreHandle) -> Vec { + let head = self.head().unwrap(); + let length = self.format.bytes_to_words(handle.len); + if head <= handle.pos { + // The value has not been compacted. + self.read_slice(handle.pos + 1, handle.len) + } else if (handle.pos + length).page(&self.format) == head.page(&self.format) { + // The value has been partially compacted. + let next_page = handle.pos.next_page(&self.format); + let erased_len = (next_page - (handle.pos + 1)) * self.format.word_size(); + self.read_slice(next_page, handle.len - erased_len) + } else { + // The value has been fully compacted. + Vec::new() + } + } + + /// Applies an operation and returns the deleted entries. + /// + /// Note that the deleted entries are before any compaction, so they may point outside the + /// window. This is more expressive than returning the deleted entries after compaction since + /// compaction can be controlled independently. + pub fn apply(&mut self, operation: &StoreOperation) -> (Vec, StoreResult<()>) { + let deleted = |store: &Store, delete_key: &dyn Fn(usize) -> bool| { + store + .iter() + .unwrap() + .map(|x| x.unwrap()) + .filter(|x| delete_key(x.key as usize)) + .collect::>() + }; + match *operation { + StoreOperation::Transaction { ref updates } => { + let keys: HashSet = updates.iter().map(|x| x.key()).collect(); + let deleted = deleted(self, &|key| keys.contains(&key)); + (deleted, self.transaction(updates)) + } + StoreOperation::Clear { min_key } => { + let deleted = deleted(self, &|key| key >= min_key); + (deleted, self.clear(min_key)) + } + StoreOperation::Prepare { length } => (Vec::new(), self.prepare(length)), + } + } + + /// Initializes an erased storage as if it has been erased `cycle` times. + pub fn init_with_cycle(storage: &mut BufferStorage, cycle: usize) { + let format = Format::new(storage).unwrap(); + // Write the init info of the first page. + let mut index = format.index_init(0); + let init_info = format.build_init(InitInfo { + cycle: usize_to_nat(cycle), + prefix: 0, + }); + storage.write_slice(index, &init_info).unwrap(); + // Pad the first word of the page. This makes the store looks used, otherwise we may confuse + // it with a partially initialized store. + index.byte += 2 * format.word_size() as usize; + storage + .write_slice(index, &vec![0; format.word_size() as usize]) + .unwrap(); + // Inform the storage that the pages have been used. + for page in 0..storage.num_pages() { + storage.set_page_erases(page, cycle); + } + } +} + +/// Represents an entry in the store. +#[derive(Debug)] +enum ParsedEntry { + /// Padding entry. + /// + /// This can be any of the following: + /// - A deleted user entry. + /// - A completed internal entry. + /// - A wiped partial entry. + Padding, + + /// Non-deleted user entry. + User(Header), + + /// Internal entry. + Internal(InternalEntry), + + /// Partial user entry with non-erased footer. + /// + /// The fact that the footer is not erased and does not checksum, means that the header is + /// valid. In particular, the length is valid. We cannot wipe the entry because wiping the + /// footer may validate the checksum. So we mark the entry as deleted, which also wipes it. + PartialUser, + + /// Partial entry. + /// + /// This can be any of the following: + /// - A partial user entry with erased footer. + /// - A partial user entry with invalid length. + Partial, + + /// End of entries. + /// + /// In particular this is where the next entry will be written. + Tail, +} + +/// Iterates over the entries of a store. +pub struct StoreIter<'a, S: Storage> { + /// The store being iterated. + store: &'a Store, + + /// The position of the next entry. + pos: Position, + + /// Iteration stops when reaching this position. + end: Position, +} + +impl<'a, S: Storage> StoreIter<'a, S> { + /// Creates an iterator over the entries of a store. + fn new(store: &'a Store) -> StoreResult> { + let pos = store.head()?; + let end = pos + store.format.virt_size(); + Ok(StoreIter { store, pos, end }) + } +} + +impl<'a, S: Storage> StoreIter<'a, S> { + /// Returns the next entry and advances the iterator. + fn transposed_next(&mut self) -> StoreResult> { + if self.pos >= self.end { + return Ok(None); + } + while self.pos < self.end { + let entry_pos = self.pos; + match self.store.parse_entry(&mut self.pos)? { + ParsedEntry::Tail => break, + ParsedEntry::Padding => (), + ParsedEntry::User(header) => { + return Ok(Some(StoreHandle { + key: header.key, + pos: entry_pos, + len: header.length, + })) + } + _ => return Err(StoreError::InvalidStorage), + } + } + self.pos = self.end; + Ok(None) + } +} + +impl<'a, S: Storage> Iterator for StoreIter<'a, S> { + type Item = StoreResult; + + fn next(&mut self) -> Option> { + self.transposed_next().transpose() + } +} + +/// Returns whether 2 slices are different. +/// +/// Returns an error if `target` has a bit set to one for which `source` is set to zero. +fn is_write_needed(source: &[u8], target: &[u8]) -> StoreResult { + debug_assert_eq!(source.len(), target.len()); + for (&source, &target) in source.iter().zip(target.iter()) { + if source & target != target { + return Err(StoreError::InvalidStorage); + } + if source != target { + return Ok(true); + } + } + Ok(false) +} From b97758dd997cca91349ed244250946508ae472a7 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 3 Nov 2020 11:58:20 +0100 Subject: [PATCH 3/6] Do not use import ::* --- libraries/persistent_store/src/format.rs | 4 +++- libraries/persistent_store/src/store.rs | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/persistent_store/src/format.rs b/libraries/persistent_store/src/format.rs index 712d8cd..5935fd6 100644 --- a/libraries/persistent_store/src/format.rs +++ b/libraries/persistent_store/src/format.rs @@ -15,7 +15,9 @@ #[macro_use] mod bitfield; -use self::bitfield::*; +#[cfg(test)] +use self::bitfield::Length; +use self::bitfield::{count_zeros, num_bits, Bit, Checksum, ConstField, Field}; use crate::{usize_to_nat, Nat, Storage, StorageIndex, StoreError, StoreResult}; use alloc::vec::Vec; use core::cmp::min; diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index ce34892..b0d132c 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::format::*; +use crate::format::{ + is_erased, CompactInfo, Format, Header, InitInfo, InternalEntry, Padding, ParsedWord, Position, + Word, WordState, +}; #[cfg(feature = "std")] pub use crate::model::{StoreModel, StoreOperation}; #[cfg(feature = "std")] From d734da3a0e55c8d7c3f8e2d356823556a53a2eab Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 3 Nov 2020 12:39:38 +0100 Subject: [PATCH 4/6] Move transaction capacity formula to Format --- libraries/persistent_store/src/format.rs | 31 +++++++++++++++++++++- libraries/persistent_store/src/model.rs | 33 +++++------------------- libraries/persistent_store/src/store.rs | 9 +++---- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/libraries/persistent_store/src/format.rs b/libraries/persistent_store/src/format.rs index 5935fd6..7f79328 100644 --- a/libraries/persistent_store/src/format.rs +++ b/libraries/persistent_store/src/format.rs @@ -18,7 +18,7 @@ mod bitfield; #[cfg(test)] use self::bitfield::Length; use self::bitfield::{count_zeros, num_bits, Bit, Checksum, ConstField, Field}; -use crate::{usize_to_nat, Nat, Storage, StorageIndex, StoreError, StoreResult}; +use crate::{usize_to_nat, Nat, Storage, StorageIndex, StoreError, StoreResult, StoreUpdate}; use alloc::vec::Vec; use core::cmp::min; use core::convert::TryFrom; @@ -491,6 +491,35 @@ impl Format { HEADER_DELETED.set(word); } + /// Returns the capacity required by a transaction. + pub fn transaction_capacity(&self, updates: &[StoreUpdate]) -> Nat { + match updates.len() { + // An empty transaction doesn't consume anything. + 0 => 0, + // Transactions with a single update are optimized by avoiding a marker entry. + 1 => match &updates[0] { + StoreUpdate::Insert { value, .. } => self.entry_size(value), + // Transactions with a single update which is a removal don't consume anything. + StoreUpdate::Remove { .. } => 0, + }, + // A transaction consumes one word for the marker entry in addition to its updates. + _ => 1 + updates.iter().map(|x| self.update_capacity(x)).sum::(), + } + } + + /// Returns the capacity of an update. + fn update_capacity(&self, update: &StoreUpdate) -> Nat { + match update { + StoreUpdate::Insert { value, .. } => self.entry_size(value), + StoreUpdate::Remove { .. } => 1, + } + } + + /// Returns the size of a user entry given its value. + pub fn entry_size(&self, value: &[u8]) -> Nat { + 1 + self.bytes_to_words(usize_to_nat(value.len())) + } + /// Returns the minimum number of words to represent a given number of bytes. /// /// # Preconditions diff --git a/libraries/persistent_store/src/model.rs b/libraries/persistent_store/src/model.rs index d3c718a..b558f99 100644 --- a/libraries/persistent_store/src/model.rs +++ b/libraries/persistent_store/src/model.rs @@ -79,7 +79,12 @@ impl StoreModel { /// Returns the capacity according to the model. pub fn capacity(&self) -> StoreRatio { let total = self.format.total_capacity(); - let used = usize_to_nat(self.content.values().map(|x| self.entry_size(x)).sum()); + let used = usize_to_nat( + self.content + .values() + .map(|x| self.format.entry_size(x) as usize) + .sum(), + ); StoreRatio { used, total } } @@ -99,18 +104,7 @@ impl StoreModel { return Err(StoreError::InvalidArgument); } // Fail if there is not enough capacity. - let capacity = match updates.len() { - // An empty transaction doesn't consume anything. - 0 => 0, - // Transactions with a single update are optimized by avoiding a marker entry. - 1 => match &updates[0] { - StoreUpdate::Insert { value, .. } => self.entry_size(value), - // Transactions with a single update which is a removal don't consume anything. - StoreUpdate::Remove { .. } => 0, - }, - // A transaction consumes one word for the marker entry in addition to its updates. - _ => 1 + updates.iter().map(|x| self.update_size(x)).sum::(), - }; + let capacity = self.format.transaction_capacity(&updates) as usize; if self.capacity().remaining() < capacity { return Err(StoreError::NoCapacity); } @@ -145,19 +139,6 @@ impl StoreModel { Ok(()) } - /// Returns the word capacity of an update. - fn update_size(&self, update: &StoreUpdate) -> usize { - match update { - StoreUpdate::Insert { value, .. } => self.entry_size(value), - StoreUpdate::Remove { .. } => 1, - } - } - - /// Returns the word capacity of an entry. - fn entry_size(&self, value: &[u8]) -> usize { - 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() as usize diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index b0d132c..b7c034a 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -262,20 +262,17 @@ impl Store { if count > self.format.max_updates() { return Err(StoreError::InvalidArgument); } - // Compute how much capacity (including transient) we need. + // Check that the updates are valid. let mut sorted_keys = Vec::with_capacity(count as usize); - let mut word_capacity = 1 + count; for update in updates { let key = usize_to_nat(update.key()); if key > self.format.max_key() { return Err(StoreError::InvalidArgument); } if let Some(value) = update.value() { - let value_len = usize_to_nat(value.len()); - if value_len > self.format.max_value_len() { + if usize_to_nat(value.len()) > self.format.max_value_len() { return Err(StoreError::InvalidArgument); } - word_capacity += self.format.bytes_to_words(value_len); } match sorted_keys.binary_search(&key) { Ok(_) => return Err(StoreError::InvalidArgument), @@ -283,7 +280,7 @@ impl Store { } } // Reserve the capacity. - self.reserve(word_capacity)?; + self.reserve(self.format.transaction_capacity(updates))?; // Write the marker entry. let marker = self.tail()?; let entry = self.format.build_internal(InternalEntry::Marker { count }); From 410314b7807c416cc76c585488e54154ae5bf8f3 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 3 Nov 2020 12:54:30 +0100 Subject: [PATCH 5/6] Move transaction validity check to Format --- libraries/persistent_store/src/format.rs | 26 ++++++++++++++++++++++++ libraries/persistent_store/src/model.rs | 23 +++------------------ libraries/persistent_store/src/store.rs | 25 +++++------------------ 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/libraries/persistent_store/src/format.rs b/libraries/persistent_store/src/format.rs index 7f79328..1f87ef3 100644 --- a/libraries/persistent_store/src/format.rs +++ b/libraries/persistent_store/src/format.rs @@ -520,6 +520,32 @@ impl Format { 1 + self.bytes_to_words(usize_to_nat(value.len())) } + /// Checks if a transaction is valid and returns its sorted keys. + /// + /// Returns `None` if the transaction is invalid. + pub fn transaction_valid(&self, updates: &[StoreUpdate]) -> Option> { + if usize_to_nat(updates.len()) > self.max_updates() { + return None; + } + let mut sorted_keys = Vec::with_capacity(updates.len()); + for update in updates { + let key = usize_to_nat(update.key()); + if key > self.max_key() { + return None; + } + if let Some(value) = update.value() { + if usize_to_nat(value.len()) > self.max_value_len() { + return None; + } + } + match sorted_keys.binary_search(&key) { + Ok(_) => return None, + Err(pos) => sorted_keys.insert(pos, key), + } + } + Some(sorted_keys) + } + /// Returns the minimum number of words to represent a given number of bytes. /// /// # Preconditions diff --git a/libraries/persistent_store/src/model.rs b/libraries/persistent_store/src/model.rs index b558f99..c509b03 100644 --- a/libraries/persistent_store/src/model.rs +++ b/libraries/persistent_store/src/model.rs @@ -14,7 +14,7 @@ use crate::format::Format; use crate::{usize_to_nat, StoreError, StoreRatio, StoreResult, StoreUpdate}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; /// Models the mutable operations of a store. /// @@ -90,17 +90,8 @@ impl StoreModel { /// Applies a transaction. fn transaction(&mut self, updates: Vec) -> StoreResult<()> { - // Fail if too many updates. - if updates.len() > self.format.max_updates() as usize { - return Err(StoreError::InvalidArgument); - } - // Fail if an update is invalid. - if !updates.iter().all(|x| self.update_valid(x)) { - return Err(StoreError::InvalidArgument); - } - // Fail if updates are not disjoint, i.e. there are duplicate keys. - let keys: HashSet<_> = updates.iter().map(|x| x.key()).collect(); - if keys.len() != updates.len() { + // Fail if the transaction is invalid. + if self.format.transaction_valid(&updates).is_none() { return Err(StoreError::InvalidArgument); } // Fail if there is not enough capacity. @@ -138,12 +129,4 @@ impl StoreModel { } Ok(()) } - - /// Returns whether an update is valid. - fn update_valid(&self, update: &StoreUpdate) -> bool { - update.key() <= self.format.max_key() as usize - && update - .value() - .map_or(true, |x| x.len() <= self.format.max_value_len() as usize) - } } diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index b7c034a..3d38b9c 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -259,26 +259,11 @@ impl Store { StoreUpdate::Remove { key } => return self.remove(key), } } - if count > self.format.max_updates() { - return Err(StoreError::InvalidArgument); - } - // Check that the updates are valid. - let mut sorted_keys = Vec::with_capacity(count as usize); - for update in updates { - let key = usize_to_nat(update.key()); - if key > self.format.max_key() { - return Err(StoreError::InvalidArgument); - } - if let Some(value) = update.value() { - if usize_to_nat(value.len()) > self.format.max_value_len() { - return Err(StoreError::InvalidArgument); - } - } - match sorted_keys.binary_search(&key) { - Ok(_) => return Err(StoreError::InvalidArgument), - Err(pos) => sorted_keys.insert(pos, key), - } - } + // Get the sorted keys. Fail if the transaction is invalid. + let sorted_keys = match self.format.transaction_valid(updates) { + None => return Err(StoreError::InvalidArgument), + Some(x) => x, + }; // Reserve the capacity. self.reserve(self.format.transaction_capacity(updates))?; // Write the marker entry. From a024e642d3671a2a13cdc1ef75e0219c86c41692 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 3 Nov 2020 13:30:39 +0100 Subject: [PATCH 6/6] Return zero instead of negative immediate capacity --- libraries/persistent_store/src/store.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index 3d38b9c..725d91a 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -324,7 +324,7 @@ impl Store { if self.capacity()?.remaining() < length { return Err(StoreError::NoCapacity); } - if self.immediate_capacity()? < length as isize { + if self.immediate_capacity()? < usize_to_nat(length) { self.compact()?; } Ok(()) @@ -623,7 +623,7 @@ impl Store { if self.capacity()?.remaining() < length as usize { return Err(StoreError::NoCapacity); } - while self.immediate_capacity()? < length as isize { + while self.immediate_capacity()? < length { self.compact()?; } Ok(()) @@ -838,16 +838,10 @@ impl Store { } /// Returns the number of words that can be written without compaction. - /// - /// This can be temporarily negative during compaction. - fn immediate_capacity(&self) -> StoreResult { + fn immediate_capacity(&self) -> StoreResult { let tail = self.tail()?; let end = self.head()? + self.format.virt_size(); - Ok(if tail > end { - -((tail - end) as isize) - } else { - (end - tail) as isize - }) + Ok(end.get().saturating_sub(tail.get())) } /// Returns the position of the first word in the store.