// #![cfg_attr(not(feature = "simulator"), no_main)] use core::{ iter::Chain, ops::{Deref, DerefMut, Range}, }; 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}; use embassy_time::{Duration, Instant}; use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; use esp_hal::rng::Trng; 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, SpectreResultType, SpectreUserKey, }; #[cfg(feature = "limit-fps")] use crate::FRAME_DURATION_MIN; use crate::{ SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid}, ui::backend::SlintBackend, util::DurationExt, }; pub mod backend; 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 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 // * Store accounts and sites as ranges in the DB slint::platform::set_platform(Box::new(backend)).expect("backend already initialized"); let main = AppWindow::new().unwrap(); main.on_accepted(|string| { warn!("Accepted: {string}"); let Ok(c_string) = CString::new(&*string) else { warn!("String cannot be converted to a C string: {string:?}"); return; }; unsafe { let user_key_start = Instant::now(); let user_key = &*spectre_api_sys::spectre_user_key( c"test".as_ptr(), c_string.as_ptr(), SpectreAlgorithm::Current, ); let user_key_duration = Instant::now().duration_since(user_key_start); warn!( "User key derived in {} seconds:\n{user_key:02x?}", user_key_duration.display_as_secs() ); let site_key_start = Instant::now(); let site_key = &*spectre_api_sys::spectre_site_key( user_key as *const SpectreUserKey, c"example.org".as_ptr(), SpectreCounter::Initial, SpectreKeyPurpose::Authentication, c"".as_ptr(), ); let site_key_duration = Instant::now().duration_since(site_key_start); warn!( "Site key derived in {} seconds:\n{site_key:02x?}", site_key_duration.display_as_secs() ); // TODO: Free memory } }); run_event_loop(main).await; } /// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve /// async by having only one iteration of the loop run, and `await`ing here. /// The following block is analogous to `main.run()`. async fn run_event_loop(main: AppWindow) { main.show().unwrap(); loop { slint::run_event_loop().unwrap(); SIGNAL_LCD_SUBMIT.signal(()); #[cfg(feature = "limit-fps")] embassy_time::Timer::after(FRAME_DURATION_MIN).await; SIGNAL_UI_RENDER.wait().await; } #[expect(unreachable_code)] main.hide().unwrap(); }