Basic db impl
This commit is contained in:
parent
6cd7b32bee
commit
bbbaea803b
|
|
@ -1,3 +1,4 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["acid-firmware", "password-hash"]
|
members = ["acid-firmware", "password-hash"]
|
||||||
|
default-members = []
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ rustflags = [
|
||||||
# "-C", "force-frame-pointers",
|
# "-C", "force-frame-pointers",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[env] # These must be kept in sync with /.zed/settings.json
|
||||||
[env]
|
|
||||||
EXPLICITLY_INCLUDE_DEFAULT_DIRS = "true"
|
EXPLICITLY_INCLUDE_DEFAULT_DIRS = "true"
|
||||||
LIBXKBCOMMON_BUILD_DIR = "../libxkbcommon/build"
|
LIBXKBCOMMON_BUILD_DIR = "../libxkbcommon/build"
|
||||||
SPECTRE_API_BUILD_DIR = "../spectre-api-c/build-esp32s3"
|
SPECTRE_API_BUILD_DIR = "../spectre-api-c/build-esp32s3"
|
||||||
|
|
|
||||||
276
firmware/acid-firmware/src/db/mod.rs
Normal file
276
firmware/acid-firmware/src/db/mod.rs
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
use core::{
|
||||||
|
iter::Chain,
|
||||||
|
ops::{Deref, DerefMut, Range},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
||||||
|
use ekv::{Database, flash::PageID};
|
||||||
|
use embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition};
|
||||||
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
|
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
||||||
|
use esp_hal::rng::Trng;
|
||||||
|
use esp_storage::FlashStorage;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
pub type PartitionAcid =
|
||||||
|
Partition<'static, CriticalSectionRawMutex, BlockingAsync<FlashStorage<'static>>>;
|
||||||
|
|
||||||
|
// Workaround for alignment requirements.
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
struct AlignedBuf<const N: usize>(pub [u8; N]);
|
||||||
|
|
||||||
|
pub struct EkvFlash<T> {
|
||||||
|
flash: T,
|
||||||
|
buffer: Box<AlignedBuf<{ ekv::config::PAGE_SIZE }>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> EkvFlash<T> {
|
||||||
|
fn new(flash: T) -> Self {
|
||||||
|
Self {
|
||||||
|
flash,
|
||||||
|
buffer: {
|
||||||
|
// Allocate the buffer directly on the heap.
|
||||||
|
let buffer = Box::new_zeroed();
|
||||||
|
unsafe { buffer.assume_init() }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for EkvFlash<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.flash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for EkvFlash<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.flash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: NorFlash + ReadNorFlash> ekv::flash::Flash for EkvFlash<T> {
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn page_count(&self) -> usize {
|
||||||
|
ekv::config::MAX_PAGE_COUNT
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn erase(
|
||||||
|
&mut self,
|
||||||
|
page_id: PageID,
|
||||||
|
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
||||||
|
self.flash
|
||||||
|
.erase(
|
||||||
|
(page_id.index() * ekv::config::PAGE_SIZE) as u32,
|
||||||
|
((page_id.index() + 1) * ekv::config::PAGE_SIZE) as u32,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(
|
||||||
|
&mut self,
|
||||||
|
page_id: PageID,
|
||||||
|
offset: usize,
|
||||||
|
data: &mut [u8],
|
||||||
|
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
||||||
|
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
|
||||||
|
self.flash
|
||||||
|
.read(address as u32, &mut self.buffer.0[..data.len()])
|
||||||
|
.await?;
|
||||||
|
data.copy_from_slice(&self.buffer.0[..data.len()]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write(
|
||||||
|
&mut self,
|
||||||
|
page_id: PageID,
|
||||||
|
offset: usize,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
||||||
|
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
|
||||||
|
self.buffer.0[..data.len()].copy_from_slice(data);
|
||||||
|
self.flash
|
||||||
|
.write(address as u32, &self.buffer.0[..data.len()])
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AcidDatabase {
|
||||||
|
db: Database<EkvFlash<PartitionAcid>, esp_sync::RawMutex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AcidDatabase {
|
||||||
|
type Target = Database<EkvFlash<PartitionAcid>, esp_sync::RawMutex>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for AcidDatabase {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcidDatabase {
|
||||||
|
pub async fn mount(flash: PartitionAcid) -> AcidDatabase {
|
||||||
|
let mut db_config = ekv::Config::default();
|
||||||
|
db_config.random_seed = Trng::try_new()
|
||||||
|
.expect("A `TrngSource` was not initialized before constructing this `Trng`.")
|
||||||
|
.random();
|
||||||
|
let db = Database::<_, esp_sync::RawMutex>::new(EkvFlash::new(flash), db_config);
|
||||||
|
|
||||||
|
#[cfg(feature = "format-db")]
|
||||||
|
{
|
||||||
|
warn!("Formatting EKV database...");
|
||||||
|
db.format()
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|error| panic!("Failed to format the EKV database: {error:?}"));
|
||||||
|
warn!("EKV database formatted successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
match db.mount().await {
|
||||||
|
Ok(()) => info!("EKV database mounted."),
|
||||||
|
Err(error) => panic!("Failed to mount the EKV database: {error:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbPathSegment<'a> = Cow<'a, str>;
|
||||||
|
type DbPathBuf<'a> = Vec<DbPathSegment<'a>>;
|
||||||
|
type DbPath<'a> = [DbPathSegment<'a>];
|
||||||
|
|
||||||
|
struct DbKey(Vec<u8>);
|
||||||
|
|
||||||
|
impl Deref for DbKey {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0[0..(self.0.len() - 2)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbKey {
|
||||||
|
fn from_raw(mut key: Vec<u8>) -> Self {
|
||||||
|
key.extend_from_slice(&[0, 1]);
|
||||||
|
Self(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new<'a>(path: impl IntoIterator<Item = DbPathSegment<'a>>) -> Self {
|
||||||
|
// Null bytes are not allowed within path segments, and will cause a panic.
|
||||||
|
// The segments are separated by `[0, 0]`.
|
||||||
|
// Two null-bytes are used to allow for easy range lookups by suffixing the key with:
|
||||||
|
// `[0]..[0, 1]`
|
||||||
|
// To avoid reallocations, we always suffix the key with `[0, 1]`.
|
||||||
|
// Then, a specific key can be looked up using by omitting the last two bytes.
|
||||||
|
// By omitting one byte, you get the start of the range of all paths within this path.
|
||||||
|
// By not omitting any bytes, you get the end of that range.
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
for segment in path {
|
||||||
|
assert!(
|
||||||
|
!segment.as_bytes().contains(&0x00),
|
||||||
|
"A path segment must not contain null bytes."
|
||||||
|
);
|
||||||
|
|
||||||
|
bytes.extend_from_slice(segment.as_bytes());
|
||||||
|
bytes.extend_from_slice(&[0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(last_byte) = bytes.last_mut() {
|
||||||
|
*last_byte = 1;
|
||||||
|
} else {
|
||||||
|
panic!("An empty path is not a valid path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DbKey(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_of_children(&self) -> Range<&[u8]> {
|
||||||
|
(&self.0[0..(self.0.len() - 1)])..(&self.0[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn segments(&self) -> impl Iterator<Item = DbPathSegment<'_>> {
|
||||||
|
struct SegmentIterator<'a> {
|
||||||
|
rest: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for SegmentIterator<'a> {
|
||||||
|
type Item = DbPathSegment<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(end_index) = self.rest.iter().position(|byte| *byte == 0) {
|
||||||
|
let segment = &self.rest[..end_index];
|
||||||
|
let segment = str::from_utf8(segment).unwrap();
|
||||||
|
self.rest = &self.rest[end_index + 2..];
|
||||||
|
|
||||||
|
Some(Cow::Borrowed(segment))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentIterator {
|
||||||
|
rest: self.0.as_slice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbPathSpectreUsers;
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for DbPathSpectreUsers {
|
||||||
|
type Item = DbPathSegment<'static>;
|
||||||
|
type IntoIter = core::array::IntoIter<DbPathSegment<'static>, 2>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
[
|
||||||
|
DbPathSegment::Borrowed("spectre"),
|
||||||
|
DbPathSegment::Borrowed("users"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbPathSpectreUserSites<'a> {
|
||||||
|
username: DbPathSegment<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for DbPathSpectreUserSites<'a> {
|
||||||
|
type Item = DbPathSegment<'a>;
|
||||||
|
type IntoIter = core::array::IntoIter<DbPathSegment<'a>, 4>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
[
|
||||||
|
DbPathSegment::Borrowed("spectre"),
|
||||||
|
DbPathSegment::Borrowed("user"),
|
||||||
|
self.username,
|
||||||
|
DbPathSegment::Borrowed("site"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbPathSpectreUserSite<'a> {
|
||||||
|
user_sites: DbPathSpectreUserSites<'a>,
|
||||||
|
site: DbPathSegment<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for DbPathSpectreUserSite<'a> {
|
||||||
|
type Item = DbPathSegment<'a>;
|
||||||
|
type IntoIter =
|
||||||
|
Chain<core::array::IntoIter<DbPathSegment<'a>, 4>, core::iter::Once<DbPathSegment<'a>>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.user_sites
|
||||||
|
.into_iter()
|
||||||
|
.chain(core::iter::once(self.site))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -77,6 +77,7 @@ use crate::vial::{CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};
|
||||||
mutually_exclusive_features::none_or_one_of!["usb-log", "alt-log", "rtt-log"];
|
mutually_exclusive_features::none_or_one_of!["usb-log", "alt-log", "rtt-log"];
|
||||||
|
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
mod db;
|
||||||
mod ffi;
|
mod ffi;
|
||||||
mod keymap;
|
mod keymap;
|
||||||
mod logging;
|
mod logging;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
||||||
|
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::{
|
||||||
|
iter::Chain,
|
||||||
|
ops::{Deref, DerefMut, Range},
|
||||||
|
};
|
||||||
|
|
||||||
use alloc::{boxed::Box, ffi::CString};
|
use alloc::{borrow::Cow, boxed::Box, ffi::CString, vec::Vec};
|
||||||
use ekv::{Database, MountError, flash::PageID};
|
use ekv::{Database, MountError, flash::PageID};
|
||||||
use embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition};
|
use embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition};
|
||||||
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
|
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
|
||||||
|
|
@ -10,6 +13,7 @@ use embassy_time::{Duration, Instant};
|
||||||
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
||||||
use esp_hal::rng::Trng;
|
use esp_hal::rng::Trng;
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
|
use itertools::Itertools;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use rmk::futures::TryFutureExt;
|
use rmk::futures::TryFutureExt;
|
||||||
use slint::SharedString;
|
use slint::SharedString;
|
||||||
|
|
@ -17,120 +21,26 @@ use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, Spect
|
||||||
|
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
use crate::FRAME_DURATION_MIN;
|
use crate::FRAME_DURATION_MIN;
|
||||||
use crate::{SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, ui::backend::SlintBackend, util::DurationExt};
|
use crate::{
|
||||||
|
SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
||||||
|
db::{AcidDatabase, PartitionAcid},
|
||||||
|
ui::backend::SlintBackend,
|
||||||
|
util::DurationExt,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod window_adapter;
|
pub mod window_adapter;
|
||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
type PartitionAcid =
|
|
||||||
Partition<'static, CriticalSectionRawMutex, BlockingAsync<FlashStorage<'static>>>;
|
|
||||||
|
|
||||||
// Workaround for alignment requirements.
|
|
||||||
#[repr(C, align(4))]
|
|
||||||
struct AlignedBuf<const N: usize>(pub [u8; N]);
|
|
||||||
|
|
||||||
struct EkvFlash<T> {
|
|
||||||
flash: T,
|
|
||||||
buffer: Box<AlignedBuf<{ ekv::config::PAGE_SIZE }>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EkvFlash<T> {
|
|
||||||
fn new(flash: T) -> Self {
|
|
||||||
Self {
|
|
||||||
flash,
|
|
||||||
buffer: {
|
|
||||||
// Allocate the buffer directly on the heap.
|
|
||||||
let buffer = Box::new_zeroed();
|
|
||||||
unsafe { buffer.assume_init() }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for EkvFlash<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.flash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for EkvFlash<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.flash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: NorFlash + ReadNorFlash> ekv::flash::Flash for EkvFlash<T> {
|
|
||||||
type Error = T::Error;
|
|
||||||
|
|
||||||
fn page_count(&self) -> usize {
|
|
||||||
ekv::config::MAX_PAGE_COUNT
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn erase(
|
|
||||||
&mut self,
|
|
||||||
page_id: PageID,
|
|
||||||
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
|
||||||
self.flash
|
|
||||||
.erase(
|
|
||||||
(page_id.index() * ekv::config::PAGE_SIZE) as u32,
|
|
||||||
((page_id.index() + 1) * ekv::config::PAGE_SIZE) as u32,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read(
|
|
||||||
&mut self,
|
|
||||||
page_id: PageID,
|
|
||||||
offset: usize,
|
|
||||||
data: &mut [u8],
|
|
||||||
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
|
||||||
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
|
|
||||||
self.flash
|
|
||||||
.read(address as u32, &mut self.buffer.0[..data.len()])
|
|
||||||
.await?;
|
|
||||||
data.copy_from_slice(&self.buffer.0[..data.len()]);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn write(
|
|
||||||
&mut self,
|
|
||||||
page_id: PageID,
|
|
||||||
offset: usize,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
|
|
||||||
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
|
|
||||||
self.buffer.0[..data.len()].copy_from_slice(data);
|
|
||||||
self.flash
|
|
||||||
.write(address as u32, &self.buffer.0[..data.len()])
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
||||||
let mut db_config = ekv::Config::default();
|
let db = AcidDatabase::mount(flash_part_acid).await;
|
||||||
db_config.random_seed = Trng::try_new()
|
let write = db.write_transaction().await;
|
||||||
.expect("A `TrngSource` was not initialized before constructing this `Trng`.")
|
|
||||||
.random();
|
|
||||||
let mut db = Database::<_, esp_sync::RawMutex>::new(EkvFlash::new(flash_part_acid), db_config);
|
|
||||||
|
|
||||||
#[cfg(feature = "format-db")]
|
// TODO:
|
||||||
{
|
// * Store a config as a versioned postcard-serialized struct
|
||||||
warn!("Formatting EKV database...");
|
// * Store accounts and sites as ranges in the DB
|
||||||
db.format()
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|error| panic!("Failed to format the EKV database: {error:?}"));
|
|
||||||
warn!("EKV database formatted successfully.");
|
|
||||||
}
|
|
||||||
|
|
||||||
match db.mount().await {
|
|
||||||
Ok(()) => info!("EKV database mounted."),
|
|
||||||
Err(error) => panic!("Failed to mount the EKV database: {error:?}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
|
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue