Compare commits

...

9 Commits

7 changed files with 172 additions and 74 deletions

View File

@ -11,5 +11,11 @@ categories = ["embedded", "database"]
license = "GPL-3" license = "GPL-3"
edition = "2018" edition = "2018"
[features]
default = ["postcard-values"]
postcard-values = ["postcard", "serde"]
[dependencies] [dependencies]
byteorder = { version = "1.0", default-features = false } byteorder = { version = "1", default-features = false }
serde = { version = "1.0", default-features = false, optional = true }
postcard = { version = "0.5", optional = true }

View File

@ -11,15 +11,7 @@ automatically erasing and rewriting when full.
For details see `trait StoreBackend`. For details see `trait StoreBackend`.
## TODO ## Ideas
* NoFlash backend
* read_int()
* write_str()
* automatic value coercion
* support for floats
### Ideas
* iterator (quadratic) * iterator (quadratic)
* compaction to a second backend instead on stack * compaction to a second backend instead on stack

View File

@ -1,9 +1,13 @@
use core::{fmt, str}; use core::{fmt, str};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error<B> { pub enum Error<B> {
Read(ReadError), Read(ReadError),
Write(WriteError<B>), Write(WriteError<B>),
#[cfg(feature = "postcard-values")]
Encode(postcard::Error),
#[cfg(feature = "postcard-values")]
Decode(postcard::Error),
} }
impl<B> From<ReadError> for Error<B> { impl<B> From<ReadError> for Error<B> {
@ -54,11 +58,11 @@ pub enum WriteError<B> {
impl<B: fmt::Display> fmt::Display for WriteError<B> { impl<B: fmt::Display> fmt::Display for WriteError<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match &self {
&WriteError::SpaceExhausted => &WriteError::SpaceExhausted =>
write!(f, "space exhausted"), write!(f, "space exhausted"),
&WriteError::Backend(ref e) => &WriteError::Backend(err) =>
e.fmt(f), write!(f, "{}", err)
} }
} }
} }

View File

@ -1,30 +0,0 @@
use core::fmt;
pub struct FmtWrapper<'a> {
buf: &'a mut [u8],
offset: usize,
}
impl<'a> FmtWrapper<'a> {
pub fn new(buf: &'a mut [u8]) -> Self {
FmtWrapper {
buf: buf,
offset: 0,
}
}
pub fn contents(&self) -> &[u8] {
&self.buf[..self.offset]
}
}
impl<'a> fmt::Write for FmtWrapper<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
let remainder = &mut self.buf[self.offset..];
let remainder = &mut remainder[..bytes.len()];
remainder.copy_from_slice(bytes);
self.offset += bytes.len();
Ok(())
}
}

View File

@ -1,19 +1,21 @@
#![no_std] //#![no_std]
use core::{fmt::Write, mem::size_of, str}; use core::{mem::size_of, str};
use byteorder::{ByteOrder, BigEndian}; use byteorder::{ByteOrder, BigEndian};
#[cfg(feature = "postcard-values")]
use serde::{Deserialize, Serialize};
mod error; mod error;
pub use error::{Error, ReadError, WriteError}; pub use error::{Error, ReadError, WriteError};
mod iter; mod iter;
use iter::Iter; use iter::Iter;
mod fmt; pub mod no_flash;
use fmt::FmtWrapper;
mod test; mod test;
/// Backend interface for `Store`
pub trait StoreBackend { pub trait StoreBackend {
type Data: Sized + AsRef<[u8]> + Clone; type Data: Sized + AsRef<[u8]>;
/// Memory-mapped /// Memory-mapped
fn data(&self) -> &Self::Data; fn data(&self) -> &Self::Data;
fn len(&self) -> usize { fn len(&self) -> usize {
@ -21,10 +23,17 @@ pub trait StoreBackend {
} }
type Error; type Error;
/// erase flash
fn erase(&mut self) -> Result<(), Self::Error>; fn erase(&mut self) -> Result<(), Self::Error>;
/// program flash
fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error>; fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error>;
/// called after repeated `program()` invocations to allow for eg. cache flushing /// called after repeated `program()` invocations to allow for eg. cache flushing
fn program_done(&mut self) {} fn program_done(&mut self) {}
/// need for automatic compaction
///
/// leaves memory management to the implementer
fn backup_space(&self) -> &'static mut [u8];
} }
/// Simple Flash Key Value Store /// Simple Flash Key Value Store
@ -59,7 +68,29 @@ impl<B: StoreBackend> Store<B> {
.map(|value| str::from_utf8(value) .map(|value| str::from_utf8(value)
.map_err(ReadError::Utf8Error) .map_err(ReadError::Utf8Error)
) )
.transpose()) .transpose()
)
}
/// read from `key`, decode with `postcard`
#[cfg(feature = "postcard-values")]
pub fn read_value<'a, T: Deserialize<'a>>(&'a self, key: &str) -> Result<Option<T>, Error<B::Error>> {
self.read(key)?
.map(
|data| postcard::from_bytes(data)
.map_err(Error::Decode)
).transpose()
}
/// how many bytes are currently used
///
/// equally, offset of free space
pub fn get_bytes_used(&self) -> Result<usize, ReadError> {
let mut iter = Iter::new(self.backend.data().as_ref());
while let Some(result) = iter.next() {
let _ = result?;
}
Ok(iter.offset)
} }
unsafe fn append_at(&mut self, mut offset: usize, unsafe fn append_at(&mut self, mut offset: usize,
@ -91,7 +122,8 @@ impl<B: StoreBackend> Store<B> {
} }
fn compact(&mut self) -> Result<(), Error<B::Error>> { fn compact(&mut self) -> Result<(), Error<B::Error>> {
let old_data = self.backend.data().clone(); let old_data = self.backend.backup_space();
old_data.copy_from_slice(self.backend.data().as_ref());
self.erase()?; self.erase()?;
@ -121,21 +153,14 @@ impl<B: StoreBackend> Store<B> {
} }
fn append(&mut self, key: &str, value: &[u8]) -> Result<(), Error<B::Error>> { fn append(&mut self, key: &str, value: &[u8]) -> Result<(), Error<B::Error>> {
let free_offset = { let free_offset = self.get_bytes_used()?;
let mut iter = Iter::new(self.backend.data().as_ref());
while let Some(result) = iter.next() {
let _ = result?;
}
iter.offset
};
unsafe { self.append_at(free_offset, key.as_bytes(), value)? }; unsafe { self.append_at(free_offset, key.as_bytes(), value)? };
Ok(()) Ok(())
} }
/// store a buffer `value` at `key` /// store a buffer `value` at `key`
pub fn write(&mut self, key: &str, value: &[u8]) -> Result<(), Error<B::Error>> { pub fn write<V: AsRef<[u8]>>(&mut self, key: &str, value: V) -> Result<(), Error<B::Error>> {
let value = value.as_ref();
match self.append(key, value) { match self.append(key, value) {
Err(Error::Write(WriteError::SpaceExhausted)) => { Err(Error::Write(WriteError::SpaceExhausted)) => {
self.compact()?; self.compact()?;
@ -145,12 +170,13 @@ impl<B: StoreBackend> Store<B> {
} }
} }
/// serialize 32-bit as ASCII, store at `key` /// encode with `postcard`, write at `key`
pub fn write_int(&mut self, key: &str, value: u32) -> Result<(), Error<B::Error>> { #[cfg(feature = "postcard-values")]
let mut buf = [0; 16]; pub fn write_value<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), Error<B::Error>> {
let mut wrapper = FmtWrapper::new(&mut buf); let mut buf = [0; 64];
write!(&mut wrapper, "{}", value).unwrap(); let data = postcard::to_slice(&value, &mut buf)
self.write(key, wrapper.contents()) .map_err(Error::Encode)?;
self.write(key, data)
} }
/// store a 0-byte tombstone value at `key` /// store a 0-byte tombstone value at `key`
@ -158,6 +184,7 @@ impl<B: StoreBackend> Store<B> {
self.write(key, &[]) self.write(key, &[])
} }
/// invokes erase on the backend (this is no compaction!)
pub fn erase(&mut self) -> Result<(), WriteError<B::Error>> { pub fn erase(&mut self) -> Result<(), WriteError<B::Error>> {
self.backend.erase() self.backend.erase()
.map_err(WriteError::Backend)?; .map_err(WriteError::Backend)?;

37
src/no_flash.rs Normal file
View File

@ -0,0 +1,37 @@
use crate::StoreBackend;
use core::fmt;
/// expect this for any write operation to `NoFlash`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoFlashError;
impl fmt::Display for NoFlashError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "no flash")
}
}
/// a valid `StoreBackend` that can be used for configurations lacking a flash
#[derive(Debug, Clone, Copy)]
pub struct NoFlash;
impl StoreBackend for NoFlash {
type Data = [u8; 0];
fn data(&self) -> &Self::Data {
&[]
}
type Error = NoFlashError;
fn erase(&mut self) -> Result<(), Self::Error> {
Err(NoFlashError)
}
fn program(&mut self, _: usize, _: &[u8]) -> Result<(), Self::Error> {
Err(NoFlashError)
}
fn backup_space(&self) -> &'static mut [u8] {
&mut []
}
}

View File

@ -1,8 +1,9 @@
#![cfg(test)] #![cfg(test)]
use crate::{Store, StoreBackend}; use crate::{Error, no_flash, Store, StoreBackend, WriteError};
const SIZE: usize = 1024; const SIZE: usize = 1024;
static mut BACKUP_SPACE: [u8; SIZE] = [0; SIZE];
pub struct TestBackend { pub struct TestBackend {
data: [u8; SIZE], data: [u8; SIZE],
@ -39,6 +40,11 @@ impl StoreBackend for TestBackend {
self.data[offset..end].copy_from_slice(payload); self.data[offset..end].copy_from_slice(payload);
Ok(()) Ok(())
} }
fn backup_space(&self) -> &'static mut [u8] {
unsafe { &mut BACKUP_SPACE }
}
} }
fn make_store() -> Store<TestBackend> { fn make_store() -> Store<TestBackend> {
@ -52,19 +58,65 @@ fn empty_read_not_found() {
assert_eq!(store.read("foo").unwrap(), None) assert_eq!(store.read("foo").unwrap(), None)
} }
#[test] #[test]
fn write_read() { fn write_read() {
let mut store = make_store(); let mut store = make_store();
store.write("foo", b"bar").unwrap(); store.write("foo", b"bar").unwrap();
assert_eq!(store.read("foo").unwrap().unwrap(), b"bar"); assert_eq!(store.read("foo").unwrap().unwrap(), b"bar");
} }
#[test] #[test]
fn write_int_read_str() { fn write_read_str() {
let mut store = make_store(); let mut store = make_store();
store.write_int("foo", 42).unwrap(); store.write("foo", "bar").unwrap();
assert_eq!(store.read_str("foo").unwrap().unwrap(), "42"); assert_eq!(store.read_str("foo").unwrap().unwrap(), "bar");
}
#[test]
fn write_read_str_utf8() {
let mut store = make_store();
store.write("réseau", "point à point").unwrap();
assert_eq!(store.read_str("réseau").unwrap().unwrap(), "point à point");
}
#[test]
#[cfg(feature = "postcard-values")]
fn write_read_value_str() {
let mut store = make_store();
store.write_value("foo", "bar").unwrap();
assert_eq!(store.read_value("foo"), Ok(Some("bar")));
}
#[test]
#[cfg(feature = "postcard-values")]
fn write_read_value_u32() {
let mut store = make_store();
store.write_value("foo", 42005u32).unwrap();
assert_eq!(store.read_value("foo"), Ok(Some(42005u32)));
}
#[test]
#[cfg(feature = "postcard-values")]
fn write_read_value_i64() {
let mut store = make_store();
store.write_value("foo", -99999999i64).unwrap();
assert_eq!(store.read_value("foo"), Ok(Some(-99999999i64)));
}
#[test]
#[cfg(feature = "postcard-values")]
fn write_read_value_f32() {
let mut store = make_store();
store.write_value("foo", 3.75e17f32).unwrap();
assert_eq!(store.read_value("foo"), Ok(Some(3.75e17f32)));
}
#[test]
#[cfg(feature = "postcard-values")]
fn write_read_value_f64() {
let mut store = make_store();
store.write_value("foo", -1.999e-13f64).unwrap();
assert_eq!(store.read_value("foo"), Ok(Some(-1.999e-13f64)));
} }
#[test] #[test]
@ -77,3 +129,13 @@ fn write_many() {
assert_eq!(store.read("foo").unwrap().unwrap(), b"do not compact away"); assert_eq!(store.read("foo").unwrap().unwrap(), b"do not compact away");
assert_eq!(store.read("bar").unwrap().unwrap(), b"SPAM"); assert_eq!(store.read("bar").unwrap().unwrap(), b"SPAM");
} }
#[test]
fn no_flash() {
let mut store = Store::new(no_flash::NoFlash);
assert_eq!(store.read("foo").unwrap(), None);
assert_eq!(
store.write("foo", b"bar"),
Err(Error::Write(WriteError::Backend(no_flash::NoFlashError)))
);
}