Storage progress
This commit is contained in:
parent
9aa5430851
commit
a3a95b179b
12
firmware/Cargo.lock
generated
12
firmware/Cargo.lock
generated
|
|
@ -11,6 +11,7 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"chrono",
|
||||||
"const-gen",
|
"const-gen",
|
||||||
"critical-section",
|
"critical-section",
|
||||||
"data-encoding-macro",
|
"data-encoding-macro",
|
||||||
|
|
@ -45,10 +46,12 @@ dependencies = [
|
||||||
"panic-rtt-target",
|
"panic-rtt-target",
|
||||||
"password-hash 0.1.0",
|
"password-hash 0.1.0",
|
||||||
"pastey 0.2.1",
|
"pastey 0.2.1",
|
||||||
|
"postcard",
|
||||||
"printf-compat",
|
"printf-compat",
|
||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
"rmk",
|
"rmk",
|
||||||
"rtt-target",
|
"rtt-target",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"slint",
|
"slint",
|
||||||
"slint-build",
|
"slint-build",
|
||||||
|
|
@ -818,6 +821,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
@ -1836,6 +1840,12 @@ dependencies = [
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-io"
|
name = "embedded-io"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
@ -5670,6 +5680,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cobs",
|
"cobs",
|
||||||
|
"embedded-io 0.4.0",
|
||||||
|
"embedded-io 0.6.1",
|
||||||
"heapless 0.7.17",
|
"heapless 0.7.17",
|
||||||
"postcard-derive",
|
"postcard-derive",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ password-hash = { path = "../password-hash", default-features = false }
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
data-encoding-macro = "0.1.19"
|
data-encoding-macro = "0.1.19"
|
||||||
embedded-storage-async = "0.4.1"
|
embedded-storage-async = "0.4.1"
|
||||||
|
postcard = { version = "1.1", default-features = false, features = ["alloc", "postcard-derive"] } # TODO: defmt
|
||||||
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
|
# serde_with = { version = "3.16", default-features = false, features = ["alloc", "macros"] }
|
||||||
|
chrono = { version = "0.4.43", default-features = false, features = ["alloc", "serde"] } # TODO: defmt
|
||||||
|
|
||||||
# Crates for serial UART CLI
|
# Crates for serial UART CLI
|
||||||
embedded-cli = { version = "0.2.1", default-features = false, features = ["help", "macros"] }
|
embedded-cli = { version = "0.2.1", default-features = false, features = ["help", "macros"] }
|
||||||
|
|
|
||||||
|
|
@ -126,11 +126,11 @@ impl AcidDatabase {
|
||||||
|
|
||||||
#[cfg(feature = "format-db")]
|
#[cfg(feature = "format-db")]
|
||||||
{
|
{
|
||||||
warn!("Formatting EKV database...");
|
log::warn!("Formatting EKV database...");
|
||||||
db.format()
|
db.format()
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|error| panic!("Failed to format the EKV database: {error:?}"));
|
.unwrap_or_else(|error| panic!("Failed to format the EKV database: {error:?}"));
|
||||||
warn!("EKV database formatted successfully.");
|
log::warn!("EKV database formatted successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
match db.mount().await {
|
match db.mount().await {
|
||||||
|
|
@ -146,7 +146,7 @@ type DbPathSegment<'a> = Cow<'a, str>;
|
||||||
type DbPathBuf<'a> = Vec<DbPathSegment<'a>>;
|
type DbPathBuf<'a> = Vec<DbPathSegment<'a>>;
|
||||||
type DbPath<'a> = [DbPathSegment<'a>];
|
type DbPath<'a> = [DbPathSegment<'a>];
|
||||||
|
|
||||||
struct DbKey {
|
pub struct DbKey {
|
||||||
/// Segments separated by `0x00`, with the whole thing suffixed with `[0x00, 0xFF]`.
|
/// Segments separated by `0x00`, with the whole thing suffixed with `[0x00, 0xFF]`.
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
@ -160,12 +160,12 @@ impl Deref for DbKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbKey {
|
impl DbKey {
|
||||||
fn from_raw(mut key: Vec<u8>) -> Self {
|
pub fn from_raw(mut key: Vec<u8>) -> Self {
|
||||||
key.extend_from_slice(&[0x00, 0xFF]);
|
key.extend_from_slice(&[0x00, 0xFF]);
|
||||||
Self { bytes: key }
|
Self { bytes: key }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new<'a>(path: impl IntoIterator<Item = DbPathSegment<'a>>) -> Self {
|
pub fn new<'a>(path: impl IntoIterator<Item = DbPathSegment<'a>>) -> Self {
|
||||||
// Null bytes are not allowed within path segments, and will cause a panic.
|
// Null bytes are not allowed within path segments, and will cause a panic.
|
||||||
// Bytes of `0xFF` cannot appear in valid UTF-8.
|
// Bytes of `0xFF` cannot appear in valid UTF-8.
|
||||||
// The byte vector stored in a `DbKey` is formed by interspersing the segments with `0x00`, and suffixing the key with `[0x00, 0xFF]`.
|
// The byte vector stored in a `DbKey` is formed by interspersing the segments with `0x00`, and suffixing the key with `[0x00, 0xFF]`.
|
||||||
|
|
@ -188,20 +188,17 @@ impl DbKey {
|
||||||
bytes.push(0x00);
|
bytes.push(0x00);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(last_byte) = bytes.last_mut() {
|
assert!(!bytes.is_empty(), "An empty path is not a valid path.");
|
||||||
bytes.push(0xFF);
|
bytes.push(0xFF);
|
||||||
} else {
|
|
||||||
panic!("An empty path is not a valid path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
DbKey { bytes }
|
DbKey { bytes }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn range_of_children(&self) -> Range<&[u8]> {
|
pub fn range_of_children(&self) -> Range<&[u8]> {
|
||||||
(&self.bytes[0..(self.bytes.len() - 1)])..(&self.bytes[..])
|
(&self.bytes[0..(self.bytes.len() - 1)])..(&self.bytes[..])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn segments(&self) -> impl Iterator<Item = DbPathSegment<'_>> {
|
pub fn segments(&self) -> impl Iterator<Item = DbPathSegment<'_>> {
|
||||||
struct SegmentIterator<'a> {
|
struct SegmentIterator<'a> {
|
||||||
rest: &'a [u8],
|
rest: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,15 @@ use core::{
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloc::{borrow::Cow, boxed::Box, ffi::CString, vec::Vec};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
ffi::CString,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use chrono::{DateTime, NaiveDateTime};
|
||||||
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};
|
||||||
|
|
@ -16,14 +24,17 @@ use esp_storage::FlashStorage;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use rmk::futures::TryFutureExt;
|
use rmk::futures::TryFutureExt;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use slint::SharedString;
|
use slint::SharedString;
|
||||||
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreUserKey};
|
use spectre_api_sys::{
|
||||||
|
SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreResultType, SpectreUserKey,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
use crate::FRAME_DURATION_MIN;
|
use crate::FRAME_DURATION_MIN;
|
||||||
use crate::{
|
use crate::{
|
||||||
SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
||||||
db::{AcidDatabase, PartitionAcid},
|
db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid},
|
||||||
ui::backend::SlintBackend,
|
ui::backend::SlintBackend,
|
||||||
util::DurationExt,
|
util::DurationExt,
|
||||||
};
|
};
|
||||||
|
|
@ -33,10 +44,131 @@ pub mod window_adapter;
|
||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct SpectreUsersConfig {
|
||||||
|
users: Vec<SpectreUserConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct SpectreUserConfig {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ReprConvert: Copy {
|
||||||
|
type Repr: Copy;
|
||||||
|
|
||||||
|
fn into_repr(self) -> Self::Repr;
|
||||||
|
fn from_repr(repr: Self::Repr) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReprConvert for SpectreAlgorithm {
|
||||||
|
type Repr = u32;
|
||||||
|
|
||||||
|
fn from_repr(repr: Self::Repr) -> Self {
|
||||||
|
unsafe { core::mem::transmute::<Self::Repr, Self>(repr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_repr(self) -> Self::Repr {
|
||||||
|
self as Self::Repr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReprConvert for SpectreResultType {
|
||||||
|
type Repr = u32;
|
||||||
|
|
||||||
|
fn from_repr(repr: Self::Repr) -> Self {
|
||||||
|
unsafe { core::mem::transmute::<Self::Repr, Self>(repr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_repr(self) -> Self::Repr {
|
||||||
|
self as Self::Repr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod with_repr {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
use super::ReprConvert;
|
||||||
|
|
||||||
|
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
T: ReprConvert<Repr = u32>,
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_u32(value.into_repr())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
where
|
||||||
|
T: ReprConvert<Repr = u32>,
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
<u32 as Deserialize>::deserialize(deserializer).map(T::from_repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct SpectreSiteConfig {
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
algorithm: SpectreAlgorithm,
|
||||||
|
counter: SpectreCounter::Type,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
result_type: SpectreResultType,
|
||||||
|
password: Option<String>,
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
login_type: SpectreResultType,
|
||||||
|
login_name: Option<String>,
|
||||||
|
uses: u32,
|
||||||
|
last_used: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct SpectreSite {
|
||||||
|
username: String,
|
||||||
|
site_name: String,
|
||||||
|
config: SpectreSiteConfig,
|
||||||
|
}
|
||||||
|
|
||||||
#[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 db = AcidDatabase::mount(flash_part_acid).await;
|
let db = AcidDatabase::mount(flash_part_acid).await;
|
||||||
let write = db.write_transaction().await;
|
|
||||||
|
let value = SpectreUsersConfig {
|
||||||
|
users: vec![SpectreUserConfig {
|
||||||
|
username: "test".to_string(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut write = db.write_transaction().await;
|
||||||
|
write
|
||||||
|
.write(
|
||||||
|
&DbKey::new(DbPathSpectreUsers),
|
||||||
|
&postcard::to_allocvec(&value).unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
write.commit().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let read_value = {
|
||||||
|
let read = db.read_transaction().await;
|
||||||
|
// TODO: https://github.com/embassy-rs/ekv/issues/20
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let length = read
|
||||||
|
.read(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
buffer.resize(length, 0);
|
||||||
|
read.read(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
postcard::from_bytes::<SpectreUsersConfig>(&buffer).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(value, read_value, "values do not match");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// * Store a config as a versioned postcard-serialized struct
|
// * Store a config as a versioned postcard-serialized struct
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue