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"
|
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 }
|
||||||
|
|
10
README.md
10
README.md
|
@ -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
|
||||||
|
|
12
src/error.rs
12
src/error.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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(())
|
|
||||||
}
|
|
||||||
}
|
|
75
src/lib.rs
75
src/lib.rs
|
@ -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)?;
|
||||||
|
|
|
@ -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 []
|
||||||
|
}
|
||||||
|
}
|
74
src/test.rs
74
src/test.rs
|
@ -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)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue