// #![cfg_attr(not(feature = "simulator"), no_main)] use core::ops::{Deref, DerefMut}; use alloc::{boxed::Box, ffi::CString}; 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 log::{info, warn}; use rmk::futures::TryFutureExt; use slint::SharedString; use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreUserKey}; #[cfg(feature = "limit-fps")] use crate::FRAME_DURATION_MIN; use crate::{SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, ui::backend::SlintBackend, util::DurationExt}; pub mod backend; pub mod window_adapter; slint::include_modules!(); type PartitionAcid = Partition<'static, CriticalSectionRawMutex, BlockingAsync>>; // Workaround for alignment requirements. #[repr(C, align(4))] struct AlignedBuf(pub [u8; N]); struct EkvFlash { flash: T, buffer: Box>, } impl EkvFlash { 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 Deref for EkvFlash { type Target = T; fn deref(&self) -> &Self::Target { &self.flash } } impl DerefMut for EkvFlash { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.flash } } impl ekv::flash::Flash for EkvFlash { type Error = T::Error; fn page_count(&self) -> usize { ekv::config::MAX_PAGE_COUNT } async fn erase( &mut self, page_id: PageID, ) -> Result<(), 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<(), 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<(), 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] pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) { 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 mut db = Database::<_, esp_sync::RawMutex>::new(EkvFlash::new(flash_part_acid), 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:?}"), }; 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(); }