Skip to content

Commit

Permalink
Add docs, test and fuzz
Browse files Browse the repository at this point in the history
  • Loading branch information
diondokter committed Feb 13, 2024
1 parent e01b629 commit 08d3d1b
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 1 deletion.
63 changes: 62 additions & 1 deletion fuzz/fuzz_targets/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct Input {
enum Op {
Store(StoreOp),
Fetch(u8),
Remove(u8),
}

#[derive(Arbitrary, Debug, Clone)]
Expand Down Expand Up @@ -107,7 +108,11 @@ enum CacheType {

fn fuzz(ops: Input, mut cache: impl KeyCacheImpl<u8>) {
let mut flash = MockFlashBase::<PAGES, WORD_SIZE, WORDS_PER_PAGE>::new(
WriteCountCheck::OnceOnly,
if ops.ops.iter().any(|op| matches!(op, Op::Remove(_))) {
WriteCountCheck::Twice
} else {
WriteCountCheck::OnceOnly
},
Some(ops.fuel as u32),
true,
);
Expand Down Expand Up @@ -243,6 +248,62 @@ fn fuzz(ops: Input, mut cache: impl KeyCacheImpl<u8>) {
Err(e) => panic!("{e:?}"),
}
}
Op::Remove(key) => {
match block_on(sequential_storage::map::remove_item::<TestItem, _>(
&mut flash,
FLASH_RANGE,
&mut cache,
&mut buf.0,
key,
)) {
Ok(()) => {
map.remove(&key);
}
Err(MapError::Storage {
value: MockFlashError::EarlyShutoff(_),
backtrace: _backtrace,
}) => {
match block_on(sequential_storage::map::fetch_item::<TestItem, _>(
&mut flash,
FLASH_RANGE,
&mut cache,
&mut buf.0,
key,
)) {
Ok(Some(_)) => {
#[cfg(fuzzing_repro)]
eprintln!("Early shutoff when removing item {key}! Originated from:\n{_backtrace:#}");
}
_ => {
// Could not fetch the item we stored...
#[cfg(fuzzing_repro)]
eprintln!("Early shutoff when removing item {key}! (but it still removed fully). Originated from:\n{_backtrace:#}");
// Even though we got a shutoff, it still managed to store well
map.remove(&key);
}
}
}
Err(MapError::Corrupted {
backtrace: _backtrace,
}) if !corruption_repaired => {
#[cfg(fuzzing_repro)]
eprintln!(
"### Encountered curruption while fetching! Repairing now. Originated from:\n{_backtrace:#}"
);

block_on(sequential_storage::map::try_repair::<TestItem, _>(
&mut flash,
FLASH_RANGE,
&mut cache,
&mut buf.0,
))
.unwrap();
corruption_repaired = true;
retry = true;
}
Err(e) => panic!("{e:?}"),
}
}
}
}
}
Expand Down
97 changes: 97 additions & 0 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,13 +481,24 @@ pub async fn store_item<I: StorageItem, S: NorFlash>(
}
}

/// Fully remove an item. Additional calls to fetch with the same key will return None until
/// a new one is stored again.
///
/// <div class="warning">
/// This is really slow!
///
/// All items in flash have to be read and deserialized to find the items with the key.
/// This is unlikely to be cached well.
/// </div>
pub async fn remove_item<I: StorageItem, S: MultiwriteNorFlash>(
flash: &mut S,
flash_range: Range<u32>,
mut cache: impl KeyCacheImpl<I::Key>,
data_buffer: &mut [u8],
search_key: I::Key,
) -> Result<(), MapError<I::Error, S::Error>> {
cache.notice_key_erased(&search_key);

// Search for the last used page. We're gonna erase from the one after this first.
// If we get an early shutoff or cancellation, this will make it so that we don't return
// an old version of the key on the next fetch.
Expand Down Expand Up @@ -545,6 +556,9 @@ pub async fn remove_item<I: StorageItem, S: MultiwriteNorFlash>(
}
}

// We're done, we now know the cache is in a good state
cache.unmark_dirty();

Ok(())
}

Expand Down Expand Up @@ -1206,4 +1220,87 @@ mod tests {
assert_eq!(item.value, vec![i as u8; LENGHT_PER_KEY[i]]);
}
}

#[test]
async fn remove_items() {
let mut flash = mock_flash::MockFlashBase::<4, 1, 4096>::new(
mock_flash::WriteCountCheck::Twice,
None,
true,
);
let mut data_buffer = AlignedBuf([0; 128]);
const FLASH_RANGE: Range<u32> = 0x0000..0x4000;

// Add some data to flash
for j in 0..10 {
for i in 0..24 {
let item = MockStorageItem {
key: i as u8,
value: vec![i as u8; j + 2],
};

store_item::<_, _>(
&mut flash,
FLASH_RANGE,
cache::NoCache::new(),
&mut data_buffer,
&item,
)
.await
.unwrap();
}
}

for j in (0..24).rev() {
// Are all things still in flash that we expect?
for i in 0..=j {
assert!(fetch_item::<MockStorageItem, _>(
&mut flash,
FLASH_RANGE,
cache::NoCache::new(),
&mut data_buffer,
i
)
.await
.unwrap()
.is_some());
}

// Remove the item
remove_item::<MockStorageItem, _>(
&mut flash,
FLASH_RANGE,
cache::NoCache::new(),
&mut data_buffer,
j,
)
.await
.unwrap();

// Are all things still in flash that we expect?
for i in 0..j {
assert!(fetch_item::<MockStorageItem, _>(
&mut flash,
FLASH_RANGE,
cache::NoCache::new(),
&mut data_buffer,
i
)
.await
.unwrap()
.is_some());
}

assert!(fetch_item::<MockStorageItem, _>(
&mut flash,
FLASH_RANGE,
cache::NoCache::new(),
&mut data_buffer,
j
)
.await
.unwrap()
.is_none());
}
}
}

0 comments on commit 08d3d1b

Please sign in to comment.