diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index b821890..3dbe320 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "bytemuck", "cc", "cfg-if", + "chrono", "const-gen", "critical-section", "data-encoding-macro", @@ -45,10 +46,12 @@ dependencies = [ "panic-rtt-target", "password-hash 0.1.0", "pastey 0.2.1", + "postcard", "printf-compat", "rand_core 0.9.5", "rmk", "rtt-target", + "serde", "sha2", "slint", "slint-build", @@ -818,6 +821,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -1836,6 +1840,12 @@ dependencies = [ "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]] name = "embedded-io" version = "0.6.1" @@ -5670,6 +5680,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", "heapless 0.7.17", "postcard-derive", "serde", diff --git a/firmware/acid-firmware/Cargo.toml b/firmware/acid-firmware/Cargo.toml index 2dc4fe9..267d8dc 100644 --- a/firmware/acid-firmware/Cargo.toml +++ b/firmware/acid-firmware/Cargo.toml @@ -80,6 +80,10 @@ password-hash = { path = "../password-hash", default-features = false } hmac = "0.12.1" data-encoding-macro = "0.1.19" 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 embedded-cli = { version = "0.2.1", default-features = false, features = ["help", "macros"] } diff --git a/firmware/acid-firmware/src/db/mod.rs b/firmware/acid-firmware/src/db/mod.rs index 7b433c9..1a32409 100644 --- a/firmware/acid-firmware/src/db/mod.rs +++ b/firmware/acid-firmware/src/db/mod.rs @@ -126,11 +126,11 @@ impl AcidDatabase { #[cfg(feature = "format-db")] { - warn!("Formatting EKV database..."); + log::warn!("Formatting EKV database..."); db.format() .await .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 { @@ -146,7 +146,7 @@ type DbPathSegment<'a> = Cow<'a, str>; type DbPathBuf<'a> = Vec>; type DbPath<'a> = [DbPathSegment<'a>]; -struct DbKey { +pub struct DbKey { /// Segments separated by `0x00`, with the whole thing suffixed with `[0x00, 0xFF]`. bytes: Vec, } @@ -160,12 +160,12 @@ impl Deref for DbKey { } impl DbKey { - fn from_raw(mut key: Vec) -> Self { + pub fn from_raw(mut key: Vec) -> Self { key.extend_from_slice(&[0x00, 0xFF]); Self { bytes: key } } - fn new<'a>(path: impl IntoIterator>) -> Self { + pub fn new<'a>(path: impl IntoIterator>) -> Self { // Null bytes are not allowed within path segments, and will cause a panic. // 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]`. @@ -188,20 +188,17 @@ impl DbKey { bytes.push(0x00); } - if let Some(last_byte) = bytes.last_mut() { - bytes.push(0xFF); - } else { - panic!("An empty path is not a valid path."); - } + assert!(!bytes.is_empty(), "An empty path is not a valid path."); + bytes.push(0xFF); 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[..]) } - fn segments(&self) -> impl Iterator> { + pub fn segments(&self) -> impl Iterator> { struct SegmentIterator<'a> { rest: &'a [u8], } diff --git a/firmware/acid-firmware/src/ui/mod.rs b/firmware/acid-firmware/src/ui/mod.rs index 1f4ab69..6f870e7 100644 --- a/firmware/acid-firmware/src/ui/mod.rs +++ b/firmware/acid-firmware/src/ui/mod.rs @@ -5,7 +5,15 @@ use core::{ 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 embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition}; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; @@ -16,14 +24,17 @@ use esp_storage::FlashStorage; use itertools::Itertools; use log::{info, warn}; use rmk::futures::TryFutureExt; +use serde::{Deserialize, Serialize}; use slint::SharedString; -use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreUserKey}; +use spectre_api_sys::{ + SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreResultType, SpectreUserKey, +}; #[cfg(feature = "limit-fps")] use crate::FRAME_DURATION_MIN; use crate::{ SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, - db::{AcidDatabase, PartitionAcid}, + db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid}, ui::backend::SlintBackend, util::DurationExt, }; @@ -33,10 +44,131 @@ pub mod window_adapter; slint::include_modules!(); +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +struct SpectreUsersConfig { + users: Vec, +} + +#[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::(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::(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(value: &T, serializer: S) -> Result + where + T: ReprConvert, + S: Serializer, + { + serializer.serialize_u32(value.into_repr()) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: ReprConvert, + D: Deserializer<'de>, + { + ::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, + #[serde(with = "with_repr")] + login_type: SpectreResultType, + login_name: Option, + uses: u32, + last_used: NaiveDateTime, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct SpectreSite { + username: String, + site_name: String, + config: SpectreSiteConfig, +} + #[embassy_executor::task] pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) { 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::(&buffer).unwrap() + }; + + assert_eq!(value, read_value, "values do not match"); // TODO: // * Store a config as a versioned postcard-serialized struct