// #![cfg_attr(not(feature = "simulator"), no_main)] use core::{ iter::Chain, ops::{Deref, DerefMut, Range}, }; use alloc::{ borrow::Cow, boxed::Box, ffi::CString, rc::Rc, 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::{ModelRc, SharedString, StandardListViewItem, VecModel}; 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, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers, PartitionAcid, ReadTransactionExt, }, 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, } impl Default for SpectreSiteConfig { fn default() -> Self { Self { algorithm: SpectreAlgorithm::Current, counter: SpectreCounter::Default, result_type: SpectreResultType::SpectreResultDefaultResult, password: None, login_type: SpectreResultType::SpectreResultDefaultLogin, login_name: None, uses: 0, last_used: Default::default(), } } } #[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(DbPathSpectreUserSite { user_sites: DbPathSpectreUserSites { username: "test".into(), }, site: "example.org".into(), }), &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(), ) .await .unwrap(); write .write( &DbKey::new(DbPathSpectreUserSite { user_sites: DbPathSpectreUserSites { username: "test".into(), }, site: "sub.example.org".into(), }), &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(), ) .await .unwrap(); 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![0; 256]; let slice = read .read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer) .await .unwrap(); let read_value = postcard::from_bytes::(&slice).unwrap(); let key_sites = DbKey::new(DbPathSpectreUserSites { username: "test".into(), }); let mut key_buffer = [0_u8; ekv::config::MAX_KEY_SIZE]; let mut cursor = read .read_range(key_sites.range_of_children()) .await .unwrap(); while let Some((key_len, value_len)) = cursor.next(&mut key_buffer, &mut buffer).await.unwrap() { let key = DbKey::from_raw(key_buffer[..key_len].into()); let mut key_segments = key.segments(); let value = &buffer[..value_len]; let site_config = postcard::from_bytes::(&value).unwrap(); let site = SpectreSite { config: site_config, username: key_segments.nth(2).unwrap().into(), site_name: key_segments.nth(1).unwrap().into(), }; info!("site = {:#?}", site); } read_value }; info!("read_value = {:#?}", read_value); 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_login_pw_accepted({ let main = main.clone_strong(); move |username, password| { info!("username = {username:?}, password = {password:?}"); main.set_app_state(AppState::UserSites); } }); // let sites = Rc::new(VecModel::default()); // sites.push("First".into()); // sites.push("Second".into()); // main.set_sites(ModelRc::new(ModelRc::new(sites.clone()).map( // |mut site: StandardListViewItem| { // site.text += "10"; // site // }, // ))); main.on_site_pw_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(); }