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.
#![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::<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.
///
/// 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.
///
/// 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<S: Storage>(storage: &S) -> Option<Format> {
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<S: Storage>(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<WordState<InitInfo>> {
pub fn parse_init(&self, word: Word) -> StoreResult<WordState<InitInfo>> {
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<WordState<CompactInfo>> {
pub fn parse_compact(&self, word: Word) -> StoreResult<WordState<CompactInfo>> {
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<WordState<ParsedWord>> {
pub fn parse_word(&self, word: Word) -> StoreResult<WordState<ParsedWord>> {
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<u8> {
let length = value.len();
pub fn build_user(&self, key: Nat, value: &[u8]) -> Vec<u8> {
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<usize> for Position {
impl core::ops::Add<Nat> 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<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
}
}
impl core::ops::AddAssign<usize> for Position {
fn add_assign(&mut self, delta: usize) {
impl core::ops::AddAssign<Nat> 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<T> {
/// 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::<Format>(), 24);
assert_eq!(std::mem::size_of::<Format>(), 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(&[]));

View File

@@ -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<usize> {
pub fn get(&self, word: Word) -> StoreResult<Nat> {
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::<usize>() - x.leading_zeros() as usize
pub const fn num_bits(x: Nat) -> Nat {
8 * core::mem::size_of::<Nat>() 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]

View File

@@ -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()
}

View File

@@ -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<StoreUpdate>) -> 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)
}
}

View File

@@ -37,9 +37,13 @@ pub type StorageResult<T> = Result<T, StorageError>;
/// 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.

View File

@@ -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<T> = Result<T, StoreError>;
#[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
}
}