extern crate std;
use std::vec::Vec;
use crate::{StorageInfo, StorageMedium};
pub struct TestStorage {
data: Vec<u8>,
info: StorageInfo,
erased_blocks: Vec<bool>,
write_count: usize,
fail_after_writes: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestStorageError {
OutOfBounds,
NotErased,
SimulatedFailure,
}
impl TestStorage {
pub fn new(info: StorageInfo) -> Self {
let data = std::vec![0xFF; info.size];
let num_erase_blocks = if let Some(erase_size) = info.erase_size {
info.size.div_ceil(erase_size.get())
} else {
0
};
Self {
data,
info,
erased_blocks: std::vec![false; num_erase_blocks],
write_count: 0,
fail_after_writes: None,
}
}
pub fn new_sram(size: usize) -> Self {
use core::num::NonZeroUsize;
Self::new(StorageInfo {
size,
erase_size: None,
write_size: NonZeroUsize::new(1).unwrap(),
})
}
pub fn new_flash(size: usize, erase_size: usize, write_size: usize) -> Self {
use core::num::NonZeroUsize;
Self::new(StorageInfo {
size,
erase_size: NonZeroUsize::new(erase_size),
write_size: NonZeroUsize::new(write_size).unwrap(),
})
}
pub fn reset_erase_state(&mut self) {
for block in &mut self.erased_blocks {
*block = false;
}
}
pub fn fail_after_writes(&mut self, count: Option<usize>) {
self.fail_after_writes = count;
self.write_count = 0;
}
pub fn write_count(&self) -> usize {
self.write_count
}
#[cfg(test)]
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.data
}
fn check_erase_alignment(&self, offset: usize, len: usize) {
if let Some(erase_size) = self.info.erase_size {
let erase_size = erase_size.get();
assert!(
offset.is_multiple_of(erase_size),
"erase offset {offset} is not aligned to erase_size {erase_size}"
);
assert!(
len.is_multiple_of(erase_size),
"erase length {len} is not aligned to erase_size {erase_size}"
);
}
}
fn check_write_alignment(&self, offset: usize, len: usize) {
let write_size = self.info.write_size.get();
assert!(
offset.is_multiple_of(write_size),
"write offset {offset} is not aligned to write_size {write_size}"
);
assert!(
len.is_multiple_of(write_size),
"write length {len} is not aligned to write_size {write_size}"
);
}
fn check_bounds(&self, offset: usize, len: usize) -> Result<(), TestStorageError> {
if offset.saturating_add(len) > self.info.size {
return Err(TestStorageError::OutOfBounds);
}
Ok(())
}
fn check_erased(&self, offset: usize, len: usize) -> Result<(), TestStorageError> {
if let Some(erase_size) = self.info.erase_size {
let erase_size = erase_size.get();
let start_block = offset / erase_size;
let end_block = (offset + len).div_ceil(erase_size);
for block in start_block..end_block {
if !self.erased_blocks.get(block).copied().unwrap_or(false) {
return Err(TestStorageError::NotErased);
}
}
}
Ok(())
}
}
impl StorageMedium for TestStorage {
type Error = TestStorageError;
fn info(&self) -> StorageInfo {
self.info
}
fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), Self::Error> {
self.check_bounds(offset, buf.len())?;
buf.copy_from_slice(&self.data[offset..offset + buf.len()]);
Ok(())
}
fn erase(&mut self, offset: usize, len: usize) -> Result<(), Self::Error> {
if self.info.erase_size.is_none() {
return Ok(());
}
self.check_bounds(offset, len)?;
self.check_erase_alignment(offset, len);
self.data[offset..offset + len].fill(0xFF);
let erase_size = self.info.erase_size.unwrap().get();
let start_block = offset / erase_size;
let end_block = (offset + len) / erase_size;
for block in start_block..end_block {
if let Some(erased) = self.erased_blocks.get_mut(block) {
*erased = true;
}
}
Ok(())
}
fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), Self::Error> {
if let Some(limit) = self.fail_after_writes
&& self.write_count >= limit
{
return Err(TestStorageError::SimulatedFailure);
}
self.check_bounds(offset, data.len())?;
self.check_write_alignment(offset, data.len());
self.check_erased(offset, data.len())?;
self.data[offset..offset + data.len()].copy_from_slice(data);
if let Some(erase_size) = self.info.erase_size {
let erase_size = erase_size.get();
let start_block = offset / erase_size;
let end_block = (offset + data.len()).div_ceil(erase_size);
for block in start_block..end_block {
if let Some(erased) = self.erased_blocks.get_mut(block) {
*erased = false;
}
}
}
self.write_count += 1;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sram_read_write() {
let mut storage = TestStorage::new_sram(1024);
storage.write(0, &[1, 2, 3, 4]).unwrap();
storage.write(100, &[5, 6, 7, 8]).unwrap();
let mut buf = [0u8; 4];
storage.read(0, &mut buf).unwrap();
assert_eq!(buf, [1, 2, 3, 4]);
storage.read(100, &mut buf).unwrap();
assert_eq!(buf, [5, 6, 7, 8]);
}
#[test]
fn sram_out_of_bounds() {
let mut storage = TestStorage::new_sram(100);
storage.write(96, &[1, 2, 3, 4]).unwrap();
let result = storage.write(97, &[1, 2, 3, 4]);
assert_eq!(result, Err(TestStorageError::OutOfBounds));
let mut buf = [0u8; 4];
let result = storage.read(97, &mut buf);
assert_eq!(result, Err(TestStorageError::OutOfBounds));
}
#[test]
fn flash_requires_erase() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
storage.reset_erase_state();
let result = storage.write(0, &[1, 2, 3, 4]);
assert_eq!(result, Err(TestStorageError::NotErased));
storage.erase(0, 256).unwrap();
storage.write(0, &[1, 2, 3, 4]).unwrap();
let mut buf = [0u8; 4];
storage.read(0, &mut buf).unwrap();
assert_eq!(buf, [1, 2, 3, 4]);
}
#[test]
#[should_panic(expected = "erase offset")]
fn flash_erase_alignment_offset() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
let _ = storage.erase(100, 256);
}
#[test]
#[should_panic(expected = "erase length")]
fn flash_erase_alignment_length() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
let _ = storage.erase(0, 100);
}
#[test]
#[should_panic(expected = "write offset")]
fn flash_write_alignment_offset() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
storage.erase(0, 256).unwrap();
let _ = storage.write(1, &[1, 2, 3, 4]);
}
#[test]
#[should_panic(expected = "write length")]
fn flash_write_alignment_length() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
storage.erase(0, 256).unwrap();
let _ = storage.write(0, &[1, 2, 3]);
}
#[test]
fn flash_erase_fills_with_ff() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
storage.erase(0, 256).unwrap();
storage.write(0, &[1, 2, 3, 4]).unwrap();
storage.erase(0, 256).unwrap();
let mut buf = [0u8; 4];
storage.read(0, &mut buf).unwrap();
assert_eq!(buf, [0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn flash_write_after_write_fails() {
let mut storage = TestStorage::new_flash(1024, 256, 4);
storage.erase(0, 256).unwrap();
storage.write(0, &[1, 2, 3, 4]).unwrap();
let result = storage.write(4, &[5, 6, 7, 8]);
assert_eq!(result, Err(TestStorageError::NotErased));
storage.erase(0, 256).unwrap();
storage.write(4, &[5, 6, 7, 8]).unwrap();
}
#[test]
fn simulated_write_failure() {
let mut storage = TestStorage::new_sram(1024);
storage.fail_after_writes(Some(2));
storage.write(0, &[1]).unwrap();
assert_eq!(storage.write_count(), 1);
storage.write(1, &[2]).unwrap();
assert_eq!(storage.write_count(), 2);
let result = storage.write(2, &[3]);
assert_eq!(result, Err(TestStorageError::SimulatedFailure));
assert_eq!(storage.write_count(), 2);
storage.fail_after_writes(None);
storage.write(2, &[3]).unwrap();
}
}