Skip to content

kvstore

A key/value store that survives being interrupted.

Nothing in FlintOS outlives a reboot today. Calibration constants, a device identity, a Wi-Fi credential later — all of it needs somewhere to live, and that somewhere is flash, which has two awkward properties: you can only clear bits by erasing a whole sector, and the power can go away in the middle of a write.

Append-only, because that is what makes power loss survivable

Section titled “Append-only, because that is what makes power loss survivable”

Writing a key does not modify the old one. It appends a new entry and the newest wins. A write interrupted halfway leaves a torn entry at the end of the log, whose checksum fails, and the scan stops there — every key written before it is exactly as it was. There is no window in which a completed earlier write can be lost, because nothing ever overwrites it.

The cost is space: the log fills with superseded entries and eventually needs compacting. [Store::used] and [Error::Full] make that the caller’s decision rather than a surprise.

This crate is lib/: no registers, no chip. It talks to a [Storage], which a driver implements over real flash. That is what lets the whole of the interesting behaviour — tearing, corruption, exhaustion — be tested against a fake, at every byte offset, which no on-target test could do.

struct Store<S> { /* private fields */ }

The log, over some [Storage].

enum Error {
Full,
BadKey,
BadValue,
NotFound,
BufferTooSmall,
Io,
}

What went wrong.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

trait Storage {
fn capacity(&self) -> u32
fn read(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error>
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Error>
fn erase_all(&mut self) -> Result<(), Error>
}

Flash, as this store needs to see it.

Deliberately small. Everything about wear levelling, cache disabling and which core is stalled belongs to the implementation, not here.

const fn entry_len(key_len: usize, value_len: usize) -> u32

Bytes one entry occupies, header and padding included.

const MAX_KEY_LEN: usize;

Longest key. One byte holds the length.

const MAX_VALUE_LEN: usize;

Longest value.