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.
This commit is contained in:
Julien Cretin
2020-10-29 17:46:01 +01:00
parent dea28f622f
commit 9778ea7fd2
6 changed files with 254 additions and 159 deletions

View File

@@ -15,68 +15,104 @@
// TODO(ia0): Remove when the module is used. // TODO(ia0): Remove when the module is used.
#![allow(dead_code)] #![allow(dead_code)]
use crate::bitfield::*; #[macro_use]
use crate::{Storage, StorageIndex, StoreError, StoreResult}; mod bitfield;
use self::bitfield::*;
use crate::{usize_to_nat, Nat, Storage, StorageIndex, StoreError, StoreResult};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp::min; 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; 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::<WORD>()];
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(<WordSlice>::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. /// Size of a word in bytes.
/// ///
/// Currently, the store only supports storages where a word is 4 bytes. /// Currently, the store only supports storages where a word is 4 bytes.
const WORD_SIZE: usize = core::mem::size_of::<WORD>(); const WORD_SIZE: Nat = core::mem::size_of::<WORD>() as Nat;
/// Minimum number of words per page. /// Minimum number of words per page.
/// ///
/// Currently, the store only supports storages where pages have at least 8 words. /// 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. /// Maximum size of a page in bytes.
/// ///
/// Currently, the store only supports storages where pages are between 8 and 1024 [words]. /// Currently, the store only supports storages where pages are between 8 and 1024 [words].
/// ///
/// [words]: constant.WORD_SIZE.html /// [words]: constant.WORD_SIZE.html
const MAX_PAGE_SIZE: usize = 4096; const MAX_PAGE_SIZE: Nat = 4096;
/// Maximum number of erase cycles. /// Maximum number of erase cycles.
/// ///
/// Currently, the store only supports storages where the maximum number of erase cycles fits on 16 /// Currently, the store only supports storages where the maximum number of erase cycles fits on 16
/// bits. /// bits.
const MAX_ERASE_CYCLE: usize = 65535; const MAX_ERASE_CYCLE: Nat = 65535;
/// Minimum number of pages. /// Minimum number of pages.
/// ///
/// Currently, the store only supports storages with at least 3 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. /// Maximum page index.
/// ///
/// Thus the maximum number of pages is one more than this number. Currently, the store only /// 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. /// 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. /// Maximum key index.
/// ///
/// Thus the number of keys is one more than this number. Currently, the store only supports 4096 /// Thus the number of keys is one more than this number. Currently, the store only supports 4096
/// keys. /// keys.
const MAX_KEY_INDEX: usize = 4095; const MAX_KEY_INDEX: Nat = 4095;
/// Maximum length in bytes of a user payload. /// Maximum length in bytes of a user payload.
/// ///
/// Currently, the store only supports values smaller than 1024 bytes. /// 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. /// Maximum number of updates per transaction.
/// ///
/// Currently, the store only supports transactions with at most 31 updates. /// 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. /// 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. /// 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. /// Helpers for a given storage configuration.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -88,7 +124,7 @@ pub struct Format {
/// - Words divide a page evenly. /// - Words divide a page evenly.
/// - There are at least 8 words in a page. /// - There are at least 8 words in a page.
/// - There are at most `MAX_PAGE_SIZE` bytes 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. /// The number of pages in the storage.
/// ///
@@ -96,14 +132,14 @@ pub struct Format {
/// ///
/// - There are at least 3 pages. /// - There are at least 3 pages.
/// - There are at most `MAX_PAGE_INDEX + 1` 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. /// The maximum number of times a page can be erased.
/// ///
/// # Invariant /// # Invariant
/// ///
/// - A page can be erased at most `MAX_ERASE_CYCLE` times. /// - A page can be erased at most `MAX_ERASE_CYCLE` times.
max_page_erases: usize, max_page_erases: Nat,
} }
impl Format { impl Format {
@@ -115,9 +151,9 @@ impl Format {
pub fn new<S: Storage>(storage: &S) -> Option<Format> { pub fn new<S: Storage>(storage: &S) -> Option<Format> {
if Format::is_storage_supported(storage) { if Format::is_storage_supported(storage) {
Some(Format { Some(Format {
page_size: storage.page_size(), page_size: usize_to_nat(storage.page_size()),
num_pages: storage.num_pages(), num_pages: usize_to_nat(storage.num_pages()),
max_page_erases: storage.max_page_erases(), max_page_erases: usize_to_nat(storage.max_page_erases()),
}) })
} else { } else {
None None
@@ -143,11 +179,11 @@ impl Format {
/// [`MAX_PAGE_INDEX`]: constant.MAX_PAGE_INDEX.html /// [`MAX_PAGE_INDEX`]: constant.MAX_PAGE_INDEX.html
/// [`MAX_ERASE_CYCLE`]: constant.MAX_ERASE_CYCLE.html /// [`MAX_ERASE_CYCLE`]: constant.MAX_ERASE_CYCLE.html
fn is_storage_supported<S: Storage>(storage: &S) -> bool { fn is_storage_supported<S: Storage>(storage: &S) -> bool {
let word_size = storage.word_size(); let word_size = usize_to_nat(storage.word_size());
let page_size = storage.page_size(); let page_size = usize_to_nat(storage.page_size());
let num_pages = storage.num_pages(); let num_pages = usize_to_nat(storage.num_pages());
let max_word_writes = storage.max_word_writes(); let max_word_writes = usize_to_nat(storage.max_word_writes());
let max_page_erases = storage.max_page_erases(); let max_page_erases = usize_to_nat(storage.max_page_erases());
word_size == WORD_SIZE word_size == WORD_SIZE
&& page_size % word_size == 0 && page_size % word_size == 0
&& (MIN_NUM_WORDS_PER_PAGE * word_size <= page_size && page_size <= MAX_PAGE_SIZE) && (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. /// The size of a word in bytes.
pub fn word_size(&self) -> usize { pub fn word_size(&self) -> Nat {
WORD_SIZE WORD_SIZE
} }
/// The size of a page in bytes. /// The size of a page in bytes.
/// ///
/// We have `MIN_NUM_WORDS_PER_PAGE * self.word_size() <= self.page_size() <= MAX_PAGE_SIZE`. /// 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 self.page_size
} }
/// The number of pages in the storage, denoted by `N`. /// The number of pages in the storage, denoted by `N`.
/// ///
/// We have `MIN_NUM_PAGES <= N <= MAX_PAGE_INDEX + 1`. /// 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 self.num_pages
} }
/// The maximum page index. /// The maximum page index.
/// ///
/// We have `2 <= self.max_page() <= MAX_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 self.num_pages - 1
} }
/// The maximum number of times a page can be erased, denoted by `E`. /// The maximum number of times a page can be erased, denoted by `E`.
/// ///
/// We have `E <= MAX_ERASE_CYCLE`. /// We have `E <= MAX_ERASE_CYCLE`.
pub fn max_page_erases(&self) -> usize { pub fn max_page_erases(&self) -> Nat {
self.max_page_erases self.max_page_erases
} }
/// The maximum key. /// The maximum key.
pub fn max_key(&self) -> usize { pub fn max_key(&self) -> Nat {
MAX_KEY_INDEX MAX_KEY_INDEX
} }
/// The maximum number of updates per transaction. /// The maximum number of updates per transaction.
pub fn max_updates(&self) -> usize { pub fn max_updates(&self) -> Nat {
MAX_UPDATES MAX_UPDATES
} }
@@ -204,7 +240,7 @@ impl Format {
/// A virtual page is stored in a physical page after the page header. /// 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`. /// 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 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() <= /// We have `(MIN_NUM_WORDS_PER_PAGE - 3) * self.word_size() <= self.max_value_len() <=
/// MAX_VALUE_LEN`. /// MAX_VALUE_LEN`.
pub fn max_value_len(&self) -> usize { pub fn max_value_len(&self) -> Nat {
min( min(
(self.virt_page_size() - 1) * self.word_size(), (self.virt_page_size() - 1) * self.word_size(),
MAX_VALUE_LEN, MAX_VALUE_LEN,
@@ -225,7 +261,7 @@ impl Format {
/// virtual page. This happens because entries may overlap up to 2 virtual pages. /// virtual page. This happens because entries may overlap up to 2 virtual pages.
/// ///
/// We have `MIN_NUM_WORDS_PER_PAGE - 3 <= M < Q`. /// 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()) self.bytes_to_words(self.max_value_len())
} }
@@ -239,7 +275,7 @@ impl Format {
/// - `V >= (N - 1) * (Q - 1) - (Q - 1)` from `V` definition /// - `V >= (N - 1) * (Q - 1) - (Q - 1)` from `V` definition
/// ///
/// [`M`]: struct.Format.html#method.max_prefix_len /// [`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() (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 /// - `(N - 2) * (Q - 1) - N = (N - 2) * (Q - 2) - 2` by calculus
/// ///
/// [`V`]: struct.Format.html#method.virt_size /// [`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 // From the virtual capacity, we reserve N - 1 words for `Erase` entries and 1 word for a
// `Clear` entry. // `Clear` entry.
self.virt_size() - self.num_pages() 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 /// The init info of the page must be provided to know where the first entry of the page
/// starts. /// 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) Position::new(self, init.cycle, page, init.prefix)
} }
/// Returns the storage index of the init info of a page. /// 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(); 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. /// Parses the init info of a page from its storage representation.
pub fn parse_init(&self, word: WORD) -> StoreResult<WordState<InitInfo>> { pub fn parse_init(&self, word: Word) -> StoreResult<WordState<InitInfo>> {
Ok(if word == ERASED_WORD { Ok(if word == ERASED_WORD {
WordState::Erased WordState::Erased
} else if WORD_CHECKSUM.get(word)? != 0 { } else if WORD_CHECKSUM.get(word)? != 0 {
@@ -297,22 +336,25 @@ impl Format {
} }
/// Builds the storage representation of an init info. /// 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; let mut word = ERASED_WORD;
INIT_CYCLE.set(&mut word, init.cycle); INIT_CYCLE.set(&mut word, init.cycle);
INIT_PREFIX.set(&mut word, init.prefix); INIT_PREFIX.set(&mut word, init.prefix);
WORD_CHECKSUM.set(&mut word, 0); WORD_CHECKSUM.set(&mut word, 0);
word.to_ne_bytes() word.as_slice()
} }
/// Returns the storage index of the compact info of a page. /// 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(); 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. /// Parses the compact info of a page from its storage representation.
pub fn parse_compact(&self, word: WORD) -> StoreResult<WordState<CompactInfo>> { pub fn parse_compact(&self, word: Word) -> StoreResult<WordState<CompactInfo>> {
Ok(if word == ERASED_WORD { Ok(if word == ERASED_WORD {
WordState::Erased WordState::Erased
} else if WORD_CHECKSUM.get(word)? != 0 { } else if WORD_CHECKSUM.get(word)? != 0 {
@@ -327,15 +369,15 @@ impl Format {
} }
/// Builds the storage representation of a compact info. /// 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; let mut word = ERASED_WORD;
COMPACT_TAIL.set(&mut word, compact.tail); COMPACT_TAIL.set(&mut word, compact.tail);
WORD_CHECKSUM.set(&mut word, 0); WORD_CHECKSUM.set(&mut word, 0);
word.to_ne_bytes() word.as_slice()
} }
/// Builds the storage representation of an internal entry. /// 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; let mut word = ERASED_WORD;
match internal { match internal {
InternalEntry::Erase { page } => { InternalEntry::Erase { page } => {
@@ -356,11 +398,11 @@ impl Format {
} }
} }
WORD_CHECKSUM.set(&mut word, 0); WORD_CHECKSUM.set(&mut word, 0);
word.to_ne_bytes() word.as_slice()
} }
/// Parses the first word of an entry from its storage representation. /// Parses the first word of an entry from its storage representation.
pub fn parse_word(&self, word: WORD) -> StoreResult<WordState<ParsedWord>> { pub fn parse_word(&self, word: Word) -> StoreResult<WordState<ParsedWord>> {
let valid = if ID_PADDING.check(word) { let valid = if ID_PADDING.check(word) {
ParsedWord::Padding(Padding { length: 0 }) ParsedWord::Padding(Padding { length: 0 })
} else if ID_HEADER.check(word) { } else if ID_HEADER.check(word) {
@@ -418,32 +460,35 @@ impl Format {
} }
/// Builds the storage representation of a user entry. /// Builds the storage representation of a user entry.
pub fn build_user(&self, key: usize, value: &[u8]) -> Vec<u8> { pub fn build_user(&self, key: Nat, value: &[u8]) -> Vec<u8> {
let length = value.len(); let length = usize_to_nat(value.len());
let word_size = self.word_size(); let word_size = self.word_size();
let footer = self.bytes_to_words(length); let footer = self.bytes_to_words(length);
let mut result = vec![0xff; (1 + footer) * word_size]; let mut result = vec![0xff; ((1 + footer) * word_size) as usize];
result[word_size..][..length].copy_from_slice(value); result[word_size as usize..][..length as usize].copy_from_slice(value);
let mut word = ERASED_WORD; let mut word = ERASED_WORD;
ID_HEADER.set(&mut 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); HEADER_FLIPPED.set(&mut word);
*result.last_mut().unwrap() = 0x7f; *result.last_mut().unwrap() = 0x7f;
} }
HEADER_LENGTH.set(&mut word, length); HEADER_LENGTH.set(&mut word, length);
HEADER_KEY.set(&mut word, key); HEADER_KEY.set(&mut word, key);
HEADER_CHECKSUM.set(&mut word, count_zeros(&result[footer * word_size..])); HEADER_CHECKSUM.set(
result[..word_size].copy_from_slice(&word.to_ne_bytes()); &mut word,
count_zeros(&result[(footer * word_size) as usize..]),
);
result[..word_size as usize].copy_from_slice(&word.as_slice());
result result
} }
/// Sets the padding bit in the first word of a user entry. /// 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); ID_PADDING.set(word);
} }
/// Sets the deleted bit in the first word of a user entry. /// 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); HEADER_DELETED.set(word);
} }
@@ -452,21 +497,21 @@ impl Format {
/// # Preconditions /// # Preconditions
/// ///
/// - `bytes + self.word_size()` does not overflow. /// - `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()) div_ceil(bytes, self.word_size())
} }
} }
/// The word index of the init info in a page. /// 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. /// 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. /// 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. /// 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. /// 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 /// Then the position of a word is `(c*N + p)*Q + w`. This position monotonically increases and
/// represents the consumed lifetime of the storage. /// 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position(usize); pub struct Position(Nat);
impl core::ops::Add<usize> for Position { impl core::ops::Add<Nat> for Position {
type Output = Position; type Output = Position;
fn add(self, delta: usize) -> Position { fn add(self, delta: Nat) -> Position {
Position(self.0 + delta) Position(self.0 + delta)
} }
} }
impl core::ops::Sub<Position> for Position { impl core::ops::Sub<Position> for Position {
type Output = usize; type Output = Nat;
fn sub(self, base: Position) -> usize { fn sub(self, base: Position) -> Nat {
self.0 - base.0 self.0 - base.0
} }
} }
impl core::ops::AddAssign<usize> for Position { impl core::ops::AddAssign<Nat> for Position {
fn add_assign(&mut self, delta: usize) { fn add_assign(&mut self, delta: Nat) {
self.0 += delta; self.0 += delta;
} }
} }
@@ -651,12 +705,12 @@ impl Position {
/// - Its word index in its page. /// - Its word index in its page.
/// - Its page index in the storage. /// - Its page index in the storage.
/// - The number of times that page was erased. /// - 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) Position((cycle * format.num_pages() + page) * format.virt_page_size() + word)
} }
/// Accesses the underlying position as a natural number. /// Accesses the underlying position as a natural number.
pub fn get(self) -> usize { pub fn get(self) -> Nat {
self.0 self.0
} }
@@ -665,7 +719,10 @@ impl Position {
let page = self.page(format); let page = self.page(format);
let word = CONTENT_WORD + self.word(format); let word = CONTENT_WORD + self.word(format);
let byte = word * format.word_size(); 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. /// Returns the beginning of the current virtual page.
@@ -681,17 +738,17 @@ impl Position {
} }
/// Returns the number of times the current page was erased. /// 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() (self.0 / format.virt_page_size()) / format.num_pages()
} }
/// Returns the current page index. /// 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() (self.0 / format.virt_page_size()) % format.num_pages()
} }
/// Returns the current word index in the page. /// 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() self.0 % format.virt_page_size()
} }
} }
@@ -711,16 +768,16 @@ pub enum WordState<T> {
/// Information for an initialized page. /// Information for an initialized page.
pub struct InitInfo { pub struct InitInfo {
/// The number of times this page has been erased. /// 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. /// The word index of the first entry in this virtual page.
pub prefix: usize, pub prefix: Nat,
} }
/// Information for a page being compacted. /// Information for a page being compacted.
pub struct CompactInfo { pub struct CompactInfo {
/// The distance in words between head and tail at compaction. /// The distance in words between head and tail at compaction.
pub tail: usize, pub tail: Nat,
} }
/// The first word of an entry. /// The first word of an entry.
@@ -740,7 +797,7 @@ pub enum ParsedWord {
#[derive(Debug)] #[derive(Debug)]
pub struct Padding { pub struct Padding {
/// The number of following padding words after the first word of the padding entry. /// 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. /// Header of a user entry.
@@ -750,13 +807,13 @@ pub struct Header {
pub flipped: bool, pub flipped: bool,
/// The length in bytes of the user data. /// The length in bytes of the user data.
pub length: usize, pub length: Nat,
/// The key of the user entry. /// The key of the user entry.
pub key: usize, pub key: Nat,
/// The checksum of the user entry. /// The checksum of the user entry.
pub checksum: usize, pub checksum: Nat,
} }
impl Header { impl Header {
@@ -775,13 +832,13 @@ pub enum InternalEntry {
/// Indicates that a page should be erased. /// Indicates that a page should be erased.
Erase { Erase {
/// The page to be erased. /// The page to be erased.
page: usize, page: Nat,
}, },
/// Indicates that user entries with high key should be deleted. /// Indicates that user entries with high key should be deleted.
Clear { Clear {
/// The minimum key a user entry should have to be deleted. /// The minimum key a user entry should have to be deleted.
min_key: usize, min_key: Nat,
}, },
/// Marks the start of a transaction. /// Marks the start of a transaction.
@@ -790,7 +847,7 @@ pub enum InternalEntry {
/// entries. /// entries.
Marker { Marker {
/// The number of updates in the transaction. /// The number of updates in the transaction.
count: usize, count: Nat,
}, },
/// Indicates that a user entry should be removed. /// Indicates that a user entry should be removed.
@@ -799,7 +856,7 @@ pub enum InternalEntry {
/// already atomic. /// already atomic.
Remove { Remove {
/// The key of the user entry to be removed. /// 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 /// # Preconditions
/// ///
/// - `x + m` does not overflow. /// - `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 (x + m - 1) / m
} }
@@ -825,7 +882,7 @@ mod tests {
#[test] #[test]
fn size_of_format() { fn size_of_format() {
assert_eq!(std::mem::size_of::<Format>(), 24); assert_eq!(std::mem::size_of::<Format>(), 12);
} }
#[test] #[test]
@@ -921,6 +978,18 @@ mod tests {
assert_eq!(LEN_REMOVE.pos, 17); 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] #[test]
fn is_erased_ok() { fn is_erased_ok() {
assert!(is_erased(&[])); assert!(is_erased(&[]));

View File

@@ -12,9 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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. /// 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`. /// - The bit field must fit in a 32-bits word: `pos + len < 32`.
pub struct Field { pub struct Field {
/// The position of the bit field. /// The position of the bit field.
pub pos: usize, pub pos: Nat,
/// The length of the bit field. /// The length of the bit field.
pub len: usize, pub len: Nat,
} }
impl Field { impl Field {
/// Reads the value of a bit field. /// Reads the value of a bit field.
pub fn get(&self, word: u32) -> usize { pub fn get(&self, word: Word) -> Nat {
((word >> self.pos) & self.mask()) as usize (word.0 >> self.pos) & self.mask()
} }
/// Sets the value of a bit field. /// 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 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`. /// - The value must only change bits from 1 to 0: `self.get(*word) & value == value`.
pub fn set(&self, word: &mut u32, value: usize) { pub fn set(&self, word: &mut Word, value: Nat) {
let value = value as u32;
debug_assert_eq!(value & self.mask(), value); debug_assert_eq!(value & self.mask(), value);
let mask = !(self.mask() << self.pos); let mask = !(self.mask() << self.pos);
*word &= mask | (value << self.pos); word.0 &= mask | (value << self.pos);
debug_assert_eq!(self.get(*word), value as usize); debug_assert_eq!(self.get(*word), value);
} }
/// Returns a bit mask the length of the bit field. /// Returns a bit mask the length of the bit field.
@@ -70,17 +72,17 @@ pub struct ConstField {
pub field: Field, pub field: Field,
/// The constant value. /// The constant value.
pub value: usize, pub value: Nat,
} }
impl ConstField { impl ConstField {
/// Checks that the bit field has its value. /// 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 self.field.get(word) == self.value
} }
/// Sets the bit field to its 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); self.field.set(word, self.value);
} }
} }
@@ -92,18 +94,18 @@ impl ConstField {
/// - The bit must fit in a 32-bits word: `pos < 32`. /// - The bit must fit in a 32-bits word: `pos < 32`.
pub struct Bit { pub struct Bit {
/// The position of the bit. /// The position of the bit.
pub pos: usize, pub pos: Nat,
} }
impl Bit { impl Bit {
/// Returns whether the value of the bit is zero. /// Returns whether the value of the bit is zero.
pub fn get(&self, word: u32) -> bool { pub fn get(&self, word: Word) -> bool {
word & (1 << self.pos) == 0 word.0 & (1 << self.pos) == 0
} }
/// Sets the value of the bit to zero. /// Sets the value of the bit to zero.
pub fn set(&self, word: &mut u32) { pub fn set(&self, word: &mut Word) {
*word &= !(1 << self.pos); word.0 &= !(1 << self.pos);
} }
} }
@@ -123,9 +125,9 @@ impl Checksum {
/// # Errors /// # Errors
/// ///
/// Returns `InvalidStorage` if the external increment would be negative. /// Returns `InvalidStorage` if the external increment would be negative.
pub fn get(&self, word: u32) -> StoreResult<usize> { pub fn get(&self, word: Word) -> StoreResult<Nat> {
let checksum = self.field.get(word); 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 checksum
.checked_sub(zeros) .checked_sub(zeros)
.ok_or(StoreError::InvalidStorage) .ok_or(StoreError::InvalidStorage)
@@ -139,9 +141,9 @@ impl Checksum {
/// self.field.mask()`. /// self.field.mask()`.
/// - The checksum value should fit in the checksum bit field: `num_bits(word.count_zeros() + /// - The checksum value should fit in the checksum bit field: `num_bits(word.count_zeros() +
/// value) < self.field.len`. /// value) < self.field.len`.
pub fn set(&self, word: &mut u32, value: usize) { pub fn set(&self, word: &mut Word, value: Nat) {
debug_assert_eq!(self.field.get(*word), self.field.mask() as usize); debug_assert_eq!(self.field.get(*word), self.field.mask());
self.field.set(word, word.count_zeros() as usize + value); self.field.set(word, word.0.count_zeros() + value);
} }
} }
@@ -153,7 +155,7 @@ impl Checksum {
#[cfg(any(doc, test))] #[cfg(any(doc, test))]
pub struct Length { pub struct Length {
/// The position of the next available bit. /// The position of the next available bit.
pub pos: usize, pub pos: Nat,
} }
/// Helps defining contiguous bit fields. /// 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. /// Counts the number of bits equal to zero in a byte slice.
pub fn count_zeros(slice: &[u8]) -> usize { pub fn count_zeros(slice: &[u8]) -> Nat {
slice.iter().map(|&x| x.count_zeros() as usize).sum() slice.iter().map(|&x| x.count_zeros()).sum()
} }
/// Returns the number of bits necessary to represent a number. /// Returns the number of bits necessary to represent a number.
pub const fn num_bits(x: usize) -> usize { pub const fn num_bits(x: Nat) -> Nat {
8 * core::mem::size_of::<usize>() - x.leading_zeros() as usize 8 * core::mem::size_of::<Nat>() as Nat - x.leading_zeros()
} }
#[cfg(test)] #[cfg(test)]
@@ -282,14 +284,14 @@ mod tests {
#[test] #[test]
fn field_ok() { fn field_ok() {
let field = Field { pos: 3, len: 5 }; let field = Field { pos: 3, len: 5 };
assert_eq!(field.get(0x00000000), 0); assert_eq!(field.get(Word(0x00000000)), 0);
assert_eq!(field.get(0x00000007), 0); assert_eq!(field.get(Word(0x00000007)), 0);
assert_eq!(field.get(0x00000008), 1); assert_eq!(field.get(Word(0x00000008)), 1);
assert_eq!(field.get(0x000000f8), 0x1f); assert_eq!(field.get(Word(0x000000f8)), 0x1f);
assert_eq!(field.get(0x0000ff37), 6); assert_eq!(field.get(Word(0x0000ff37)), 6);
let mut word = 0xffffffff; let mut word = Word(0xffffffff);
field.set(&mut word, 3); field.set(&mut word, 3);
assert_eq!(word, 0xffffff1f); assert_eq!(word, Word(0xffffff1f));
} }
#[test] #[test]
@@ -298,25 +300,25 @@ mod tests {
field: Field { pos: 3, len: 5 }, field: Field { pos: 3, len: 5 },
value: 9, value: 9,
}; };
assert!(!field.check(0x00000000)); assert!(!field.check(Word(0x00000000)));
assert!(!field.check(0x0000ffff)); assert!(!field.check(Word(0x0000ffff)));
assert!(field.check(0x00000048)); assert!(field.check(Word(0x00000048)));
assert!(field.check(0x0000ff4f)); assert!(field.check(Word(0x0000ff4f)));
let mut word = 0xffffffff; let mut word = Word(0xffffffff);
field.set(&mut word); field.set(&mut word);
assert_eq!(word, 0xffffff4f); assert_eq!(word, Word(0xffffff4f));
} }
#[test] #[test]
fn bit_ok() { fn bit_ok() {
let bit = Bit { pos: 3 }; let bit = Bit { pos: 3 };
assert!(bit.get(0x00000000)); assert!(bit.get(Word(0x00000000)));
assert!(bit.get(0xfffffff7)); assert!(bit.get(Word(0xfffffff7)));
assert!(!bit.get(0x00000008)); assert!(!bit.get(Word(0x00000008)));
assert!(!bit.get(0xffffffff)); assert!(!bit.get(Word(0xffffffff)));
let mut word = 0xffffffff; let mut word = Word(0xffffffff);
bit.set(&mut word); bit.set(&mut word);
assert_eq!(word, 0xfffffff7); assert_eq!(word, Word(0xfffffff7));
} }
#[test] #[test]
@@ -324,15 +326,15 @@ mod tests {
let field = Checksum { let field = Checksum {
field: Field { pos: 3, len: 5 }, field: Field { pos: 3, len: 5 },
}; };
assert_eq!(field.get(0x00000000), Err(StoreError::InvalidStorage)); assert_eq!(field.get(Word(0x00000000)), Err(StoreError::InvalidStorage));
assert_eq!(field.get(0xffffffff), Ok(31)); assert_eq!(field.get(Word(0xffffffff)), Ok(31));
assert_eq!(field.get(0xffffff07), Ok(0)); assert_eq!(field.get(Word(0xffffff07)), Ok(0));
assert_eq!(field.get(0xffffff0f), Ok(1)); assert_eq!(field.get(Word(0xffffff0f)), Ok(1));
assert_eq!(field.get(0x00ffff67), Ok(4)); assert_eq!(field.get(Word(0x00ffff67)), Ok(4));
assert_eq!(field.get(0x7fffff07), Err(StoreError::InvalidStorage)); assert_eq!(field.get(Word(0x7fffff07)), Err(StoreError::InvalidStorage));
let mut word = 0x0fffffff; let mut word = Word(0x0fffffff);
field.set(&mut word, 4); field.set(&mut word, 4);
assert_eq!(word, 0x0fffff47); assert_eq!(word, Word(0x0fffff47));
} }
#[test] #[test]

View File

@@ -348,8 +348,6 @@
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;
#[macro_use]
mod bitfield;
mod buffer; mod buffer;
mod format; mod format;
#[cfg(feature = "std")] #[cfg(feature = "std")]
@@ -362,3 +360,25 @@ pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage};
pub use self::model::{StoreModel, StoreOperation}; pub use self::model::{StoreModel, StoreOperation};
pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult}; pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult};
pub use self::store::{StoreError, StoreRatio, StoreResult, StoreUpdate}; 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()
}

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
use crate::format::Format; use crate::format::Format;
use crate::{StoreError, StoreRatio, StoreResult, StoreUpdate}; use crate::{usize_to_nat, StoreError, StoreRatio, StoreResult, StoreUpdate};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
/// Models the mutable operations of a store. /// Models the mutable operations of a store.
@@ -79,14 +79,14 @@ impl StoreModel {
/// Returns the capacity according to the model. /// Returns the capacity according to the model.
pub fn capacity(&self) -> StoreRatio { pub fn capacity(&self) -> StoreRatio {
let total = self.format.total_capacity(); 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 } StoreRatio { used, total }
} }
/// Applies a transaction. /// Applies a transaction.
fn transaction(&mut self, updates: Vec<StoreUpdate>) -> StoreResult<()> { fn transaction(&mut self, updates: Vec<StoreUpdate>) -> StoreResult<()> {
// Fail if too many updates. // 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); return Err(StoreError::InvalidArgument);
} }
// Fail if an update is invalid. // Fail if an update is invalid.
@@ -130,7 +130,7 @@ impl StoreModel {
/// Applies a clear operation. /// Applies a clear operation.
fn clear(&mut self, min_key: usize) -> StoreResult<()> { 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); return Err(StoreError::InvalidArgument);
} }
self.content.retain(|&k, _| k < min_key); self.content.retain(|&k, _| k < min_key);
@@ -155,14 +155,14 @@ impl StoreModel {
/// Returns the word capacity of an entry. /// Returns the word capacity of an entry.
fn entry_size(&self, value: &[u8]) -> usize { 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. /// Returns whether an update is valid.
fn update_valid(&self, update: &StoreUpdate) -> bool { fn update_valid(&self, update: &StoreUpdate) -> bool {
update.key() <= self.format.max_key() update.key() <= self.format.max_key() as usize
&& update && update
.value() .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)
} }
} }

View File

@@ -37,9 +37,13 @@ pub type StorageResult<T> = Result<T, StorageError>;
/// Abstracts a flash storage. /// Abstracts a flash storage.
pub trait Storage { pub trait Storage {
/// The size of a word in bytes. /// The size of a word in bytes.
///
/// A word is the smallest unit of writable flash.
fn word_size(&self) -> usize; fn word_size(&self) -> usize;
/// The size of a page in bytes. /// The size of a page in bytes.
///
/// A page is the smallest unit of erasable flash.
fn page_size(&self) -> usize; fn page_size(&self) -> usize;
/// The number of pages in the storage. /// The number of pages in the storage.

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::StorageError; use crate::{Nat, StorageError};
use alloc::vec::Vec; use alloc::vec::Vec;
/// Errors returned by store operations. /// Errors returned by store operations.
@@ -78,26 +78,26 @@ pub type StoreResult<T> = Result<T, StoreError>;
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub struct StoreRatio { pub struct StoreRatio {
/// How much of the metric is used. /// 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. /// How much of the metric can be used at most.
pub(crate) total: usize, pub(crate) total: Nat,
} }
impl StoreRatio { impl StoreRatio {
/// How much of the metric is used. /// How much of the metric is used.
pub fn used(self) -> usize { pub fn used(self) -> usize {
self.used self.used as usize
} }
/// How much of the metric can be used at most. /// How much of the metric can be used at most.
pub fn total(self) -> usize { pub fn total(self) -> usize {
self.total self.total as usize
} }
/// How much of the metric is remaining. /// How much of the metric is remaining.
pub fn remaining(self) -> usize { pub fn remaining(self) -> usize {
self.total - self.used (self.total - self.used) as usize
} }
} }