use core::mem::{size_of, MaybeUninit};
use cache::CacheImpl;
use embedded_storage_async::nor_flash::MultiwriteNorFlash;
use crate::item::{find_next_free_item_spot, Item, ItemHeader, ItemIter};
use self::{
cache::{KeyCacheImpl, PrivateKeyCacheImpl},
item::{ItemHeaderIter, ItemUnborrowed},
};
use super::*;
pub async fn fetch_item<'d, K: Key, V: Value<'d>, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &'d mut [u8],
search_key: &K,
) -> Result<Option<V>, Error<S::Error>> {
let result = run_with_auto_repair!(
function = {
fetch_item_with_location(flash, flash_range.clone(), cache, data_buffer, search_key)
.await
},
repair = try_repair::<K, _>(flash, flash_range.clone(), cache, data_buffer).await?
);
let Some((item, _, item_key_len)) = result? else {
return Ok(None);
};
let data_len = item.header.length as usize;
let item_key_len = match item_key_len {
Some(item_key_len) => item_key_len,
None => K::get_len(&data_buffer[..data_len])?,
};
Ok(Some(
V::deserialize_from(&data_buffer[item_key_len..][..data_len - item_key_len])
.map_err(Error::SerializationError)?,
))
}
#[allow(clippy::type_complexity)]
async fn fetch_item_with_location<K: Key, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl PrivateKeyCacheImpl<K>,
data_buffer: &mut [u8],
search_key: &K,
) -> Result<Option<(ItemUnborrowed, u32, Option<usize>)>, Error<S::Error>> {
assert_eq!(flash_range.start % S::ERASE_SIZE as u32, 0);
assert_eq!(flash_range.end % S::ERASE_SIZE as u32, 0);
assert!(flash_range.end - flash_range.start >= S::ERASE_SIZE as u32 * 2);
assert!(S::ERASE_SIZE >= S::WORD_SIZE * 3);
assert!(S::WORD_SIZE <= MAX_WORD_SIZE);
if cache.is_dirty() {
cache.invalidate_cache_state();
}
'cache: {
if let Some(cached_location) = cache.key_location(search_key) {
let page_index = calculate_page_index::<S>(flash_range.clone(), cached_location);
let page_data_end_address =
calculate_page_end_address::<S>(flash_range.clone(), page_index)
- S::WORD_SIZE as u32;
let Some(header) =
ItemHeader::read_new(flash, cached_location, page_data_end_address).await?
else {
if cfg!(feature = "_test") {
panic!("Wrong cache value. Addr: {cached_location}");
}
cache.invalidate_cache_state();
break 'cache;
};
let item = header
.read_item(flash, data_buffer, cached_location, page_data_end_address)
.await?;
match item {
item::MaybeItem::Corrupted(_, _) | item::MaybeItem::Erased(_, _) => {
if cfg!(feature = "_test") {
panic!("Wrong cache value. Addr: {cached_location}");
}
cache.invalidate_cache_state();
break 'cache;
}
item::MaybeItem::Present(item) => {
return Ok(Some((item.unborrow(), cached_location, None)));
}
}
}
}
let mut last_used_page =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen).await?;
if last_used_page.is_none() {
if let Some(first_open_page) =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::Open).await?
{
let previous_page = previous_page::<S>(flash_range.clone(), first_open_page);
if get_page_state(flash, flash_range.clone(), cache, previous_page)
.await?
.is_closed()
{
last_used_page = Some(previous_page);
} else {
cache.unmark_dirty();
return Ok(None);
}
} else {
return Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
});
}
}
let mut current_page_to_check = last_used_page.unwrap();
let mut newest_found_item_data = None;
loop {
let page_data_start_address =
calculate_page_address::<S>(flash_range.clone(), current_page_to_check)
+ S::WORD_SIZE as u32;
let page_data_end_address =
calculate_page_end_address::<S>(flash_range.clone(), current_page_to_check)
- S::WORD_SIZE as u32;
let mut it = ItemIter::new(page_data_start_address, page_data_end_address);
while let Some((item, address)) = it.next(flash, data_buffer).await? {
let (found_key, found_key_len) = K::deserialize_from(item.data())?;
if found_key == *search_key {
newest_found_item_data = Some((address, found_key_len));
}
}
if let Some((newest_found_item_address, _)) = newest_found_item_data.as_ref() {
cache.notice_key_location(search_key, *newest_found_item_address, false);
break;
}
let previous_page = previous_page::<S>(flash_range.clone(), current_page_to_check);
if get_page_state(flash, flash_range.clone(), cache, previous_page).await?
!= PageState::Closed
{
cache.unmark_dirty();
return Ok(None);
}
current_page_to_check = previous_page;
}
cache.unmark_dirty();
if let Some((newest_found_item_address, newest_found_item_key_len)) = newest_found_item_data {
let item = ItemHeader::read_new(flash, newest_found_item_address, u32::MAX)
.await?
.ok_or_else(|| {
Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
}
})?
.read_item(flash, data_buffer, newest_found_item_address, u32::MAX)
.await?;
Ok(Some((
item.unwrap()?.unborrow(),
newest_found_item_address,
Some(newest_found_item_key_len),
)))
} else {
Ok(None)
}
}
pub async fn store_item<'d, K: Key, V: Value<'d>, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
key: &K,
item: &V,
) -> Result<(), Error<S::Error>> {
run_with_auto_repair!(
function =
store_item_inner(flash, flash_range.clone(), cache, data_buffer, key, item).await,
repair = try_repair::<K, _>(flash, flash_range.clone(), cache, data_buffer).await?
)
}
async fn store_item_inner<K: Key, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
key: &K,
item: &dyn Value<'_>,
) -> Result<(), Error<S::Error>> {
assert_eq!(flash_range.start % S::ERASE_SIZE as u32, 0);
assert_eq!(flash_range.end % S::ERASE_SIZE as u32, 0);
assert!(flash_range.end - flash_range.start >= S::ERASE_SIZE as u32 * 2);
assert!(S::ERASE_SIZE >= S::WORD_SIZE * 3);
assert!(S::WORD_SIZE <= MAX_WORD_SIZE);
if cache.is_dirty() {
cache.invalidate_cache_state();
}
let mut recursion_level = 0;
loop {
if recursion_level == get_pages::<S>(flash_range.clone(), 0).count() {
cache.unmark_dirty();
return Err(Error::FullStorage);
}
let next_page_to_use = if let Some(partial_open_page) =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen).await?
{
if !get_page_state(
flash,
flash_range.clone(),
cache,
next_page::<S>(flash_range.clone(), partial_open_page),
)
.await?
.is_open()
{
return Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
});
}
let page_data_start_address =
calculate_page_address::<S>(flash_range.clone(), partial_open_page)
+ S::WORD_SIZE as u32;
let page_data_end_address =
calculate_page_end_address::<S>(flash_range.clone(), partial_open_page)
- S::WORD_SIZE as u32;
let key_len = key.serialize_into(data_buffer)?;
let item_data_length = key_len
+ item
.serialize_into(&mut data_buffer[key_len..])
.map_err(Error::SerializationError)?;
if item_data_length > u16::MAX as usize
|| item_data_length
> calculate_page_size::<S>()
.saturating_sub(ItemHeader::data_address::<S>(0) as usize)
{
cache.unmark_dirty();
return Err(Error::ItemTooBig);
}
let free_spot_address = find_next_free_item_spot(
flash,
flash_range.clone(),
cache,
page_data_start_address,
page_data_end_address,
item_data_length as u32,
)
.await?;
match free_spot_address {
Some(free_spot_address) => {
cache.notice_key_location(key, free_spot_address, true);
Item::write_new(
flash,
flash_range.clone(),
cache,
free_spot_address,
&data_buffer[..item_data_length],
)
.await?;
cache.unmark_dirty();
return Ok(());
}
None => {
close_page(flash, flash_range.clone(), cache, partial_open_page).await?;
Some(next_page::<S>(flash_range.clone(), partial_open_page))
}
}
} else {
None
};
match next_page_to_use {
Some(next_page_to_use) => {
let next_page_state =
get_page_state(flash, flash_range.clone(), cache, next_page_to_use).await?;
if !next_page_state.is_open() {
return Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
});
}
partial_close_page(flash, flash_range.clone(), cache, next_page_to_use).await?;
let next_buffer_page = next_page::<S>(flash_range.clone(), next_page_to_use);
let next_buffer_page_state =
get_page_state(flash, flash_range.clone(), cache, next_buffer_page).await?;
if !next_buffer_page_state.is_open() {
migrate_items::<K, _>(
flash,
flash_range.clone(),
cache,
data_buffer,
next_buffer_page,
next_page_to_use,
)
.await?;
}
}
None => {
let first_open_page =
match find_first_page(flash, flash_range.clone(), cache, 0, PageState::Open)
.await?
{
Some(first_open_page) => first_open_page,
None => {
return Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
});
}
};
partial_close_page(flash, flash_range.clone(), cache, first_open_page).await?;
}
}
recursion_level += 1;
}
}
pub async fn remove_item<K: Key, S: MultiwriteNorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
search_key: &K,
) -> Result<(), Error<S::Error>> {
run_with_auto_repair!(
function = remove_item_inner::<K, _>(
flash,
flash_range.clone(),
cache,
data_buffer,
Some(search_key)
)
.await,
repair = try_repair::<K, _>(flash, flash_range.clone(), cache, data_buffer).await?
)
}
pub async fn remove_all_items<K: Key, S: MultiwriteNorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
) -> Result<(), Error<S::Error>> {
run_with_auto_repair!(
function =
remove_item_inner::<K, _>(flash, flash_range.clone(), cache, data_buffer, None).await,
repair = try_repair::<K, _>(flash, flash_range.clone(), cache, data_buffer).await?
)
}
async fn remove_item_inner<K: Key, S: MultiwriteNorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
search_key: Option<&K>,
) -> Result<(), Error<S::Error>> {
if let Some(key) = &search_key {
cache.notice_key_erased(key);
} else {
cache.invalidate_cache_state();
}
let last_used_page =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen)
.await?
.unwrap_or_default();
for page_index in get_pages::<S>(
flash_range.clone(),
next_page::<S>(flash_range.clone(), last_used_page),
) {
if get_page_state(flash, flash_range.clone(), cache, page_index)
.await?
.is_open()
{
continue;
}
let page_data_start_address =
calculate_page_address::<S>(flash_range.clone(), page_index) + S::WORD_SIZE as u32;
let page_data_end_address =
calculate_page_end_address::<S>(flash_range.clone(), page_index) - S::WORD_SIZE as u32;
let mut item_headers = ItemHeaderIter::new(page_data_start_address, page_data_end_address);
while let (Some(item_header), item_address) = item_headers.next(flash).await? {
let item = item_header
.read_item(flash, data_buffer, item_address, page_data_end_address)
.await?;
match item {
item::MaybeItem::Corrupted(_, _) => continue,
item::MaybeItem::Erased(_, _) => continue,
item::MaybeItem::Present(item) => {
let item_match = match search_key {
Some(search_key) => K::deserialize_from(item.data())?.0 == *search_key,
_ => true,
};
if item_match {
item.header
.erase_data(flash, flash_range.clone(), cache, item_address)
.await?;
}
}
}
}
}
cache.unmark_dirty();
Ok(())
}
pub struct MapItemIter<'d, 'c, S: NorFlash, CI: CacheImpl> {
flash: &'d mut S,
flash_range: Range<u32>,
first_page: usize,
cache: &'c mut CI,
current_page_index: usize,
pub(crate) current_iter: ItemIter,
}
impl<S: NorFlash, CI: CacheImpl> MapItemIter<'_, '_, S, CI> {
pub async fn next<'a, K: Key, V: Value<'a>>(
&mut self,
data_buffer: &'a mut [u8],
) -> Result<Option<(K, V)>, Error<S::Error>> {
let item = loop {
if let Some((item, _address)) = self.current_iter.next(self.flash, data_buffer).await? {
break item;
}
loop {
self.current_page_index =
next_page::<S>(self.flash_range.clone(), self.current_page_index);
if self.current_page_index == self.first_page {
return Ok(None);
}
match get_page_state::<S>(
self.flash,
self.flash_range.clone(),
self.cache,
self.current_page_index,
)
.await
{
Ok(PageState::Closed) | Ok(PageState::PartialOpen) => {
self.current_iter = ItemIter::new(
calculate_page_address::<S>(
self.flash_range.clone(),
self.current_page_index,
) + S::WORD_SIZE as u32,
calculate_page_end_address::<S>(
self.flash_range.clone(),
self.current_page_index,
) - S::WORD_SIZE as u32,
);
break;
}
_ => continue,
}
}
};
let data_len = item.header.length as usize;
let (key, key_len) = K::deserialize_from(item.data())?;
Ok(Some((
key,
V::deserialize_from(&data_buffer[key_len..][..data_len - key_len])
.map_err(Error::SerializationError)?,
)))
}
}
pub async fn fetch_all_items<'d, 'c, K: Key, S: NorFlash, CI: KeyCacheImpl<K>>(
flash: &'d mut S,
flash_range: Range<u32>,
cache: &'c mut CI,
data_buffer: &mut [u8],
) -> Result<MapItemIter<'d, 'c, S, CI>, Error<S::Error>> {
let first_page = run_with_auto_repair!(
function = {
match find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen)
.await?
{
Some(last_used_page) => {
Ok(next_page::<S>(flash_range.clone(), last_used_page))
}
None => {
if let Some(first_open_page) =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::Open)
.await?
{
let previous_page =
previous_page::<S>(flash_range.clone(), first_open_page);
if get_page_state(flash, flash_range.clone(), cache, previous_page)
.await?
.is_closed()
{
Ok(first_open_page)
} else {
cache.unmark_dirty();
Ok(0)
}
} else {
Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
})
}
}
}
},
repair = try_repair::<K, _>(flash, flash_range.clone(), cache, data_buffer).await?
)?;
Ok(MapItemIter {
flash,
flash_range: flash_range.clone(),
first_page,
cache,
current_page_index: first_page,
current_iter: ItemIter::new(
calculate_page_address::<S>(flash_range.clone(), first_page) + S::WORD_SIZE as u32,
calculate_page_end_address::<S>(flash_range.clone(), first_page) - S::WORD_SIZE as u32,
),
})
}
pub trait Key: Eq + Clone + Sized {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError>;
fn deserialize_from(buffer: &[u8]) -> Result<(Self, usize), SerializationError>;
fn get_len(buffer: &[u8]) -> Result<usize, SerializationError> {
Self::deserialize_from(buffer).map(|(_, len)| len)
}
}
macro_rules! impl_key_num {
($int:ty) => {
impl Key for $int {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
let len = size_of::<Self>();
if buffer.len() < len {
return Err(SerializationError::BufferTooSmall);
}
buffer[..len].copy_from_slice(&self.to_le_bytes());
Ok(len)
}
fn deserialize_from(buffer: &[u8]) -> Result<(Self, usize), SerializationError> {
let len = size_of::<Self>();
if buffer.len() < len {
return Err(SerializationError::BufferTooSmall);
}
Ok((
Self::from_le_bytes(buffer[..len].try_into().unwrap()),
size_of::<Self>(),
))
}
fn get_len(_buffer: &[u8]) -> Result<usize, SerializationError> {
Ok(size_of::<Self>())
}
}
};
}
impl_key_num!(u8);
impl_key_num!(u16);
impl_key_num!(u32);
impl_key_num!(u64);
impl_key_num!(u128);
impl_key_num!(i8);
impl_key_num!(i16);
impl_key_num!(i32);
impl_key_num!(i64);
impl_key_num!(i128);
impl<const N: usize> Key for [u8; N] {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
if buffer.len() < N {
return Err(SerializationError::BufferTooSmall);
}
buffer[..N].copy_from_slice(self);
Ok(N)
}
fn deserialize_from(buffer: &[u8]) -> Result<(Self, usize), SerializationError> {
if buffer.len() < N {
return Err(SerializationError::BufferTooSmall);
}
Ok((buffer[..N].try_into().unwrap(), N))
}
fn get_len(_buffer: &[u8]) -> Result<usize, SerializationError> {
Ok(N)
}
}
pub trait Value<'a> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError>;
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, SerializationError>
where
Self: Sized;
}
impl<'a> Value<'a> for bool {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
<u8 as Value>::serialize_into(&(*self as u8), buffer)
}
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, SerializationError>
where
Self: Sized,
{
Ok(<u8 as Value>::deserialize_from(buffer)? != 0)
}
}
impl<'a, T: Value<'a>> Value<'a> for Option<T> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
if let Some(val) = self {
<bool as Value>::serialize_into(&true, buffer)?;
<T as Value>::serialize_into(val, buffer)
} else {
<bool as Value>::serialize_into(&false, buffer)
}
}
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, SerializationError>
where
Self: Sized,
{
if <bool as Value>::deserialize_from(buffer)? {
Ok(Some(<T as Value>::deserialize_from(buffer)?))
} else {
Ok(None)
}
}
}
impl<'a, T: Value<'a>, const N: usize> Value<'a> for [T; N] {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
if buffer.len() < size_of::<T>() * N {
return Err(SerializationError::BufferTooSmall);
}
let mut size = 0;
for v in self {
size += <T as Value>::serialize_into(v, &mut buffer[size..])?;
}
Ok(size)
}
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, SerializationError>
where
Self: Sized,
{
let mut array = MaybeUninit::<[T; N]>::uninit();
if N == 0 {
return Ok(unsafe { array.assume_init() });
}
let ptr = array.as_mut_ptr() as *mut T;
unsafe {
for i in 0..N {
*ptr.add(i) = <T as Value>::deserialize_from(&buffer[i * size_of::<T>()..])?;
}
Ok(array.assume_init())
}
}
}
impl<'a> Value<'a> for &'a [u8] {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
if buffer.len() < self.len() {
return Err(SerializationError::BufferTooSmall);
}
buffer[..self.len()].copy_from_slice(self);
Ok(self.len())
}
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, SerializationError>
where
Self: Sized,
{
Ok(buffer)
}
}
macro_rules! impl_map_item_num {
($int:ty) => {
impl<'a> Value<'a> for $int {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
buffer[..core::mem::size_of::<Self>()].copy_from_slice(&self.to_le_bytes());
Ok(core::mem::size_of::<Self>())
}
fn deserialize_from(buffer: &[u8]) -> Result<Self, SerializationError> {
Ok(Self::from_le_bytes(
buffer[..core::mem::size_of::<Self>()]
.try_into()
.map_err(|_| SerializationError::BufferTooSmall)?,
))
}
}
};
}
impl_map_item_num!(u8);
impl_map_item_num!(u16);
impl_map_item_num!(u32);
impl_map_item_num!(u64);
impl_map_item_num!(u128);
impl_map_item_num!(i8);
impl_map_item_num!(i16);
impl_map_item_num!(i32);
impl_map_item_num!(i64);
impl_map_item_num!(i128);
impl_map_item_num!(f32);
impl_map_item_num!(f64);
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub enum SerializationError {
BufferTooSmall,
InvalidData,
InvalidFormat,
Custom(i32),
}
impl core::fmt::Display for SerializationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SerializationError::BufferTooSmall => write!(f, "Buffer too small"),
SerializationError::InvalidData => write!(f, "Invalid data"),
SerializationError::InvalidFormat => write!(f, "Invalid format"),
SerializationError::Custom(val) => write!(f, "Custom error: {val}"),
}
}
}
async fn migrate_items<K: Key, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl PrivateKeyCacheImpl<K>,
data_buffer: &mut [u8],
source_page: usize,
target_page: usize,
) -> Result<(), Error<S::Error>> {
let mut next_page_write_address =
calculate_page_address::<S>(flash_range.clone(), target_page) + S::WORD_SIZE as u32;
let mut it = ItemIter::new(
calculate_page_address::<S>(flash_range.clone(), source_page) + S::WORD_SIZE as u32,
calculate_page_end_address::<S>(flash_range.clone(), source_page) - S::WORD_SIZE as u32,
);
while let Some((item, item_address)) = it.next(flash, data_buffer).await? {
let (key, _) = K::deserialize_from(item.data())?;
let (_, data_buffer) = item.destruct();
cache.unmark_dirty();
let Some((found_item, found_address, _)) =
fetch_item_with_location::<K, S>(flash, flash_range.clone(), cache, data_buffer, &key)
.await?
else {
return Err(Error::Corrupted {
#[cfg(feature = "_test")]
backtrace: std::backtrace::Backtrace::capture(),
});
};
let found_item = found_item.reborrow(data_buffer);
if found_address == item_address {
cache.notice_key_location(&key, next_page_write_address, true);
found_item
.write(flash, flash_range.clone(), cache, next_page_write_address)
.await?;
next_page_write_address = found_item
.header
.next_item_address::<S>(next_page_write_address);
}
}
open_page(flash, flash_range.clone(), cache, source_page).await?;
Ok(())
}
async fn try_repair<K: Key, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
cache: &mut impl KeyCacheImpl<K>,
data_buffer: &mut [u8],
) -> Result<(), Error<S::Error>> {
cache.invalidate_cache_state();
crate::try_general_repair(flash, flash_range.clone(), cache).await?;
if let Some(partial_open_page) =
find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen).await?
{
let buffer_page = next_page::<S>(flash_range.clone(), partial_open_page);
if !get_page_state(flash, flash_range.clone(), cache, buffer_page)
.await?
.is_open()
{
open_page(flash, flash_range.clone(), cache, partial_open_page).await?;
partial_close_page(flash, flash_range.clone(), cache, partial_open_page).await?;
migrate_items::<K, _>(
flash,
flash_range.clone(),
cache,
data_buffer,
buffer_page,
partial_open_page,
)
.await?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use futures_test::test;
type MockFlashBig = mock_flash::MockFlashBase<4, 4, 256>;
type MockFlashTiny = mock_flash::MockFlashBase<2, 1, 32>;
#[test]
async fn store_and_fetch() {
let mut flash = MockFlashBig::default();
let flash_range = 0x000..0x1000;
let mut data_buffer = AlignedBuf([0; 128]);
let start_snapshot = flash.stats_snapshot();
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0,
)
.await
.unwrap();
assert_eq!(item, None);
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&60,
)
.await
.unwrap();
assert_eq!(item, None);
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0xFF,
)
.await
.unwrap();
assert_eq!(item, None);
store_item(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0u8,
&[5u8],
)
.await
.unwrap();
store_item(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0u8,
&[5u8, 6],
)
.await
.unwrap();
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0,
)
.await
.unwrap()
.unwrap();
assert_eq!(item, &[5, 6]);
store_item(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&1u8,
&[2u8, 2, 2, 2, 2, 2],
)
.await
.unwrap();
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&0,
)
.await
.unwrap()
.unwrap();
assert_eq!(item, &[5, 6]);
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&1,
)
.await
.unwrap()
.unwrap();
assert_eq!(item, &[2, 2, 2, 2, 2, 2]);
for index in 0..4000 {
store_item(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&((index % 10) as u8),
&vec![(index % 10) as u8 * 2; index % 10].as_slice(),
)
.await
.unwrap();
}
for i in 0..10 {
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
)
.await
.unwrap()
.unwrap();
assert_eq!(item, &vec![(i % 10) * 2; (i % 10) as usize]);
}
for _ in 0..4000 {
store_item(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&11u8,
&[0; 10],
)
.await
.unwrap();
}
for i in 0..10 {
let item = fetch_item::<u8, &[u8], _>(
&mut flash,
flash_range.clone(),
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
)
.await
.unwrap()
.unwrap();
assert_eq!(item, &vec![(i % 10) * 2; (i % 10) as usize]);
}
println!("{:?}", start_snapshot.compare_to(flash.stats_snapshot()),);
}
#[test]
async fn store_too_many_items() {
const UPPER_BOUND: u8 = 3;
let mut tiny_flash = MockFlashTiny::default();
let mut data_buffer = AlignedBuf([0; 128]);
for i in 0..UPPER_BOUND {
println!("Storing {i:?}");
store_item(
&mut tiny_flash,
0x00..0x40,
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
&vec![i; i as usize].as_slice(),
)
.await
.unwrap();
}
assert_eq!(
store_item(
&mut tiny_flash,
0x00..0x40,
&mut cache::NoCache::new(),
&mut data_buffer,
&UPPER_BOUND,
&vec![0; UPPER_BOUND as usize].as_slice(),
)
.await,
Err(Error::FullStorage)
);
for i in 0..UPPER_BOUND {
let item = fetch_item::<u8, &[u8], _>(
&mut tiny_flash,
0x00..0x40,
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
)
.await
.unwrap()
.unwrap();
println!("Fetched {item:?}");
assert_eq!(item, vec![i; i as usize]);
}
}
#[test]
async fn store_too_many_items_big() {
const UPPER_BOUND: u8 = 68;
let mut big_flash = MockFlashBig::default();
let mut data_buffer = AlignedBuf([0; 128]);
for i in 0..UPPER_BOUND {
println!("Storing {i:?}");
store_item(
&mut big_flash,
0x0000..0x1000,
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
&vec![i; i as usize].as_slice(),
)
.await
.unwrap();
}
assert_eq!(
store_item(
&mut big_flash,
0x0000..0x1000,
&mut cache::NoCache::new(),
&mut data_buffer,
&UPPER_BOUND,
&vec![0; UPPER_BOUND as usize].as_slice(),
)
.await,
Err(Error::FullStorage)
);
for i in 0..UPPER_BOUND {
let item = fetch_item::<u8, &[u8], _>(
&mut big_flash,
0x0000..0x1000,
&mut cache::NoCache::new(),
&mut data_buffer,
&i,
)
.await
.unwrap()
.unwrap();
println!("Fetched {item:?}");
assert_eq!(item, vec![i; i as usize]);
}
}
#[test]
async fn store_many_items_big() {
let mut flash = mock_flash::MockFlashBase::<4, 1, 4096>::default();
let mut data_buffer = AlignedBuf([0; 128]);
const LENGHT_PER_KEY: [usize; 24] = [
11, 13, 6, 13, 13, 10, 2, 3, 5, 36, 1, 65, 4, 6, 1, 15, 10, 7, 3, 15, 9, 3, 4, 5,
];
for _ in 0..100 {
#[allow(clippy::needless_range_loop)]
for i in 0..24 {
store_item(
&mut flash,
0x0000..0x4000,
&mut cache::NoCache::new(),
&mut data_buffer,
&(i as u16),
&vec![i as u8; LENGHT_PER_KEY[i]].as_slice(),
)
.await
.unwrap();
}
}
#[allow(clippy::needless_range_loop)]
for i in 0..24 {
let item = fetch_item::<u16, &[u8], _>(
&mut flash,
0x0000..0x4000,
&mut cache::NoCache::new(),
&mut data_buffer,
&(i as u16),
)
.await
.unwrap()
.unwrap();
println!("Fetched {item:?}");
assert_eq!(item, 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;
for j in 0..10 {
for i in 0..24 {
store_item(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&(i as u8),
&vec![i as u8; j + 2].as_slice(),
)
.await
.unwrap();
}
}
for j in (0..24).rev() {
for i in 0..=j {
assert!(fetch_item::<u8, &[u8], _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&i
)
.await
.unwrap()
.is_some());
}
remove_item::<u8, _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&j,
)
.await
.unwrap();
for i in 0..j {
assert!(fetch_item::<u8, &[u8], _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&i
)
.await
.unwrap()
.is_some());
}
assert!(fetch_item::<u8, &[u8], _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&j
)
.await
.unwrap()
.is_none());
}
}
#[test]
async fn remove_all() {
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;
for value in 0..10 {
for key in 0..24u8 {
store_item(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&key,
&vec![key; value + 2].as_slice(),
)
.await
.unwrap();
}
}
for key in 0..24u8 {
assert!(fetch_item::<u8, &[u8], _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&key
)
.await
.unwrap()
.is_some());
}
remove_all_items::<u8, _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
)
.await
.unwrap();
for key in 0..24 {
assert!(fetch_item::<u8, &[u8], _>(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut data_buffer,
&key
)
.await
.unwrap()
.is_none());
}
}
#[test]
async fn store_too_big_item() {
let mut flash = MockFlashBig::new(mock_flash::WriteCountCheck::Twice, None, true);
const FLASH_RANGE: Range<u32> = 0x000..0x1000;
store_item(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut [0; 1024],
&0u8,
&[0u8; 1024 - 4 * 2 - 8 - 1],
)
.await
.unwrap();
assert_eq!(
store_item(
&mut flash,
FLASH_RANGE,
&mut cache::NoCache::new(),
&mut [0; 1024],
&0u8,
&[0u8; 1024 - 4 * 2 - 8 - 1 + 1],
)
.await,
Err(Error::ItemTooBig)
);
}
#[test]
async fn item_iterator() {
const UPPER_BOUND: u8 = 64;
let mut flash = MockFlashBig::default();
let flash_range = 0x000..0x1000;
let mut cache = cache::NoCache::new();
let mut data_buffer = AlignedBuf([0; 128]);
for i in 0..UPPER_BOUND {
store_item(
&mut flash,
flash_range.clone(),
&mut cache,
&mut data_buffer,
&i,
&vec![i; i as usize].as_slice(),
)
.await
.unwrap();
}
for i in 0..10 {
store_item(
&mut flash,
flash_range.clone(),
&mut cache,
&mut data_buffer,
&1u8,
&vec![i; i as usize].as_slice(),
)
.await
.unwrap();
}
let mut map_iter = fetch_all_items::<u8, _, _>(
&mut flash,
flash_range.clone(),
&mut cache,
&mut data_buffer,
)
.await
.unwrap();
let mut count = 0;
let mut last_value_buffer = [0u8; 64];
let mut last_value_length = 0;
while let Ok(Some((key, value))) = map_iter.next::<u8, &[u8]>(&mut data_buffer).await {
if key == 1 {
last_value_length = value.len();
last_value_buffer[..value.len()].copy_from_slice(value);
} else {
assert_eq!(value, vec![key; key as usize]);
count += 1;
}
}
assert_eq!(last_value_length, 9);
assert_eq!(
&last_value_buffer[..last_value_length],
vec![9u8; 9].as_slice()
);
assert_eq!(count + 1, UPPER_BOUND);
}
}