Compare commits
9 Commits
57758cdc9a
...
91d69474ed
Author | SHA1 | Date |
---|---|---|
Astro | 91d69474ed | |
Astro | 0101a4f90a | |
Astro | 532ba8381f | |
Astro | fb28e8a9b1 | |
Astro | f4245ec937 | |
Astro | 8a2bcf13f8 | |
Astro | 9153240b9b | |
Astro | e5f8b6b56e | |
Astro | a0f34e945d |
|
@ -11,5 +11,11 @@ categories = ["embedded", "database"]
|
|||
license = "GPL-3"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["postcard-values"]
|
||||
postcard-values = ["postcard", "serde"]
|
||||
|
||||
[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 }
|
||||
|
|
10
README.md
10
README.md
|
@ -11,15 +11,7 @@ automatically erasing and rewriting when full.
|
|||
|
||||
For details see `trait StoreBackend`.
|
||||
|
||||
## TODO
|
||||
|
||||
* NoFlash backend
|
||||
* read_int()
|
||||
* write_str()
|
||||
* automatic value coercion
|
||||
* support for floats
|
||||
|
||||
### Ideas
|
||||
## Ideas
|
||||
|
||||
* iterator (quadratic)
|
||||
* compaction to a second backend instead on stack
|
||||
|
|
12
src/error.rs
12
src/error.rs
|
@ -1,9 +1,13 @@
|
|||
use core::{fmt, str};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Error<B> {
|
||||
Read(ReadError),
|
||||
Write(WriteError<B>),
|
||||
#[cfg(feature = "postcard-values")]
|
||||
Encode(postcard::Error),
|
||||
#[cfg(feature = "postcard-values")]
|
||||
Decode(postcard::Error),
|
||||
}
|
||||
|
||||
impl<B> From<ReadError> for Error<B> {
|
||||
|
@ -54,11 +58,11 @@ pub enum WriteError<B> {
|
|||
|
||||
impl<B: fmt::Display> fmt::Display for WriteError<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
match &self {
|
||||
&WriteError::SpaceExhausted =>
|
||||
write!(f, "space exhausted"),
|
||||
&WriteError::Backend(ref e) =>
|
||||
e.fmt(f),
|
||||
&WriteError::Backend(err) =>
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/fmt.rs
30
src/fmt.rs
|
@ -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(())
|
||||
}
|
||||
}
|
73
src/lib.rs
73
src/lib.rs
|
@ -1,18 +1,20 @@
|
|||
#![no_std]
|
||||
//#![no_std]
|
||||
|
||||
use core::{fmt::Write, mem::size_of, str};
|
||||
use core::{mem::size_of, str};
|
||||
use byteorder::{ByteOrder, BigEndian};
|
||||
#[cfg(feature = "postcard-values")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod error;
|
||||
pub use error::{Error, ReadError, WriteError};
|
||||
mod iter;
|
||||
use iter::Iter;
|
||||
mod fmt;
|
||||
use fmt::FmtWrapper;
|
||||
pub mod no_flash;
|
||||
mod test;
|
||||
|
||||
/// Backend interface for `Store`
|
||||
pub trait StoreBackend {
|
||||
type Data: Sized + AsRef<[u8]> + Clone;
|
||||
type Data: Sized + AsRef<[u8]>;
|
||||
|
||||
/// Memory-mapped
|
||||
fn data(&self) -> &Self::Data;
|
||||
|
@ -21,10 +23,17 @@ pub trait StoreBackend {
|
|||
}
|
||||
|
||||
type Error;
|
||||
/// erase flash
|
||||
fn erase(&mut self) -> Result<(), Self::Error>;
|
||||
/// program flash
|
||||
fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error>;
|
||||
/// called after repeated `program()` invocations to allow for eg. cache flushing
|
||||
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
|
||||
|
@ -59,7 +68,29 @@ impl<B: StoreBackend> Store<B> {
|
|||
.map(|value| str::from_utf8(value)
|
||||
.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,
|
||||
|
@ -91,7 +122,8 @@ impl<B: StoreBackend> Store<B> {
|
|||
}
|
||||
|
||||
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()?;
|
||||
|
||||
|
@ -121,21 +153,14 @@ impl<B: StoreBackend> Store<B> {
|
|||
}
|
||||
|
||||
fn append(&mut self, key: &str, value: &[u8]) -> Result<(), Error<B::Error>> {
|
||||
let free_offset = {
|
||||
let mut iter = Iter::new(self.backend.data().as_ref());
|
||||
while let Some(result) = iter.next() {
|
||||
let _ = result?;
|
||||
}
|
||||
iter.offset
|
||||
};
|
||||
|
||||
let free_offset = self.get_bytes_used()?;
|
||||
unsafe { self.append_at(free_offset, key.as_bytes(), value)? };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
Err(Error::Write(WriteError::SpaceExhausted)) => {
|
||||
self.compact()?;
|
||||
|
@ -145,12 +170,13 @@ impl<B: StoreBackend> Store<B> {
|
|||
}
|
||||
}
|
||||
|
||||
/// serialize 32-bit as ASCII, store at `key`
|
||||
pub fn write_int(&mut self, key: &str, value: u32) -> Result<(), Error<B::Error>> {
|
||||
let mut buf = [0; 16];
|
||||
let mut wrapper = FmtWrapper::new(&mut buf);
|
||||
write!(&mut wrapper, "{}", value).unwrap();
|
||||
self.write(key, wrapper.contents())
|
||||
/// encode with `postcard`, write at `key`
|
||||
#[cfg(feature = "postcard-values")]
|
||||
pub fn write_value<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), Error<B::Error>> {
|
||||
let mut buf = [0; 64];
|
||||
let data = postcard::to_slice(&value, &mut buf)
|
||||
.map_err(Error::Encode)?;
|
||||
self.write(key, data)
|
||||
}
|
||||
|
||||
/// store a 0-byte tombstone value at `key`
|
||||
|
@ -158,6 +184,7 @@ impl<B: StoreBackend> Store<B> {
|
|||
self.write(key, &[])
|
||||
}
|
||||
|
||||
/// invokes erase on the backend (this is no compaction!)
|
||||
pub fn erase(&mut self) -> Result<(), WriteError<B::Error>> {
|
||||
self.backend.erase()
|
||||
.map_err(WriteError::Backend)?;
|
||||
|
|
|
@ -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 []
|
||||
}
|
||||
}
|
72
src/test.rs
72
src/test.rs
|
@ -1,8 +1,9 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use crate::{Store, StoreBackend};
|
||||
use crate::{Error, no_flash, Store, StoreBackend, WriteError};
|
||||
|
||||
const SIZE: usize = 1024;
|
||||
static mut BACKUP_SPACE: [u8; SIZE] = [0; SIZE];
|
||||
|
||||
pub struct TestBackend {
|
||||
data: [u8; SIZE],
|
||||
|
@ -39,6 +40,11 @@ impl StoreBackend for TestBackend {
|
|||
self.data[offset..end].copy_from_slice(payload);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn backup_space(&self) -> &'static mut [u8] {
|
||||
unsafe { &mut BACKUP_SPACE }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_store() -> Store<TestBackend> {
|
||||
|
@ -52,7 +58,6 @@ fn empty_read_not_found() {
|
|||
assert_eq!(store.read("foo").unwrap(), None)
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn write_read() {
|
||||
let mut store = make_store();
|
||||
|
@ -61,10 +66,57 @@ fn write_read() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn write_int_read_str() {
|
||||
fn write_read_str() {
|
||||
let mut store = make_store();
|
||||
store.write_int("foo", 42).unwrap();
|
||||
assert_eq!(store.read_str("foo").unwrap().unwrap(), "42");
|
||||
store.write("foo", "bar").unwrap();
|
||||
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]
|
||||
|
@ -77,3 +129,13 @@ fn write_many() {
|
|||
assert_eq!(store.read("foo").unwrap().unwrap(), b"do not compact away");
|
||||
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)))
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue