use alloc::borrow::{Borrow, Cow};
use alloc::boxed::Box;
use alloc::vec;
use crate::{Storage, StorageError, StorageIndex, StorageResult};
#[derive(Clone)]
pub struct BufferStorage {
storage: Box<[u8]>,
options: BufferOptions,
word_writes: Box<[usize]>,
page_erases: Box<[usize]>,
interruption: Interruption,
}
#[derive(Clone, Debug)]
pub struct BufferOptions {
pub word_size: usize,
pub page_size: usize,
pub max_word_writes: usize,
pub max_page_erases: usize,
pub strict_mode: bool,
}
pub type BufferCorruptFunction<'a> = Box<dyn FnOnce(&mut [u8], &[u8]) + 'a>;
impl BufferStorage {
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
}
pub fn arm_interruption(&mut self, delay: usize) {
self.interruption.arm(delay);
}
pub fn disarm_interruption(&mut self) -> usize {
self.interruption.get().err().unwrap()
}
pub fn reset_interruption(&mut self) {
let _ = self.interruption.get();
}
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);
}
pub fn get_word_writes(&self, word: usize) -> usize {
self.word_writes[word]
}
pub fn get_page_erases(&self, page: usize) -> usize {
self.page_erases[page]
}
pub fn set_page_erases(&mut self, page: usize, cycle: usize) {
self.page_erases[page] = cycle;
}
fn is_word_aligned(&self, x: usize) -> bool {
x & (self.options.word_size - 1) == 0
}
fn is_page_aligned(&self, x: usize) -> bool {
x & (self.options.page_size - 1) == 0
}
fn incr_page_erases(&mut self, page: usize) {
if self.options.strict_mode {
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;
}
}
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 };
if value[range.clone()] != complete[range.clone()] {
continue;
}
if value[range.clone()] == self.storage[index ..][range] {
continue;
}
let word = index / word_size + i;
if self.options.strict_mode {
assert!(self.word_writes[word] < self.max_word_writes());
}
self.word_writes[word] += 1;
}
}
fn operation_range(
&self, operation: &BufferOperation<impl Borrow<[u8]>>,
) -> StorageResult<core::ops::Range<usize>> {
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<Cow<[u8]>> {
Ok(Cow::Borrowed(&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)?;
self.interruption.tick(&operation)?;
self.incr_word_writes(range.start, value, value);
if self.options.strict_mode {
for (byte, &val) in range.clone().zip(value.iter()) {
assert_eq!(self.storage[byte] & val, val);
}
}
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)?;
self.interruption.tick(&operation)?;
self.incr_page_erases(page);
for byte in &mut self.storage[range] {
*byte = 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(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum BufferOperation<ByteSlice: Borrow<[u8]>> {
Write {
index: StorageIndex,
value: ByteSlice,
},
Erase {
page: usize,
},
}
type OwnedBufferOperation = BufferOperation<Box<[u8]>>;
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 },
}
}
}
#[derive(Clone)]
enum Interruption {
Ready,
Armed { delay: usize },
Saved { operation: OwnedBufferOperation },
}
impl Interruption {
fn arm(&mut self, delay: usize) {
match self {
Interruption::Ready => *self = Interruption::Armed { delay },
_ => panic!(),
}
}
fn get(&mut self) -> Result<OwnedBufferOperation, usize> {
let mut interruption = Interruption::Ready;
core::mem::swap(self, &mut interruption);
match interruption {
Interruption::Armed { delay } => Err(delay),
Interruption::Saved { operation } => Ok(operation),
_ => panic!(),
}
}
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_mode: true,
};
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 };
assert!(buffer.read_slice(index, 4).is_ok());
assert!(buffer.read_slice(half_index, 2).is_ok());
assert!(buffer.read_slice(over_index, 1).is_err());
assert!(buffer.read_slice(over_index, 0).is_ok());
assert!(buffer.read_slice(bad_page, 0).is_err());
assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
assert!(buffer.write_slice(half_index, FIRST_WORD).is_err());
assert!(buffer.write_slice(over_index, FIRST_WORD).is_err());
assert!(buffer.write_slice(over_index, &[]).is_ok());
assert!(buffer.write_slice(bad_page, &[]).is_err());
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);
buffer.arm_interruption(1);
buffer.write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]).unwrap();
assert_eq!(buffer.disarm_interruption(), 0);
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);
buffer.arm_interruption(1);
buffer.write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]).unwrap();
assert!(buffer.write_slice(StorageIndex { page: 0, byte: 8 }, &[0x93; 8]).is_err());
buffer.corrupt_operation(Box::new(|_, value| assert_eq!(value, &[0x93; 8])));
assert_eq!(&buffer.storage[.. 8], &[0x5c; 8]);
assert!(buffer.storage[8 ..].iter().all(|&x| x == 0xff));
}
}