Skip to content

Commit

Permalink
Fix all docs and add to changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
diondokter committed Mar 27, 2024
1 parent bda10c8 commit 49aa18a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 48 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

## Unreleased

- *Breaking:* Made the cache API a bit more strict. Caches now always have to be passed as a mutable reference.
The API before would lead to a lot of extra unncesessary binary size.
- *Breaking:* Removed the `StorageItem` trait in favor of two separate `Key` and `Value` traits. This helps cut
binary size and is closer to what users of the map APIs were expecting.
- *Breaking:* The error type is no longer generic over the Item error. That error variant has been renamed `MapValueError`
and carries a predefined error subtype.

## 1.0.0 01-03-24

- *Breaking:* Corruption repair is automatic now! The repair functions have been made private.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sequential-storage"
version = "1.0.0"
version = "2.0.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "A crate for storing data in flash with minimal erase cycles."
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ The in-flash representation is not (yet?) stable. This too follows semver.

- A breaking change to the in-flash representation will lead to a major version bump
- A feature addition will lead to a minor version bump
- This is always backward-compatible. So data created by e.g. `0.8.0` can be used by `0.8.1`.
- This may not be forward-compatible. So data created by e.g. `0.8.1` may not be usable by `0.8.0`.
- This is always backward-compatible. So data created by e.g. `1.0.0` can be used by `1.1.0`.
- This may not be forward-compatible. So data created by e.g. `1.0.1` may not be usable by `1.0.0`.
- After 1.0, patch releases only fix bugs and don't change the in-flash representation

For any update, consult the changelog to see what changed. Any externally noticable changes are recorded there.
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use core::{
ops::{Deref, DerefMut, Range},
};
use embedded_storage_async::nor_flash::NorFlash;
use map::MapItemError;
use map::MapValueError;

pub mod cache;
mod item;
Expand Down Expand Up @@ -360,8 +360,8 @@ pub enum Error<S> {
BufferTooBig,
/// A provided buffer was to small to be used (usize is size needed)
BufferTooSmall(usize),
/// A storage item error
Item(MapItemError),
/// A map value error
MapValueError(MapValueError),
}

impl<S: PartialEq> PartialEq for Error<S> {
Expand Down Expand Up @@ -390,7 +390,7 @@ where
f,
"A provided buffer was to small to be used. Needed was {needed}"
),
Error::Item(value) => write!(f, "Item error: {value}"),
Error::MapValueError(value) => write!(f, "Map value error: {value}"),
}
}
}
Expand Down
159 changes: 118 additions & 41 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
//! let flash_range = 0x1000..0x3000;
//! // We need to give the crate a buffer to work with.
//! // It must be big enough to serialize the biggest value of your storage type in,
//! // rounded up to to word alignment of the flash. Some kinds of flash may require
//! // rounded up to to word alignment of the flash. Some kinds of internal flash may require
//! // this buffer to be aligned in RAM as well.
//! let mut data_buffer = [0; 128];
//!
//! // We can fetch an item from the flash.
//! // We can fetch an item from the flash. We're using `u8` as our key type and `u32` as our value type.
//! // Nothing is stored in it yet, so it will return None.
//!
//! assert_eq!(
Expand All @@ -43,7 +43,9 @@
//! None
//! );
//!
//! // Now we store an item the flash with key 42
//! // Now we store an item the flash with key 42.
//! // Again we make sure we pass the correct key and value types, u8 and u32.
//! // It is important to do this consistently.
//!
//! store_item(
//! &mut flash,
Expand All @@ -68,6 +70,32 @@
//! );
//! # });
//! ```
//!
//! ## Key and value traits
//!
//! In the previous example we saw we used one key and one value type.
//! It is ***crucial*** we use the same key type every time on the same range of flash.
//! This is because the internal items are serialized as `[key|value]`. A different key type
//! will have a different length and will make all data nonsense.
//!
//! However, if we have special knowledge about what we store for each key,
//! we are allowed to use different value types.
//!
//! For example, we can do the following:
//!
//! 1. Store a u32 with key 0
//! 2. Store a custom type 'Foo' with key 1
//! 3. Fetch a u32 with key 0
//! 4. Fetch a custom type 'Foo' with key 1
//!
//! It is up to the user to make sure this is done correctly.
//! If done incorrectly, the deserialize function of requested value type will see
//! data it doesn't expect. In the best case it'll return an error, in a bad case it'll
//! give bad invalid data and in the worst case the deserialization code panics.
//! So be careful.
//!
//! For your convenience there are premade implementations for the [Key] and [Value] traits.
//!

use embedded_storage_async::nor_flash::MultiwriteNorFlash;

Expand All @@ -80,17 +108,20 @@ use self::{

use super::*;

// TODO revise
// /// Get a storage item from the flash.
// /// Only the last stored item of the given key is returned.
// ///
// /// If no value with the key is found, None is returned.
// ///
// /// The data buffer must be long enough to hold the longest serialized data of your [StorageItem] type,
// /// rounded up to flash word alignment.
// ///
// /// *Note: On a given flash range, make sure to use only the same type as [StorageItem] every time
// /// or types that serialize and deserialize the key in the same way.*
/// Get the last stored value from the flash that is associated with the given key.
/// If no value with the key is found, None is returned.
///
/// The data buffer must be long enough to hold the longest serialized data of your [Key] + [Value] types combined,
/// rounded up to flash word alignment.
///
/// <div class="warning">
///
/// *You are required to, on a given flash range, use the same [Key] type every time. You are allowed to use*
/// *multiple [Value] types. See the module-level docs for more information about this.*
///
/// Also watch out for using integers. This function will take any integer and it's easy to pass the wrong type.
///
/// </div>
pub async fn fetch_item<'d, K: Key, V: Value<'d>, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
Expand Down Expand Up @@ -122,7 +153,7 @@ pub async fn fetch_item<'d, K: Key, V: Value<'d>, S: NorFlash>(

Ok(Some(
V::deserialize_from(&item.destruct().1[K::LEN..][..data_len - K::LEN])
.map_err(Error::Item)?,
.map_err(Error::MapValueError)?,
))
}

Expand Down Expand Up @@ -283,15 +314,21 @@ async fn fetch_item_with_location<'d, K: Key, S: NorFlash>(
}
}

// TODO revise
// /// Store an item into flash memory.
// /// It will overwrite the last value that has the same key.
// /// The flash needs to be at least 2 pages long.
// ///
// /// The data buffer must be long enough to hold the longest serialized data of your [StorageItem] type.
// ///
// /// *Note: On a given flash range, make sure to use only the same type as [StorageItem] every time
// /// or types that serialize and deserialize the key in the same way.*
/// Store a key-value pair into flash memory.
/// It will overwrite the last value that has the same key.
/// The flash needs to be at least 2 pages long.
///
/// The data buffer must be long enough to hold the longest serialized data of your [Key] + [Value] types combined,
/// rounded up to flash word alignment.
///
/// <div class="warning">
///
/// *You are required to, on a given flash range, use the same [Key] type every time. You are allowed to use*
/// *multiple [Value] types. See the module-level docs for more information about this.*
///
/// Also watch out for using integers. This function will take any integer and it's easy to pass the wrong type.
///
/// </div>
pub async fn store_item<'d, K: Key, V: Value<'d>, S: NorFlash>(
flash: &mut S,
flash_range: Range<u32>,
Expand Down Expand Up @@ -379,7 +416,7 @@ async fn store_item_inner<'d, K: Key, S: NorFlash>(
let item_data_length = K::LEN
+ item
.serialize_into(&mut data_buffer[K::LEN..])
.map_err(Error::Item)?;
.map_err(Error::MapValueError)?;

let free_spot_address = find_next_free_item_spot(
flash,
Expand Down Expand Up @@ -491,6 +528,15 @@ async fn store_item_inner<'d, K: Key, S: NorFlash>(
/// 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>
///
/// <div class="warning">
///
/// *You are required to, on a given flash range, use the same [Key] type every time. You are allowed to use*
/// *multiple [Value] types. See the module-level docs for more information about this.*
///
/// Also watch out for using integers. This function will take any integer and it's easy to pass the wrong type.
///
/// </div>
pub async fn remove_item<K: Key, S: MultiwriteNorFlash>(
flash: &mut S,
flash_range: Range<u32>,
Expand Down Expand Up @@ -578,10 +624,24 @@ async fn remove_item_inner<K: Key, S: MultiwriteNorFlash>(
Ok(())
}

/// Anything implementing this trait can be used as a key in the map functions.
///
/// It provides a way to serialize and deserialize the key as well as provide a
/// constant for the serialized length.
///
/// The functions don't return a result. Keys should be simple and trivial.
///
/// The `Eq` bound is used because we need to be able to compare keys and the
/// `Clone` bound helps us pass the key around.
pub trait Key: Eq + Clone + Sized {
/// The serialized length of the key
const LEN: usize;

/// Serialize the key into the given buffer.
/// The buffer is always of the same length as the [Self::LEN] constant.
fn serialize_into(&self, buffer: &mut [u8]);
/// Deserialize the key from the given buffer.
/// The buffer is always of the same length as the [Self::LEN] constant.
fn deserialize_from(buffer: &[u8]) -> Self;
}

Expand Down Expand Up @@ -624,24 +684,32 @@ impl<const N: usize> Key for [u8; N] {
}
}

/// The trait that defines how map values are serialized and deserialized.
///
/// It also carries a lifetime so that zero-copy deserialization is supported.
/// Zero-copy serialization is not supported due to technical restrictions.
pub trait Value<'a> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapItemError>;
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapItemError>
/// Serialize the value into the given buffer. If everything went ok, this function returns the length
/// of the used part of the buffer.
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapValueError>;
/// Deserialize the value from the buffer. Because of the added lifetime, the implementation can borrow from the
/// buffer which opens up some zero-copy possibilities.
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapValueError>
where
Self: Sized;
}

impl<'a> Value<'a> for &'a [u8] {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapItemError> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapValueError> {
if buffer.len() < self.len() {
return Err(MapItemError::BufferTooSmall);
return Err(MapValueError::BufferTooSmall);
}

buffer[..self.len()].copy_from_slice(self);
Ok(self.len())
}

fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapItemError>
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapValueError>
where
Self: Sized,
{
Expand All @@ -650,36 +718,36 @@ impl<'a> Value<'a> for &'a [u8] {
}

impl<'a, const N: usize> Value<'a> for [u8; N] {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapItemError> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapValueError> {
if buffer.len() < self.len() {
return Err(MapItemError::BufferTooSmall);
return Err(MapValueError::BufferTooSmall);
}

buffer[..self.len()].copy_from_slice(self);
Ok(self.len())
}

fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapItemError>
fn deserialize_from(buffer: &'a [u8]) -> Result<Self, MapValueError>
where
Self: Sized,
{
buffer.try_into().map_err(|_| MapItemError::BufferTooSmall)
buffer.try_into().map_err(|_| MapValueError::BufferTooSmall)
}
}

macro_rules! impl_map_item_num {
($int:ty) => {
impl<'a> Value<'a> for $int {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapItemError> {
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, MapValueError> {
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, MapItemError> {
fn deserialize_from(buffer: &[u8]) -> Result<Self, MapValueError> {
Ok(Self::from_le_bytes(
buffer[..core::mem::size_of::<Self>()]
.try_into()
.map_err(|_| MapItemError::BufferTooSmall)?,
.map_err(|_| MapValueError::BufferTooSmall)?,
))
}
}
Expand All @@ -699,19 +767,28 @@ impl_map_item_num!(i128);
impl_map_item_num!(f32);
impl_map_item_num!(f64);

/// Error for map value (de)serialization.
///
/// This error type is predefined (in contrast to using generics) to save many kilobytes of binary size.
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub enum MapItemError {
pub enum MapValueError {
/// The provided buffer was too small.
BufferTooSmall,
/// The deserialization could not succeed because the bytes are in an invalid format.
InvalidFormat,
/// An implementation defined error that might contain more information than the other predefined
/// error variants.
Custom(i32),
}

impl core::fmt::Display for MapItemError {
impl core::fmt::Display for MapValueError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
MapItemError::BufferTooSmall => write!(f, "Buffer too small"),
MapItemError::InvalidFormat => write!(f, "Invalid format"),
MapValueError::BufferTooSmall => write!(f, "Buffer too small"),
MapValueError::InvalidFormat => write!(f, "Invalid format"),
MapValueError::Custom(val) => write!(f, "Custom error: {val}"),
}
}
}
Expand Down

0 comments on commit 49aa18a

Please sign in to comment.