Merge branch 'develop' into command-cred-mgmt
This commit is contained in:
5
.github/workflows/persistent_store_test.yml
vendored
5
.github/workflows/persistent_store_test.yml
vendored
@@ -13,6 +13,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Unit testing of Persistent store library (release mode)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -124,15 +124,15 @@ fn main() {
|
||||
compute_latency(&timer, 20, 1, 50);
|
||||
// Those overwritten 1 word entries simulate counters.
|
||||
compute_latency(&timer, 3, 0, 1);
|
||||
compute_latency(&timer, 6, 0, 1);
|
||||
compute_latency(&timer, 20, 0, 1);
|
||||
writeln!(Console::new(), "\nDone.").unwrap();
|
||||
|
||||
// Results on nrf52840dk:
|
||||
//
|
||||
// | Pages | Overwrite | Length | Boot | Compaction | Insert | Remove |
|
||||
// | ----- | --------- | --------- | ------- | ---------- | ------ | ------- |
|
||||
// | 3 | no | 50 words | 2.0 ms | 132.5 ms | 4.8 ms | 1.2 ms |
|
||||
// | 20 | no | 50 words | 7.4 ms | 135.5 ms | 10.2 ms | 3.9 ms |
|
||||
// | 3 | yes | 1 word | 21.9 ms | 94.5 ms | 12.4 ms | 5.9 ms |
|
||||
// | 6 | yes | 1 word | 55.2 ms | 100.8 ms | 24.8 ms | 12.1 ms |
|
||||
// | Pages | Overwrite | Length | Boot | Compaction | Insert | Remove |
|
||||
// | ----- | --------- | --------- | ------- | ---------- | ------ | ------ |
|
||||
// | 3 | no | 50 words | 2.0 ms | 132.8 ms | 4.3 ms | 1.2 ms |
|
||||
// | 20 | no | 50 words | 7.8 ms | 135.7 ms | 9.9 ms | 4.0 ms |
|
||||
// | 3 | yes | 1 word | 19.6 ms | 90.8 ms | 4.7 ms | 2.3 ms |
|
||||
// | 20 | yes | 1 word | 183.3 ms | 90.9 ms | 4.8 ms | 2.3 ms |
|
||||
}
|
||||
|
||||
@@ -335,12 +335,12 @@ impl Format {
|
||||
}
|
||||
|
||||
/// Builds the storage representation of an init info.
|
||||
pub fn build_init(&self, init: InitInfo) -> WordSlice {
|
||||
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);
|
||||
word.as_slice()
|
||||
INIT_CYCLE.set(&mut word, init.cycle)?;
|
||||
INIT_PREFIX.set(&mut word, init.prefix)?;
|
||||
WORD_CHECKSUM.set(&mut word, 0)?;
|
||||
Ok(word.as_slice())
|
||||
}
|
||||
|
||||
/// Returns the storage index of the compact info of a page.
|
||||
@@ -368,36 +368,36 @@ impl Format {
|
||||
}
|
||||
|
||||
/// Builds the storage representation of a compact info.
|
||||
pub fn build_compact(&self, compact: CompactInfo) -> WordSlice {
|
||||
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);
|
||||
word.as_slice()
|
||||
COMPACT_TAIL.set(&mut word, compact.tail)?;
|
||||
WORD_CHECKSUM.set(&mut word, 0)?;
|
||||
Ok(word.as_slice())
|
||||
}
|
||||
|
||||
/// Builds the storage representation of an internal entry.
|
||||
pub fn build_internal(&self, internal: InternalEntry) -> WordSlice {
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
ID_REMOVE.set(&mut word)?;
|
||||
REMOVE_KEY.set(&mut word, key)?;
|
||||
}
|
||||
}
|
||||
WORD_CHECKSUM.set(&mut word, 0);
|
||||
word.as_slice()
|
||||
WORD_CHECKSUM.set(&mut word, 0)?;
|
||||
Ok(word.as_slice())
|
||||
}
|
||||
|
||||
/// Parses the first word of an entry from its storage representation.
|
||||
@@ -459,31 +459,31 @@ impl Format {
|
||||
}
|
||||
|
||||
/// Builds the storage representation of a user entry.
|
||||
pub fn build_user(&self, key: Nat, value: &[u8]) -> Vec<u8> {
|
||||
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);
|
||||
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_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());
|
||||
result
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Sets the padding bit in the first word of a user entry.
|
||||
pub fn set_padding(&self, word: &mut Word) {
|
||||
ID_PADDING.set(word);
|
||||
pub fn set_padding(&self, word: &mut Word) -> StoreResult<()> {
|
||||
ID_PADDING.set(word)
|
||||
}
|
||||
|
||||
/// Sets the deleted bit in the first word of a user entry.
|
||||
@@ -1077,4 +1077,15 @@ mod tests {
|
||||
0xff800000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_offsets_fit_in_a_halfword() {
|
||||
// The store stores in RAM the entry positions as their offset from the head. Those offsets
|
||||
// are represented as u16. The bound below is a large over-approximation of the maximal
|
||||
// offset. We first make sure it fits in a u16.
|
||||
const MAX_POS: Nat = (MAX_PAGE_INDEX + 1) * MAX_VIRT_PAGE_SIZE;
|
||||
assert!(MAX_POS <= u16::MAX as Nat);
|
||||
// We also check the actual value for up-to-date documentation, since it's a constant.
|
||||
assert_eq!(MAX_POS, 0xff80);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,15 +42,20 @@ impl Field {
|
||||
|
||||
/// Sets the value of a bit field.
|
||||
///
|
||||
/// # Preconditions
|
||||
/// # Errors
|
||||
///
|
||||
/// - 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 Word, value: Nat) {
|
||||
debug_assert_eq!(value & self.mask(), value);
|
||||
pub fn set(&self, word: &mut Word, value: Nat) -> StoreResult<()> {
|
||||
if value & self.mask() != value {
|
||||
return Err(StoreError::InvalidStorage);
|
||||
}
|
||||
let mask = !(self.mask() << self.pos);
|
||||
word.0 &= mask | (value << self.pos);
|
||||
debug_assert_eq!(self.get(*word), value);
|
||||
if self.get(*word) != value {
|
||||
return Err(StoreError::InvalidStorage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a bit mask the length of the bit field.
|
||||
@@ -82,8 +87,8 @@ impl ConstField {
|
||||
}
|
||||
|
||||
/// Sets the bit field to its value.
|
||||
pub fn set(&self, word: &mut Word) {
|
||||
self.field.set(word, self.value);
|
||||
pub fn set(&self, word: &mut Word) -> StoreResult<()> {
|
||||
self.field.set(word, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +140,15 @@ impl Checksum {
|
||||
|
||||
/// Sets the checksum to the external increment value.
|
||||
///
|
||||
/// # Preconditions
|
||||
/// # Errors
|
||||
///
|
||||
/// - The bits of the checksum bit field should be set to one: `self.field.get(*word) ==
|
||||
/// 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 Word, value: Nat) {
|
||||
pub fn set(&self, word: &mut Word, value: Nat) -> StoreResult<()> {
|
||||
debug_assert_eq!(self.field.get(*word), self.field.mask());
|
||||
self.field.set(word, word.0.count_zeros() + value);
|
||||
self.field.set(word, word.0.count_zeros() + value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +295,7 @@ mod tests {
|
||||
assert_eq!(field.get(Word(0x000000f8)), 0x1f);
|
||||
assert_eq!(field.get(Word(0x0000ff37)), 6);
|
||||
let mut word = Word(0xffffffff);
|
||||
field.set(&mut word, 3);
|
||||
field.set(&mut word, 3).unwrap();
|
||||
assert_eq!(word, Word(0xffffff1f));
|
||||
}
|
||||
|
||||
@@ -305,7 +310,7 @@ mod tests {
|
||||
assert!(field.check(Word(0x00000048)));
|
||||
assert!(field.check(Word(0x0000ff4f)));
|
||||
let mut word = Word(0xffffffff);
|
||||
field.set(&mut word);
|
||||
field.set(&mut word).unwrap();
|
||||
assert_eq!(word, Word(0xffffff4f));
|
||||
}
|
||||
|
||||
@@ -333,7 +338,7 @@ mod tests {
|
||||
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);
|
||||
field.set(&mut word, 4).unwrap();
|
||||
assert_eq!(word, Word(0x0fffff47));
|
||||
}
|
||||
|
||||
|
||||
@@ -344,6 +344,7 @@
|
||||
//! storage, the store is checked not to crash.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![feature(try_trait)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
@@ -23,8 +23,11 @@ use crate::{usize_to_nat, Nat, Storage, StorageError, StorageIndex};
|
||||
pub use crate::{
|
||||
BufferStorage, StoreDriver, StoreDriverOff, StoreDriverOn, StoreInterruption, StoreInvariant,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::{max, min, Ordering};
|
||||
use core::convert::TryFrom;
|
||||
use core::option::NoneError;
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -75,6 +78,14 @@ impl From<StorageError> for StoreError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NoneError> for StoreError {
|
||||
fn from(error: NoneError) -> StoreError {
|
||||
match error {
|
||||
NoneError => StoreError::InvalidStorage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of store operations.
|
||||
pub type StoreResult<T> = Result<T, StoreError>;
|
||||
|
||||
@@ -174,6 +185,8 @@ impl StoreUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
pub type StoreIter<'a> = Box<dyn Iterator<Item = StoreResult<StoreHandle>> + 'a>;
|
||||
|
||||
/// Implements a store with a map interface over a storage.
|
||||
#[derive(Clone)]
|
||||
pub struct Store<S: Storage> {
|
||||
@@ -182,6 +195,14 @@ pub struct Store<S: Storage> {
|
||||
|
||||
/// The storage configuration.
|
||||
format: Format,
|
||||
|
||||
/// The position of the first word in the store.
|
||||
head: Option<Position>,
|
||||
|
||||
/// The list of the position of the user entries.
|
||||
///
|
||||
/// The position is encoded as the word offset from the [head](Store#structfield.head).
|
||||
entries: Option<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl<S: Storage> Store<S> {
|
||||
@@ -199,7 +220,12 @@ impl<S: Storage> Store<S> {
|
||||
None => return Err((StoreError::InvalidArgument, storage)),
|
||||
Some(x) => x,
|
||||
};
|
||||
let mut store = Store { storage, format };
|
||||
let mut store = Store {
|
||||
storage,
|
||||
format,
|
||||
head: None,
|
||||
entries: None,
|
||||
};
|
||||
if let Err(error) = store.recover() {
|
||||
return Err((error, store.storage));
|
||||
}
|
||||
@@ -207,8 +233,19 @@ impl<S: Storage> Store<S> {
|
||||
}
|
||||
|
||||
/// Iterates over the entries.
|
||||
pub fn iter<'a>(&'a self) -> StoreResult<StoreIter<'a, S>> {
|
||||
StoreIter::new(self)
|
||||
pub fn iter<'a>(&'a self) -> StoreResult<StoreIter<'a>> {
|
||||
let head = self.head?;
|
||||
Ok(Box::new(self.entries.as_ref()?.iter().map(
|
||||
move |&offset| {
|
||||
let pos = head + offset as Nat;
|
||||
match self.parse_entry(&mut pos.clone())? {
|
||||
ParsedEntry::User(Header {
|
||||
key, length: len, ..
|
||||
}) => Ok(StoreHandle { key, pos, len }),
|
||||
_ => Err(StoreError::InvalidStorage),
|
||||
}
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
/// Returns the current capacity in words.
|
||||
@@ -217,16 +254,9 @@ impl<S: Storage> Store<S> {
|
||||
pub fn capacity(&self) -> StoreResult<StoreRatio> {
|
||||
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),
|
||||
}
|
||||
for handle in self.iter()? {
|
||||
let handle = handle?;
|
||||
used += 1 + self.format.bytes_to_words(handle.len);
|
||||
}
|
||||
Ok(StoreRatio { used, total })
|
||||
}
|
||||
@@ -270,7 +300,9 @@ impl<S: Storage> Store<S> {
|
||||
self.reserve(self.format.transaction_capacity(updates))?;
|
||||
// Write the marker entry.
|
||||
let marker = self.tail()?;
|
||||
let entry = self.format.build_internal(InternalEntry::Marker { count });
|
||||
let entry = self
|
||||
.format
|
||||
.build_internal(InternalEntry::Marker { count })?;
|
||||
self.write_slice(marker, &entry)?;
|
||||
self.init_page(marker, marker)?;
|
||||
// Write the updates.
|
||||
@@ -278,7 +310,7 @@ impl<S: Storage> Store<S> {
|
||||
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 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])?;
|
||||
@@ -287,7 +319,7 @@ impl<S: Storage> Store<S> {
|
||||
}
|
||||
StoreUpdate::Remove { key } => {
|
||||
let key = usize_to_nat(key);
|
||||
let remove = self.format.build_internal(InternalEntry::Remove { key });
|
||||
let remove = self.format.build_internal(InternalEntry::Remove { key })?;
|
||||
self.write_slice(tail, &remove)?;
|
||||
0
|
||||
}
|
||||
@@ -307,7 +339,9 @@ impl<S: Storage> Store<S> {
|
||||
if min_key > self.format.max_key() {
|
||||
return Err(StoreError::InvalidArgument);
|
||||
}
|
||||
let clear = self.format.build_internal(InternalEntry::Clear { min_key });
|
||||
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 {
|
||||
@@ -373,7 +407,7 @@ impl<S: Storage> Store<S> {
|
||||
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 = 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()?;
|
||||
@@ -381,6 +415,7 @@ impl<S: Storage> Store<S> {
|
||||
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.push_entry(tail)?;
|
||||
self.insert_init(tail, footer, key)
|
||||
}
|
||||
|
||||
@@ -398,7 +433,8 @@ impl<S: Storage> Store<S> {
|
||||
/// 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))
|
||||
self.delete_pos(handle.pos, self.format.bytes_to_words(handle.len))?;
|
||||
self.remove_entry(handle.pos)
|
||||
}
|
||||
|
||||
/// Returns the maximum length in bytes of a value.
|
||||
@@ -437,7 +473,7 @@ impl<S: Storage> Store<S> {
|
||||
let init_info = self.format.build_init(InitInfo {
|
||||
cycle: 0,
|
||||
prefix: 0,
|
||||
});
|
||||
})?;
|
||||
self.storage_write_slice(index, &init_info)
|
||||
}
|
||||
|
||||
@@ -460,7 +496,9 @@ impl<S: Storage> Store<S> {
|
||||
|
||||
/// Recovers a possible compaction interrupted while copying the entries.
|
||||
fn recover_compaction(&mut self) -> StoreResult<()> {
|
||||
let head_page = self.head()?.page(&self.format);
|
||||
let head = self.get_extremum_page_head(Ordering::Less)?;
|
||||
self.head = Some(head);
|
||||
let head_page = head.page(&self.format);
|
||||
match self.parse_compact(head_page)? {
|
||||
WordState::Erased => Ok(()),
|
||||
WordState::Partial => self.compact(),
|
||||
@@ -470,14 +508,15 @@ impl<S: Storage> Store<S> {
|
||||
|
||||
/// Recover a possible interrupted operation which is not a compaction.
|
||||
fn recover_operation(&mut self) -> StoreResult<()> {
|
||||
let mut pos = self.head()?;
|
||||
self.entries = Some(Vec::new());
|
||||
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::User(_) => self.push_entry(entry_pos)?,
|
||||
ParsedEntry::Padding => {
|
||||
self.wipe_span(entry_pos + 1, pos - entry_pos - 1)?;
|
||||
}
|
||||
@@ -610,7 +649,7 @@ impl<S: Storage> Store<S> {
|
||||
///
|
||||
/// In particular, the handle has not been compacted.
|
||||
fn check_handle(&self, handle: &StoreHandle) -> StoreResult<()> {
|
||||
if handle.pos < self.head()? {
|
||||
if handle.pos < self.head? {
|
||||
Err(StoreError::InvalidArgument)
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -640,20 +679,22 @@ impl<S: Storage> Store<S> {
|
||||
|
||||
/// Compacts one page.
|
||||
fn compact(&mut self) -> StoreResult<()> {
|
||||
let head = self.head()?;
|
||||
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 });
|
||||
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 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)? {
|
||||
@@ -667,8 +708,12 @@ impl<S: Storage> Store<S> {
|
||||
let pos = head;
|
||||
match self.parse_entry(&mut head)? {
|
||||
ParsedEntry::Tail => break,
|
||||
// This can happen if we copy to the next page. We actually reached the tail but we
|
||||
// read what we just copied.
|
||||
ParsedEntry::Partial if head > end => break,
|
||||
ParsedEntry::User(_) => (),
|
||||
_ => continue,
|
||||
ParsedEntry::Padding => continue,
|
||||
_ => return Err(StoreError::InvalidStorage),
|
||||
};
|
||||
let length = head - pos;
|
||||
// We have to copy the slice for 2 reasons:
|
||||
@@ -676,11 +721,13 @@ impl<S: Storage> Store<S> {
|
||||
// 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.remove_entry(pos)?;
|
||||
self.write_slice(tail, &entry)?;
|
||||
self.push_entry(tail)?;
|
||||
self.init_page(tail, tail + (length - 1))?;
|
||||
tail += length;
|
||||
}
|
||||
let erase = self.format.build_internal(InternalEntry::Erase { page });
|
||||
let erase = self.format.build_internal(InternalEntry::Erase { page })?;
|
||||
self.write_slice(tail, &erase)?;
|
||||
self.init_page(tail, tail)?;
|
||||
self.compact_erase(tail)
|
||||
@@ -688,14 +735,31 @@ impl<S: Storage> Store<S> {
|
||||
|
||||
/// 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())? {
|
||||
// Read the page to erase from the erase entry.
|
||||
let mut page = match self.parse_entry(&mut erase.clone())? {
|
||||
ParsedEntry::Internal(InternalEntry::Erase { page }) => page,
|
||||
_ => return Err(StoreError::InvalidStorage),
|
||||
};
|
||||
// Erase the page.
|
||||
self.storage_erase_page(page)?;
|
||||
let head = self.head()?;
|
||||
// Update the head.
|
||||
page = (page + 1) % self.format.num_pages();
|
||||
let init = match self.parse_init(page)? {
|
||||
WordState::Valid(x) => x,
|
||||
_ => return Err(StoreError::InvalidStorage),
|
||||
};
|
||||
let head = self.format.page_head(init, page);
|
||||
if let Some(entries) = &mut self.entries {
|
||||
let head_offset = u16::try_from(head - self.head?).ok()?;
|
||||
for entry in entries {
|
||||
*entry = entry.checked_sub(head_offset)?;
|
||||
}
|
||||
}
|
||||
self.head = Some(head);
|
||||
// Wipe the overlapping entry from the erased page.
|
||||
let pos = head.page_begin(&self.format);
|
||||
self.wipe_span(pos, head - pos)?;
|
||||
// Mark the erase entry as done.
|
||||
self.set_padding(erase)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -704,13 +768,13 @@ impl<S: Storage> Store<S> {
|
||||
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 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::User(_) => self.push_entry(entry_pos)?,
|
||||
ParsedEntry::Internal(InternalEntry::Remove { .. }) => {
|
||||
self.set_padding(entry_pos)?
|
||||
}
|
||||
@@ -727,37 +791,38 @@ impl<S: Storage> Store<S> {
|
||||
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.delete_if(clear, |key| key >= min_key)?;
|
||||
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(_) => (),
|
||||
self.delete_if(end, |key| sorted_keys.binary_search(&key).is_ok())
|
||||
}
|
||||
|
||||
/// Deletes entries matching a predicate up to a certain position.
|
||||
fn delete_if(&mut self, end: Position, delete: impl Fn(Nat) -> bool) -> StoreResult<()> {
|
||||
let head = self.head?;
|
||||
let mut entries = self.entries.take()?;
|
||||
let mut i = 0;
|
||||
while i < entries.len() {
|
||||
let pos = head + entries[i] as Nat;
|
||||
if pos >= end {
|
||||
break;
|
||||
}
|
||||
let header = match self.parse_entry(&mut pos.clone())? {
|
||||
ParsedEntry::User(x) => x,
|
||||
_ => return Err(StoreError::InvalidStorage),
|
||||
};
|
||||
if delete(header.key) {
|
||||
self.delete_pos(pos, self.format.bytes_to_words(header.length))?;
|
||||
entries.swap_remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
self.entries = Some(entries);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -792,7 +857,7 @@ impl<S: Storage> Store<S> {
|
||||
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(())
|
||||
}
|
||||
@@ -800,7 +865,7 @@ impl<S: Storage> Store<S> {
|
||||
/// 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.format.set_padding(&mut word)?;
|
||||
self.write_slice(pos, &word.as_slice())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -836,19 +901,20 @@ impl<S: Storage> Store<S> {
|
||||
}
|
||||
}
|
||||
// There is always at least one initialized page.
|
||||
best.ok_or(StoreError::InvalidStorage)
|
||||
Ok(best?)
|
||||
}
|
||||
|
||||
/// Returns the number of words that can be written without compaction.
|
||||
fn immediate_capacity(&self) -> StoreResult<Nat> {
|
||||
let tail = self.tail()?;
|
||||
let end = self.head()? + self.format.virt_size();
|
||||
let end = self.head? + self.format.virt_size();
|
||||
Ok(end.get().saturating_sub(tail.get()))
|
||||
}
|
||||
|
||||
/// Returns the position of the first word in the store.
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) fn head(&self) -> StoreResult<Position> {
|
||||
self.get_extremum_page_head(Ordering::Less)
|
||||
Ok(self.head?)
|
||||
}
|
||||
|
||||
/// Returns one past the position of the last word in the store.
|
||||
@@ -863,6 +929,30 @@ impl<S: Storage> Store<S> {
|
||||
Ok(pos)
|
||||
}
|
||||
|
||||
fn push_entry(&mut self, pos: Position) -> StoreResult<()> {
|
||||
let entries = match &mut self.entries {
|
||||
None => return Ok(()),
|
||||
Some(x) => x,
|
||||
};
|
||||
let head = self.head?;
|
||||
let offset = u16::try_from(pos - head).ok()?;
|
||||
debug_assert!(!entries.contains(&offset));
|
||||
entries.push(offset);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_entry(&mut self, pos: Position) -> StoreResult<()> {
|
||||
let entries = match &mut self.entries {
|
||||
None => return Ok(()),
|
||||
Some(x) => x,
|
||||
};
|
||||
let head = self.head?;
|
||||
let offset = u16::try_from(pos - head).ok()?;
|
||||
let i = entries.iter().position(|x| *x == offset)?;
|
||||
entries.swap_remove(i);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the entry at a given position.
|
||||
///
|
||||
/// The position is updated to point to the next entry.
|
||||
@@ -1061,7 +1151,7 @@ impl Store<BufferStorage> {
|
||||
/// 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<u8> {
|
||||
let head = self.head().unwrap();
|
||||
let head = self.head.unwrap();
|
||||
let length = self.format.bytes_to_words(handle.len);
|
||||
if head <= handle.pos {
|
||||
// The value has not been compacted.
|
||||
@@ -1087,20 +1177,21 @@ impl Store<BufferStorage> {
|
||||
store
|
||||
.iter()
|
||||
.unwrap()
|
||||
.map(|x| x.unwrap())
|
||||
.filter(|x| delete_key(x.key as usize))
|
||||
.collect::<Vec<_>>()
|
||||
.filter(|x| x.is_err() || delete_key(x.as_ref().unwrap().key as usize))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
};
|
||||
match *operation {
|
||||
StoreOperation::Transaction { ref updates } => {
|
||||
let keys: HashSet<usize> = 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))
|
||||
match deleted(self, &|key| keys.contains(&key)) {
|
||||
Ok(deleted) => (deleted, self.transaction(updates)),
|
||||
Err(error) => (Vec::new(), Err(error)),
|
||||
}
|
||||
}
|
||||
StoreOperation::Clear { min_key } => match deleted(self, &|key| key >= min_key) {
|
||||
Ok(deleted) => (deleted, self.clear(min_key)),
|
||||
Err(error) => (Vec::new(), Err(error)),
|
||||
},
|
||||
StoreOperation::Prepare { length } => (Vec::new(), self.prepare(length)),
|
||||
}
|
||||
}
|
||||
@@ -1110,10 +1201,12 @@ impl Store<BufferStorage> {
|
||||
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,
|
||||
});
|
||||
let init_info = format
|
||||
.build_init(InitInfo {
|
||||
cycle: usize_to_nat(cycle),
|
||||
prefix: 0,
|
||||
})
|
||||
.unwrap();
|
||||
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.
|
||||
@@ -1165,61 +1258,6 @@ enum ParsedEntry {
|
||||
Tail,
|
||||
}
|
||||
|
||||
/// Iterates over the entries of a store.
|
||||
pub struct StoreIter<'a, S: Storage> {
|
||||
/// The store being iterated.
|
||||
store: &'a Store<S>,
|
||||
|
||||
/// 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<S>) -> StoreResult<StoreIter<'a, S>> {
|
||||
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<Option<StoreHandle>> {
|
||||
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<StoreHandle>;
|
||||
|
||||
fn next(&mut self) -> Option<StoreResult<StoreHandle>> {
|
||||
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.
|
||||
@@ -1438,4 +1476,22 @@ mod tests {
|
||||
driver = driver.power_off().power_on().unwrap();
|
||||
driver.check().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_ok() {
|
||||
let mut driver = MINIMAL.new_driver().power_on().unwrap();
|
||||
|
||||
// The store is initially empty.
|
||||
assert!(driver.store().entries.as_ref().unwrap().is_empty());
|
||||
|
||||
// Inserted elements are added.
|
||||
const LEN: usize = 6;
|
||||
driver.insert(0, &[0x38; (LEN - 1) * 4]).unwrap();
|
||||
driver.insert(1, &[0x5c; 4]).unwrap();
|
||||
assert_eq!(driver.store().entries, Some(vec![0, LEN as u16]));
|
||||
|
||||
// Deleted elements are removed.
|
||||
driver.remove(0).unwrap();
|
||||
assert_eq!(driver.store().entries, Some(vec![LEN as u16]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@ pub struct IterCredentials<'a> {
|
||||
store: &'a persistent_store::Store<Storage>,
|
||||
|
||||
/// The store iterator.
|
||||
iter: persistent_store::StoreIter<'a, Storage>,
|
||||
iter: persistent_store::StoreIter<'a>,
|
||||
|
||||
/// The iteration result.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user