From 10ac76e58a3c5b44a3d3aaa86868ea3c39c731fe Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 08:13:50 +0200 Subject: [PATCH 1/6] Add buffer storage for new store --- libraries/persistent_store/src/buffer.rs | 666 +++++++++++++++++++++++ libraries/persistent_store/src/lib.rs | 3 + 2 files changed, 669 insertions(+) create mode 100644 libraries/persistent_store/src/buffer.rs diff --git a/libraries/persistent_store/src/buffer.rs b/libraries/persistent_store/src/buffer.rs new file mode 100644 index 0000000..076ba0d --- /dev/null +++ b/libraries/persistent_store/src/buffer.rs @@ -0,0 +1,666 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Storage, StorageError, StorageIndex, StorageResult}; +use alloc::borrow::Borrow; +use alloc::boxed::Box; +use alloc::vec; + +/// Simulates a flash storage using a buffer in memory. +/// +/// This storage tracks how many times words are written between page erase cycles, how many times +/// pages are erased, and whether an operation flips bits in the wrong direction (optional). +/// Operations panic if those conditions are broken. This storage also permits to interrupt +/// operations for inspection or to corrupt the operation. +#[derive(Clone)] +pub struct BufferStorage { + /// Content of the storage. + storage: Box<[u8]>, + + /// Options of the storage. + options: BufferOptions, + + /// Number of times a word was written since the last time its page was erased. + word_writes: Box<[usize]>, + + /// Number of times a page was erased. + page_erases: Box<[usize]>, + + /// Interruption state. + interruption: Interruption, +} + +/// Options of a buffer storage. +#[derive(Clone, Debug)] +pub struct BufferOptions { + /// Size of a word in bytes. + pub word_size: usize, + + /// Size of a page in bytes. + pub page_size: usize, + + /// How many times a word can be written between page erase cycles. + pub max_word_writes: usize, + + /// How many times a page can be erased. + pub max_page_erases: usize, + + /// Whether bits cannot be written from 0 to 1. + pub strict_write: bool, +} + +/// Corrupts a slice given actual and expected value. +/// +/// A corruption function is called exactly once and takes 2 arguments: +/// - A mutable slice representing the storage before the interrupted operation. +/// - A shared slice representing what the storage would have been if the operation was not +/// interrupted. +/// +/// The corruption function may flip an arbitrary number of bits in the mutable slice, but may only +/// flip bits that differ between both slices. +pub type BufferCorruptFunction<'a> = Box; + +impl BufferStorage { + /// Creates a buffer storage. + /// + /// # Panics + /// + /// The following preconditions must hold: + /// - `options.word_size` must be a power of two. + /// - `options.page_size` must be a power of two. + /// - `options.page_size` must be word-aligned. + /// - `storage.len()` must be page-aligned. + pub fn new(storage: Box<[u8]>, options: BufferOptions) -> BufferStorage { + assert!(options.word_size.is_power_of_two()); + assert!(options.page_size.is_power_of_two()); + let num_words = storage.len() / options.word_size; + let num_pages = storage.len() / options.page_size; + let buffer = BufferStorage { + storage, + options, + word_writes: vec![0; num_words].into_boxed_slice(), + page_erases: vec![0; num_pages].into_boxed_slice(), + interruption: Interruption::Ready, + }; + assert!(buffer.is_word_aligned(buffer.options.page_size)); + assert!(buffer.is_page_aligned(buffer.storage.len())); + buffer + } + + /// Arms an interruption after a given delay. + /// + /// Before each subsequent mutable operation (write or erase), the delay is decremented if + /// positive. If the delay is elapsed, the operation is saved and an error is returned. + /// Subsequent operations will panic until the interrupted operation is [corrupted]. + /// + /// # Panics + /// + /// Panics if an interruption is already armed. + /// + /// [corrupted]: struct.BufferStorage.html#method.corrupt_operation + pub fn arm_interruption(&mut self, delay: usize) { + self.interruption.arm(delay); + } + + /// Disarms an interruption that did not trigger. + /// + /// Returns the remaining delay. + /// + /// # Panics + /// + /// Panics if an interruption was not [armed] and if the interruption already triggered. + /// + /// [armed]: struct.BufferStorage.html#method.arm_interruption + pub fn disarm_interruption(&mut self) -> usize { + self.interruption.get().err().unwrap() + } + + /// Resets an interruption regardless of triggering. + /// + /// # Panics + /// + /// Panics if an interruption was not [armed]. + /// + /// [armed]: struct.BufferStorage.html#method.arm_interruption + pub fn reset_interruption(&mut self) { + let _ = self.interruption.get(); + } + + /// Corrupts an interrupted operation. + /// + /// Applies the [corruption function] to the storage. Counters are incremented if the operation + /// is complete at their level, i.e. a word is written and a page is erased if the corruption + /// function writes all its bits. + /// + /// # Panics + /// + /// Panics if an interruption was not [armed] and if the interruption did not trigger. May also + /// panic if the corruption function corrupts more bits than allowed or if the interrupted + /// operation itself would have panicked. + /// + /// [armed]: struct.BufferStorage.html#method.arm_interruption + /// [corruption function]: type.BufferCorruptFunction.html + pub fn corrupt_operation(&mut self, corrupt: BufferCorruptFunction) { + let operation = self.interruption.get().unwrap(); + let range = self.operation_range(&operation).unwrap(); + let mut before = self.storage[range.clone()].to_vec().into_boxed_slice(); + match operation { + BufferOperation::Write { value: after, .. } => { + corrupt(&mut before, &after); + self.incr_word_writes(range.start, &before, &after); + } + BufferOperation::Erase { page } => { + let after = vec![0xff; self.page_size()].into_boxed_slice(); + corrupt(&mut before, &after); + if before == after { + self.incr_page_erases(page); + } + } + }; + self.storage[range].copy_from_slice(&before); + } + + /// Returns the number of times a word was written. + pub fn get_word_writes(&self, word: usize) -> usize { + self.word_writes[word] + } + + /// Returns the number of times a page was erased. + pub fn get_page_erases(&self, page: usize) -> usize { + self.page_erases[page] + } + + /// Sets the number of times a page was erased. + pub fn set_page_erases(&mut self, page: usize, cycle: usize) { + self.page_erases[page] = cycle; + } + + /// Returns whether a number is word-aligned. + fn is_word_aligned(&self, x: usize) -> bool { + x & (self.options.word_size - 1) == 0 + } + + /// Returns whether a number is page-aligned. + fn is_page_aligned(&self, x: usize) -> bool { + x & (self.options.page_size - 1) == 0 + } + + /// Updates the counters as if a page was erased. + /// + /// The page counter of that page is incremented and the word counters of that page are reset. + /// + /// # Panics + /// + /// Panics if the maximum number of erase cycles per page is reached. + fn incr_page_erases(&mut self, page: usize) { + assert!(self.page_erases[page] < self.max_page_erases()); + self.page_erases[page] += 1; + let num_words = self.page_size() / self.word_size(); + for word in 0..num_words { + self.word_writes[page * num_words + word] = 0; + } + } + + /// Updates the word counters as if a partial write occurred. + /// + /// The partial write is described as if `complete` was supposed to be written to the storage + /// starting at byte `index`, but actually only `value` was written. Word counters are + /// incremented only if their value would change and they would be completely written. + /// + /// # Panics + /// + /// Panics if the maximum number of writes per word is reached. + fn incr_word_writes(&mut self, index: usize, value: &[u8], complete: &[u8]) { + let word_size = self.word_size(); + for i in 0..value.len() / word_size { + let range = core::ops::Range { + start: i * word_size, + end: (i + 1) * word_size, + }; + // Partial word writes do not count. + if value[range.clone()] != complete[range.clone()] { + continue; + } + // Words are written only if necessary. + if value[range.clone()] == self.storage[index..][range] { + continue; + } + let word = index / word_size + i; + assert!(self.word_writes[word] < self.max_word_writes()); + self.word_writes[word] += 1; + } + } + + /// Returns the storage range of an operation. + fn operation_range( + &self, + operation: &BufferOperation>, + ) -> StorageResult> { + match *operation { + BufferOperation::Write { index, ref value } => index.range(value.borrow().len(), self), + BufferOperation::Erase { page } => { + StorageIndex { page, byte: 0 }.range(self.page_size(), self) + } + } + } +} + +impl Storage for BufferStorage { + fn word_size(&self) -> usize { + self.options.word_size + } + + fn page_size(&self) -> usize { + self.options.page_size + } + + fn num_pages(&self) -> usize { + self.storage.len() / self.options.page_size + } + + fn max_word_writes(&self) -> usize { + self.options.max_word_writes + } + + fn max_page_erases(&self) -> usize { + self.options.max_page_erases + } + + fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<&[u8]> { + Ok(&self.storage[index.range(length, self)?]) + } + + fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> { + if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) { + return Err(StorageError::NotAligned); + } + let operation = BufferOperation::Write { index, value }; + let range = self.operation_range(&operation)?; + // Interrupt operation if armed and delay expired. + self.interruption.tick(&operation)?; + // Check and update counters. + self.incr_word_writes(range.start, value, value); + // Check strict write. + if self.options.strict_write { + for (byte, &val) in range.clone().zip(value.iter()) { + assert_eq!(self.storage[byte] & val, val); + } + } + // Write to the storage. + self.storage[range].copy_from_slice(value); + Ok(()) + } + + fn erase_page(&mut self, page: usize) -> StorageResult<()> { + let operation = BufferOperation::Erase { page }; + let range = self.operation_range(&operation)?; + // Interrupt operation if armed and delay expired. + self.interruption.tick(&operation)?; + // Check and update counters. + self.incr_page_erases(page); + // Write to the storage. + self.storage[range].fill(0xff); + Ok(()) + } +} + +impl core::fmt::Display for BufferStorage { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + let num_pages = self.num_pages(); + let num_words = self.page_size() / self.word_size(); + let num_bytes = self.word_size(); + for page in 0..num_pages { + write!(f, "[{}]", self.page_erases[page])?; + for word in 0..num_words { + write!(f, " [{}]", self.word_writes[page * num_words + word])?; + for byte in 0..num_bytes { + let index = (page * num_words + word) * num_bytes + byte; + write!(f, "{:02x}", self.storage[index])?; + } + } + writeln!(f)?; + } + Ok(()) + } +} + +/// Represents a storage operation. +/// +/// It is polymorphic over the ownership of the byte slice ot avoid unnecessary copies. +#[derive(Clone, Debug, PartialEq, Eq)] +enum BufferOperation> { + /// Represents a write operation. + Write { + /// The storage index at which the write should occur. + index: StorageIndex, + + /// The slice that should be written. + value: ByteSlice, + }, + + /// Represents an erase operation. + Erase { + /// The page that should be erased. + page: usize, + }, +} + +/// Represents a storage operation owning its byte slices. +type OwnedBufferOperation = BufferOperation>; + +/// Represents a storage operation sharing its byte slices. +type SharedBufferOperation<'a> = BufferOperation<&'a [u8]>; + +impl<'a> SharedBufferOperation<'a> { + fn to_owned(&self) -> OwnedBufferOperation { + match *self { + BufferOperation::Write { index, value } => BufferOperation::Write { + index, + value: value.to_vec().into_boxed_slice(), + }, + BufferOperation::Erase { page } => BufferOperation::Erase { page }, + } + } +} + +/// Controls when an operation is interrupted. +/// +/// This can be used to simulate power-offs while the device is writing to the storage or erasing a +/// page in the storage. +#[derive(Clone)] +enum Interruption { + /// Mutable operations have normal behavior. + Ready, + + /// If the delay is positive, mutable operations decrement it. If the count is zero, mutable + /// operations fail and are saved. + Armed { delay: usize }, + + /// Mutable operations panic. + Saved { operation: OwnedBufferOperation }, +} + +impl Interruption { + /// Arms an interruption for a given delay. + /// + /// # Panics + /// + /// Panics if an interruption is already armed. + fn arm(&mut self, delay: usize) { + match self { + Interruption::Ready => *self = Interruption::Armed { delay }, + _ => panic!(), + } + } + + /// Disarms an interruption. + /// + /// Returns the interrupted operation if any, otherwise the remaining delay. + /// + /// # Panics + /// + /// Panics if an interruption was not armed. + fn get(&mut self) -> Result { + let mut interruption = Interruption::Ready; + core::mem::swap(self, &mut interruption); + match interruption { + Interruption::Armed { delay } => Err(delay), + Interruption::Saved { operation } => Ok(operation), + _ => panic!(), + } + } + + /// Interrupts an operation if the delay is over. + /// + /// Decrements the delay if positive. Otherwise, the operation is stored and an error is + /// returned to interrupt the operation. + /// + /// # Panics + /// + /// Panics if an operation has already been interrupted and the interruption has not been + /// disarmed. + fn tick(&mut self, operation: &SharedBufferOperation) -> StorageResult<()> { + match self { + Interruption::Ready => (), + Interruption::Armed { delay } if *delay == 0 => { + let operation = operation.to_owned(); + *self = Interruption::Saved { operation }; + return Err(StorageError::CustomError); + } + Interruption::Armed { delay } => *delay -= 1, + Interruption::Saved { .. } => panic!(), + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const NUM_PAGES: usize = 2; + const OPTIONS: BufferOptions = BufferOptions { + word_size: 4, + page_size: 16, + max_word_writes: 2, + max_page_erases: 3, + strict_write: true, + }; + // Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at last one + // bit is changed. + const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff]; + const FIRST_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77]; + const SECOND_WORD: &[u8] = &[0xca, 0xc9, 0xa9, 0x65]; + const THIRD_WORD: &[u8] = &[0x88, 0x88, 0x88, 0x44]; + + fn new_storage() -> Box<[u8]> { + vec![0xff; NUM_PAGES * OPTIONS.page_size].into_boxed_slice() + } + + #[test] + fn words_are_decreasing() { + fn assert_is_decreasing(prev: &[u8], next: &[u8]) { + for (&prev, &next) in prev.iter().zip(next.iter()) { + assert_eq!(prev & next, next); + assert!(prev != next); + } + } + assert_is_decreasing(BLANK_WORD, FIRST_WORD); + assert_is_decreasing(FIRST_WORD, SECOND_WORD); + assert_is_decreasing(SECOND_WORD, THIRD_WORD); + } + + #[test] + fn options_ok() { + let buffer = BufferStorage::new(new_storage(), OPTIONS); + assert_eq!(buffer.word_size(), OPTIONS.word_size); + assert_eq!(buffer.page_size(), OPTIONS.page_size); + assert_eq!(buffer.num_pages(), NUM_PAGES); + assert_eq!(buffer.max_word_writes(), OPTIONS.max_word_writes); + assert_eq!(buffer.max_page_erases(), OPTIONS.max_page_erases); + } + + #[test] + fn read_write_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 0 }; + let next_index = StorageIndex { page: 0, byte: 4 }; + assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD); + buffer.write_slice(index, FIRST_WORD).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD); + assert_eq!(buffer.read_slice(next_index, 4).unwrap(), BLANK_WORD); + } + + #[test] + fn erase_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 0 }; + let other_index = StorageIndex { page: 1, byte: 0 }; + buffer.write_slice(index, FIRST_WORD).unwrap(); + buffer.write_slice(other_index, FIRST_WORD).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD); + assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD); + buffer.erase_page(0).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD); + assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD); + } + + #[test] + fn invalid_range() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 12 }; + let half_index = StorageIndex { page: 0, byte: 14 }; + let over_index = StorageIndex { page: 0, byte: 16 }; + let bad_page = StorageIndex { page: 2, byte: 0 }; + + // Reading a word in the storage is ok. + assert!(buffer.read_slice(index, 4).is_ok()); + // Reading a half-word in the storage is ok. + assert!(buffer.read_slice(half_index, 2).is_ok()); + // Reading even a single byte outside a page is not ok. + assert!(buffer.read_slice(over_index, 1).is_err()); + // But reading an empty slice just after a page is ok. + assert!(buffer.read_slice(over_index, 0).is_ok()); + // Reading even an empty slice outside the storage is not ok. + assert!(buffer.read_slice(bad_page, 0).is_err()); + + // Writing a word in the storage is ok. + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + // Writing an unaligned word is not ok. + assert!(buffer.write_slice(half_index, FIRST_WORD).is_err()); + // Writing a word outside a page is not ok. + assert!(buffer.write_slice(over_index, FIRST_WORD).is_err()); + // But writing an empty slice just after a page is ok. + assert!(buffer.write_slice(over_index, &[]).is_ok()); + // Writing even an empty slice outside the storage is not ok. + assert!(buffer.write_slice(bad_page, &[]).is_err()); + + // Only pages in the storage can be erased. + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(2).is_err()); + } + + #[test] + fn write_twice_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + } + + #[test] + fn write_twice_and_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 0 }; + let next_index = StorageIndex { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + assert!(buffer.write_slice(next_index, THIRD_WORD).is_ok()); + } + + #[test] + #[should_panic] + fn write_three_times_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + let _ = buffer.write_slice(index, THIRD_WORD); + } + + #[test] + fn write_twice_then_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 0 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + } + + #[test] + fn erase_three_times_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + } + + #[test] + fn erase_three_times_and_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(1).is_ok()); + } + + #[test] + #[should_panic] + fn erase_four_times_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + let _ = buffer.erase_page(0).is_ok(); + } + + #[test] + #[should_panic] + fn switch_zero_to_one_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = StorageIndex { page: 0, byte: 0 }; + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + let _ = buffer.write_slice(index, FIRST_WORD); + } + + #[test] + fn interrupt_delay_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + + // Interrupt the second operation. + buffer.arm_interruption(1); + + // The first operation should not fail. + buffer + .write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]) + .unwrap(); + // The delay should be decremented. + assert_eq!(buffer.disarm_interruption(), 0); + // The storage should have been modified. + assert_eq!(&buffer.storage[..8], &[0x5c; 8]); + assert!(buffer.storage[8..].iter().all(|&x| x == 0xff)); + } + + #[test] + fn interrupt_save_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + + // Interrupt the second operation. + buffer.arm_interruption(1); + + // The second operation should fail. + buffer + .write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]) + .unwrap(); + assert!(buffer + .write_slice(StorageIndex { page: 0, byte: 8 }, &[0x93; 8]) + .is_err()); + // The operation should represent the change. + buffer.corrupt_operation(Box::new(|_, value| assert_eq!(value, &[0x93; 8]))); + // The storage should not have been modified. + assert_eq!(&buffer.storage[..8], &[0x5c; 8]); + assert!(buffer.storage[8..].iter().all(|&x| x == 0xff)); + } +} diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index 954e66f..93d673d 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -13,11 +13,14 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] +#![feature(slice_fill)] #[macro_use] mod bitfield; +mod buffer; mod storage; mod store; +pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage}; pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult}; pub use self::store::{StoreError, StoreResult}; From d8f405baab695e7d38a471affa0c9828f85bb2a9 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 08:37:09 +0200 Subject: [PATCH 2/6] Add missing alloc crate --- libraries/persistent_store/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index 93d673d..942192f 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -15,6 +15,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![feature(slice_fill)] +extern crate alloc; + #[macro_use] mod bitfield; mod buffer; From f5e6f8728f1bdb043b5fab9d26e53d67814e826d Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 08:41:44 +0200 Subject: [PATCH 3/6] Do not use nightly features --- libraries/persistent_store/src/buffer.rs | 4 +++- libraries/persistent_store/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/persistent_store/src/buffer.rs b/libraries/persistent_store/src/buffer.rs index 076ba0d..a511141 100644 --- a/libraries/persistent_store/src/buffer.rs +++ b/libraries/persistent_store/src/buffer.rs @@ -310,7 +310,9 @@ impl Storage for BufferStorage { // Check and update counters. self.incr_page_erases(page); // Write to the storage. - self.storage[range].fill(0xff); + for byte in &mut self.storage[range] { + *byte = 0xff; + } Ok(()) } } diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index 942192f..fd85711 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -13,7 +13,6 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] -#![feature(slice_fill)] extern crate alloc; From 34ad35fdc16e1928a4baecce687a7953037bf7c1 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 11:52:21 +0200 Subject: [PATCH 4/6] Fix documentation --- libraries/persistent_store/src/buffer.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/libraries/persistent_store/src/buffer.rs b/libraries/persistent_store/src/buffer.rs index a511141..f344125 100644 --- a/libraries/persistent_store/src/buffer.rs +++ b/libraries/persistent_store/src/buffer.rs @@ -119,7 +119,9 @@ impl BufferStorage { /// /// # Panics /// - /// Panics if an interruption was not [armed] and if the interruption already triggered. + /// Panics if any of the following conditions hold: + /// - An interruption was not [armed]. + /// - An interruption was armed and it has triggered. /// /// [armed]: struct.BufferStorage.html#method.arm_interruption pub fn disarm_interruption(&mut self) -> usize { @@ -139,15 +141,18 @@ impl BufferStorage { /// Corrupts an interrupted operation. /// - /// Applies the [corruption function] to the storage. Counters are incremented if the operation - /// is complete at their level, i.e. a word is written and a page is erased if the corruption - /// function writes all its bits. + /// Applies the [corruption function] to the storage. Counters are updated accordingly: + /// - If a word is fully written, its counter is incremented regardless of whether other words + /// of the same operation have been fully written. + /// - If a page is fully erased, its counter is incremented (and its word counters are reset). /// /// # Panics /// - /// Panics if an interruption was not [armed] and if the interruption did not trigger. May also - /// panic if the corruption function corrupts more bits than allowed or if the interrupted - /// operation itself would have panicked. + /// Panics if any of the following conditions hold: + /// - An interruption was not [armed]. + /// - An interruption was armed but did not trigger. + /// - The corruption function corrupts more bits than allowed. + /// - The interrupted operation itself would have panicked. /// /// [armed]: struct.BufferStorage.html#method.arm_interruption /// [corruption function]: type.BufferCorruptFunction.html @@ -339,7 +344,7 @@ impl core::fmt::Display for BufferStorage { /// Represents a storage operation. /// -/// It is polymorphic over the ownership of the byte slice ot avoid unnecessary copies. +/// It is polymorphic over the ownership of the byte slice to avoid unnecessary copies. #[derive(Clone, Debug, PartialEq, Eq)] enum BufferOperation> { /// Represents a write operation. @@ -459,7 +464,7 @@ mod tests { max_page_erases: 3, strict_write: true, }; - // Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at last one + // Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at least one // bit is changed. const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff]; const FIRST_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77]; From f52ea953b80d85fb101e2a955e1537a9865cf3a7 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 15:38:21 +0200 Subject: [PATCH 5/6] Improve documentation --- libraries/persistent_store/src/buffer.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/persistent_store/src/buffer.rs b/libraries/persistent_store/src/buffer.rs index f344125..9a03c49 100644 --- a/libraries/persistent_store/src/buffer.rs +++ b/libraries/persistent_store/src/buffer.rs @@ -19,6 +19,9 @@ use alloc::vec; /// Simulates a flash storage using a buffer in memory. /// +/// This buffer storage can be used in place of an actual flash storage. It is particularly useful +/// for tests and fuzzing, for which it has dedicated functionalities. +/// /// This storage tracks how many times words are written between page erase cycles, how many times /// pages are erased, and whether an operation flips bits in the wrong direction (optional). /// Operations panic if those conditions are broken. This storage also permits to interrupt @@ -223,6 +226,11 @@ impl BufferStorage { /// starting at byte `index`, but actually only `value` was written. Word counters are /// incremented only if their value would change and they would be completely written. /// + /// # Preconditions + /// + /// - `index` must be word-aligned. + /// - `value` and `complete` must have the same word-aligned length. + /// /// # Panics /// /// Panics if the maximum number of writes per word is reached. From 99e2d0715696bbbf83e9cd963056da2898dab480 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 23 Oct 2020 16:38:48 +0200 Subject: [PATCH 6/6] Fix documentation --- libraries/persistent_store/src/buffer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/persistent_store/src/buffer.rs b/libraries/persistent_store/src/buffer.rs index 9a03c49..0059826 100644 --- a/libraries/persistent_store/src/buffer.rs +++ b/libraries/persistent_store/src/buffer.rs @@ -105,13 +105,15 @@ impl BufferStorage { /// /// Before each subsequent mutable operation (write or erase), the delay is decremented if /// positive. If the delay is elapsed, the operation is saved and an error is returned. - /// Subsequent operations will panic until the interrupted operation is [corrupted]. + /// Subsequent operations will panic until the interrupted operation is [corrupted] or the + /// interruption is [reset]. /// /// # Panics /// /// Panics if an interruption is already armed. /// /// [corrupted]: struct.BufferStorage.html#method.corrupt_operation + /// [reset]: struct.BufferStorage.html#method.reset_interruption pub fn arm_interruption(&mut self, delay: usize) { self.interruption.arm(delay); }