#[macro_use]
mod bitfield;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::cmp::min;
use core::convert::TryFrom;
#[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, StoreUpdate};
#[allow(clippy::upper_case_acronyms)]
type WORD = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Word(WORD);
pub type WordSlice = [u8; core::mem::size_of::<WORD>()];
impl Word {
pub fn from_slice(slice: &[u8]) -> Word {
Word(WORD::from_le_bytes(<WordSlice>::try_from(slice).unwrap()))
}
pub fn as_slice(self) -> WordSlice {
self.0.to_le_bytes()
}
}
const WORD_SIZE: Nat = core::mem::size_of::<WORD>() as Nat;
const MIN_PAGE_SIZE: Nat = 8;
const MAX_PAGE_SIZE: Nat = 4096;
const MAX_ERASE_CYCLE: Nat = 65535;
const MIN_NUM_PAGES: Nat = 3;
const MAX_PAGE_INDEX: Nat = 63;
const MAX_KEY_INDEX: Nat = 4095;
const MAX_VALUE_LEN: Nat = 1023;
const MAX_UPDATES: Nat = 31;
const MAX_VIRT_PAGE_SIZE: Nat = MAX_PAGE_SIZE / WORD_SIZE - CONTENT_WORD;
const ERASED_WORD: Word = Word(!(0 as WORD));
#[derive(Clone, Debug)]
pub struct Format {
page_size: Nat,
num_pages: Nat,
max_page_erases: Nat,
}
impl Format {
pub fn new<S: Storage>(storage: &S) -> Option<Format> {
if Format::is_storage_supported(storage) {
Some(Format {
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
}
}
fn is_storage_supported<S: Storage>(storage: &S) -> bool {
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_PAGE_SIZE * word_size ..= MAX_PAGE_SIZE).contains(&page_size)
&& (MIN_NUM_PAGES ..= MAX_PAGE_INDEX + 1).contains(&num_pages)
&& max_word_writes >= 2
&& max_page_erases <= MAX_ERASE_CYCLE
}
pub fn word_size(&self) -> Nat {
WORD_SIZE
}
pub fn page_size(&self) -> Nat {
self.page_size
}
pub fn num_pages(&self) -> Nat {
self.num_pages
}
pub fn max_page(&self) -> Nat {
self.num_pages - 1
}
pub fn max_page_erases(&self) -> Nat {
self.max_page_erases
}
pub fn max_key(&self) -> Nat {
MAX_KEY_INDEX
}
pub fn max_updates(&self) -> Nat {
MAX_UPDATES
}
pub fn virt_page_size(&self) -> Nat {
self.page_size() / self.word_size() - CONTENT_WORD
}
pub fn max_value_len(&self) -> Nat {
min((self.virt_page_size() - 1) * self.word_size(), MAX_VALUE_LEN)
}
pub fn max_prefix_len(&self) -> Nat {
self.bytes_to_words(self.max_value_len())
}
pub fn window_size(&self) -> Nat {
(self.num_pages() - 1) * self.virt_page_size() - self.max_prefix_len()
}
pub fn virt_size(&self) -> Nat {
(self.num_pages() - 1) * (self.virt_page_size() - 1) - self.max_prefix_len()
}
pub fn total_capacity(&self) -> Nat {
self.virt_size() - self.num_pages()
}
pub fn total_lifetime(&self) -> Position {
Position::new(self, self.max_page_erases(), self.num_pages() - 1, 0)
}
pub fn page_head(&self, init: InitInfo, page: Nat) -> Position {
Position::new(self, init.cycle, page, init.prefix)
}
pub fn index_init(&self, page: Nat) -> StorageIndex {
let byte = INIT_WORD * self.word_size();
StorageIndex { page: page as usize, byte: byte as usize }
}
pub fn parse_init(&self, word: Word) -> StoreResult<WordState<InitInfo>> {
Ok(if word == ERASED_WORD {
WordState::Erased
} else if WORD_CHECKSUM.get(word)? != 0 {
WordState::Partial
} else {
let cycle = INIT_CYCLE.get(word);
let prefix = INIT_PREFIX.get(word);
if cycle > self.max_page_erases() || prefix > self.max_prefix_len() {
return Err(StoreError::InvalidStorage);
}
WordState::Valid(InitInfo { cycle, prefix })
})
}
pub fn build_init(&self, init: InitInfo) -> StoreResult<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)?;
Ok(word.as_slice())
}
pub fn index_compact(&self, page: Nat) -> StorageIndex {
let byte = COMPACT_WORD * self.word_size();
StorageIndex { page: page as usize, byte: byte as usize }
}
pub fn parse_compact(&self, word: Word) -> StoreResult<WordState<CompactInfo>> {
Ok(if word == ERASED_WORD {
WordState::Erased
} else if WORD_CHECKSUM.get(word)? != 0 {
WordState::Partial
} else {
let tail = COMPACT_TAIL.get(word);
if tail > self.window_size() {
return Err(StoreError::InvalidStorage);
}
WordState::Valid(CompactInfo { tail })
})
}
pub fn build_compact(&self, compact: CompactInfo) -> StoreResult<WordSlice> {
let mut word = ERASED_WORD;
COMPACT_TAIL.set(&mut word, compact.tail)?;
WORD_CHECKSUM.set(&mut word, 0)?;
Ok(word.as_slice())
}
pub fn build_internal(&self, internal: InternalEntry) -> StoreResult<WordSlice> {
let mut word = ERASED_WORD;
match internal {
InternalEntry::Erase { page } => {
ID_ERASE.set(&mut word)?;
ERASE_PAGE.set(&mut word, page)?;
}
InternalEntry::Clear { min_key } => {
ID_CLEAR.set(&mut word)?;
CLEAR_MIN_KEY.set(&mut word, min_key)?;
}
InternalEntry::Marker { count } => {
ID_MARKER.set(&mut word)?;
MARKER_COUNT.set(&mut word, count)?;
}
InternalEntry::Remove { key } => {
ID_REMOVE.set(&mut word)?;
REMOVE_KEY.set(&mut word, key)?;
}
}
WORD_CHECKSUM.set(&mut word, 0)?;
Ok(word.as_slice())
}
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) {
if HEADER_DELETED.get(word) {
let length = HEADER_LENGTH.get(word);
if length > self.max_value_len() {
return Err(StoreError::InvalidStorage);
}
let length = self.bytes_to_words(length);
ParsedWord::Padding(Padding { length })
} else {
let flipped = HEADER_FLIPPED.get(word);
let length = HEADER_LENGTH.get(word);
let key = HEADER_KEY.get(word);
let checksum = HEADER_CHECKSUM.get(word)?;
ParsedWord::Header(Header { flipped, length, key, checksum })
}
} else if ID_ERASE.check(word) {
let page = ERASE_PAGE.get(word);
ParsedWord::Internal(InternalEntry::Erase { page })
} else if ID_CLEAR.check(word) {
let min_key = CLEAR_MIN_KEY.get(word);
ParsedWord::Internal(InternalEntry::Clear { min_key })
} else if ID_MARKER.check(word) {
let count = MARKER_COUNT.get(word);
ParsedWord::Internal(InternalEntry::Marker { count })
} else if ID_REMOVE.check(word) {
let key = REMOVE_KEY.get(word);
ParsedWord::Internal(InternalEntry::Remove { key })
} else if word == ERASED_WORD {
return Ok(WordState::Erased);
} else {
return Ok(WordState::Partial);
};
if let ParsedWord::Internal(internal) = &valid {
if WORD_CHECKSUM.get(word)? != 0 {
return Ok(WordState::Partial);
}
let invalid = match internal {
InternalEntry::Erase { page } => *page > self.max_page(),
InternalEntry::Clear { min_key } => *min_key > self.max_key(),
InternalEntry::Marker { count } => *count > MAX_UPDATES,
InternalEntry::Remove { key } => *key > self.max_key(),
};
if invalid {
return Err(StoreError::InvalidStorage);
}
}
Ok(WordState::Valid(valid))
}
pub fn build_user(&self, key: Nat, value: &[u8]) -> StoreResult<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) 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) 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) as usize ..]))?;
result[.. word_size as usize].copy_from_slice(&word.as_slice());
Ok(result)
}
pub fn set_padding(&self, word: &mut Word) -> StoreResult<()> {
ID_PADDING.set(word)
}
pub fn set_deleted(&self, word: &mut Word) {
HEADER_DELETED.set(word);
}
pub fn transaction_capacity<ByteSlice: Borrow<[u8]>>(
&self, updates: &[StoreUpdate<ByteSlice>],
) -> Nat {
match updates.len() {
0 => 0,
1 => match &updates[0] {
StoreUpdate::Insert { value, .. } => self.entry_size(value.borrow()),
StoreUpdate::Remove { .. } => 0,
},
_ => 1 + updates.iter().map(|x| self.update_capacity(x)).sum::<Nat>(),
}
}
fn update_capacity<ByteSlice: Borrow<[u8]>>(&self, update: &StoreUpdate<ByteSlice>) -> Nat {
match update {
StoreUpdate::Insert { value, .. } => self.entry_size(value.borrow()),
StoreUpdate::Remove { .. } => 1,
}
}
pub fn entry_size(&self, value: &[u8]) -> Nat {
1 + self.bytes_to_words(usize_to_nat(value.len()))
}
pub fn transaction_valid<ByteSlice: Borrow<[u8]>>(
&self, updates: &[StoreUpdate<ByteSlice>],
) -> Option<Vec<Nat>> {
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)
}
pub fn bytes_to_words(&self, bytes: Nat) -> Nat {
div_ceil(bytes, self.word_size())
}
}
const INIT_WORD: Nat = 0;
const COMPACT_WORD: Nat = 1;
const CONTENT_WORD: Nat = 2;
const WORD_CHECKSUM: Checksum = Checksum { field: Field { pos: 27, len: 5 } };
bitfield! {
INIT_CYCLE: Field <= MAX_ERASE_CYCLE,
INIT_PREFIX: Field <= div_ceil(MAX_VALUE_LEN, WORD_SIZE),
#[cfg(test)]
LEN_INIT: Length,
}
bitfield! {
COMPACT_TAIL: Field <= MAX_VIRT_PAGE_SIZE * MAX_PAGE_INDEX,
#[cfg(test)]
LEN_COMPACT: Length,
}
bitfield! {
ID_PADDING: ConstField = [0],
}
bitfield! {
ID_HEADER: ConstField = [1 0],
HEADER_DELETED: Bit,
HEADER_FLIPPED: Bit,
HEADER_LENGTH: Field <= MAX_VALUE_LEN,
HEADER_KEY: Field <= MAX_KEY_INDEX,
HEADER_CHECKSUM: Checksum <= 58,
#[cfg(test)]
LEN_HEADER: Length,
}
bitfield! {
ID_ERASE: ConstField = [1 1 0 0 0],
ERASE_PAGE: Field <= MAX_PAGE_INDEX,
#[cfg(test)]
LEN_ERASE: Length,
}
bitfield! {
ID_CLEAR: ConstField = [1 1 0 0 1],
CLEAR_MIN_KEY: Field <= MAX_KEY_INDEX,
#[cfg(test)]
LEN_CLEAR: Length,
}
bitfield! {
ID_MARKER: ConstField = [1 1 0 1 0],
MARKER_COUNT: Field <= MAX_UPDATES,
#[cfg(test)]
LEN_MARKER: Length,
}
bitfield! {
ID_REMOVE: ConstField = [1 1 0 1 1],
REMOVE_KEY: Field <= MAX_KEY_INDEX,
#[cfg(test)]
LEN_REMOVE: Length,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position(Nat);
impl core::ops::Add<Nat> for Position {
type Output = Position;
fn add(self, delta: Nat) -> Position {
Position(self.0 + delta)
}
}
impl core::ops::Sub<Position> for Position {
type Output = Nat;
fn sub(self, base: Position) -> Nat {
self.0 - base.0
}
}
impl core::ops::AddAssign<Nat> for Position {
fn add_assign(&mut self, delta: Nat) {
self.0 += delta;
}
}
impl Position {
pub fn new(format: &Format, cycle: Nat, page: Nat, word: Nat) -> Position {
Position((cycle * format.num_pages() + page) * format.virt_page_size() + word)
}
pub fn get(self) -> Nat {
self.0
}
pub fn index(self, format: &Format) -> StorageIndex {
let page = self.page(format);
let word = CONTENT_WORD + self.word(format);
let byte = word * format.word_size();
StorageIndex { page: page as usize, byte: byte as usize }
}
pub fn page_begin(self, format: &Format) -> Position {
let virt_page_size = format.virt_page_size();
Position((self.0 / virt_page_size) * virt_page_size)
}
pub fn next_page(self, format: &Format) -> Position {
let virt_page_size = format.virt_page_size();
Position((self.0 / virt_page_size + 1) * virt_page_size)
}
pub fn cycle(self, format: &Format) -> Nat {
(self.0 / format.virt_page_size()) / format.num_pages()
}
pub fn page(self, format: &Format) -> Nat {
(self.0 / format.virt_page_size()) % format.num_pages()
}
pub fn word(self, format: &Format) -> Nat {
self.0 % format.virt_page_size()
}
}
pub enum WordState<T> {
Erased,
Partial,
Valid(T),
}
pub struct InitInfo {
pub cycle: Nat,
pub prefix: Nat,
}
pub struct CompactInfo {
pub tail: Nat,
}
#[derive(Debug)]
pub enum ParsedWord {
Padding(Padding),
Header(Header),
Internal(InternalEntry),
}
#[derive(Debug)]
pub struct Padding {
pub length: Nat,
}
#[derive(Debug)]
pub struct Header {
pub flipped: bool,
pub length: Nat,
pub key: Nat,
pub checksum: Nat,
}
impl Header {
pub fn check(&self, footer: Option<&[u8]>) -> bool {
footer.map_or(0, count_zeros) == self.checksum
}
}
#[derive(Debug)]
pub enum InternalEntry {
Erase {
page: Nat,
},
Clear {
min_key: Nat,
},
Marker {
count: Nat,
},
Remove {
key: Nat,
},
}
pub fn is_erased(slice: &[u8]) -> bool {
slice.iter().all(|&x| x == 0xff)
}
const fn div_ceil(x: Nat, m: Nat) -> Nat {
(x + m - 1) / m
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_of_format() {
assert_eq!(std::mem::size_of::<Format>(), 12);
}
#[test]
fn checksum_ok() {
let Field { pos, len } = WORD_CHECKSUM.field;
assert_eq!(len, num_bits(pos));
assert_eq!(pos + len, 8 * WORD_SIZE);
let words = &[&LEN_INIT, &LEN_COMPACT, &LEN_ERASE, &LEN_CLEAR, &LEN_MARKER, &LEN_REMOVE];
for word in words {
assert!(word.pos < pos);
}
}
#[test]
fn init_ok() {
assert_eq!(INIT_CYCLE.pos, 0);
assert_eq!(INIT_CYCLE.len, 16);
assert_eq!(INIT_PREFIX.pos, 16);
assert_eq!(INIT_PREFIX.len, 9);
assert_eq!(LEN_INIT.pos, 25);
}
#[test]
fn compact_ok() {
assert_eq!(COMPACT_TAIL.pos, 0);
assert_eq!(COMPACT_TAIL.len, 16);
assert_eq!(LEN_COMPACT.pos, 16);
}
#[test]
fn header_ok() {
assert_eq!(ID_HEADER.field.pos, 0);
assert_eq!(ID_HEADER.field.len, 2);
assert_eq!(ID_HEADER.value, 0b01);
assert_eq!(HEADER_DELETED.pos, 2);
assert_eq!(HEADER_FLIPPED.pos, 3);
assert_eq!(HEADER_LENGTH.pos, 4);
assert_eq!(HEADER_LENGTH.len, 10);
assert_eq!(HEADER_KEY.pos, 14);
assert_eq!(HEADER_KEY.len, 12);
assert_eq!(HEADER_CHECKSUM.field.pos, 26);
assert_eq!(HEADER_CHECKSUM.field.len, 6);
assert_eq!(LEN_HEADER.pos, 32);
}
#[test]
fn erase_ok() {
assert_eq!(ID_ERASE.field.pos, 0);
assert_eq!(ID_ERASE.field.len, 5);
assert_eq!(ID_ERASE.value, 0b00011);
assert_eq!(ERASE_PAGE.pos, 5);
assert_eq!(ERASE_PAGE.len, 6);
assert_eq!(LEN_ERASE.pos, 11);
}
#[test]
fn clear_ok() {
assert_eq!(ID_CLEAR.field.pos, 0);
assert_eq!(ID_CLEAR.field.len, 5);
assert_eq!(ID_CLEAR.value, 0b10011);
assert_eq!(CLEAR_MIN_KEY.pos, 5);
assert_eq!(CLEAR_MIN_KEY.len, 12);
assert_eq!(LEN_CLEAR.pos, 17);
}
#[test]
fn marker_ok() {
assert_eq!(ID_MARKER.field.pos, 0);
assert_eq!(ID_MARKER.field.len, 5);
assert_eq!(ID_MARKER.value, 0b01011);
assert_eq!(MARKER_COUNT.pos, 5);
assert_eq!(MARKER_COUNT.len, 5);
assert_eq!(LEN_MARKER.pos, 10);
}
#[test]
fn remove_ok() {
assert_eq!(ID_REMOVE.field.pos, 0);
assert_eq!(ID_REMOVE.field.len, 5);
assert_eq!(ID_REMOVE.value, 0b11011);
assert_eq!(REMOVE_KEY.pos, 5);
assert_eq!(REMOVE_KEY.len, 12);
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(&[]));
assert!(is_erased(&[0xff]));
assert!(is_erased(&[0xff, 0xff]));
assert!(!is_erased(&[0x00]));
assert!(!is_erased(&[0xff, 0xfe]));
assert!(!is_erased(&[0x7f, 0xff]));
}
#[test]
fn div_ceil_ok() {
assert_eq!(div_ceil(0, 1), 0);
assert_eq!(div_ceil(1, 1), 1);
assert_eq!(div_ceil(2, 1), 2);
assert_eq!(div_ceil(0, 2), 0);
assert_eq!(div_ceil(1, 2), 1);
assert_eq!(div_ceil(2, 2), 1);
assert_eq!(div_ceil(3, 2), 2);
}
#[test]
fn positions_fit_in_a_word() {
assert_eq!((MAX_ERASE_CYCLE + 1) * (MAX_PAGE_INDEX + 1) * MAX_VIRT_PAGE_SIZE, 0xff800000);
}
#[test]
fn position_offsets_fit_in_a_halfword() {
const MAX_POS: Nat = (MAX_PAGE_INDEX + 1) * MAX_VIRT_PAGE_SIZE;
assert!(MAX_POS <= u16::MAX as Nat);
assert_eq!(MAX_POS, 0xff80);
}
}