2025-12-29 19:36:00 +01:00
|
|
|
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
|
|
|
|
|
2026-01-24 00:42:16 +01:00
|
|
|
use core::ops::{Deref, DerefMut};
|
|
|
|
|
|
2026-01-20 02:55:17 +01:00
|
|
|
use alloc::{boxed::Box, ffi::CString};
|
2026-01-24 00:42:16 +01:00
|
|
|
use ekv::{Database, MountError, flash::PageID};
|
|
|
|
|
use embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition};
|
|
|
|
|
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
|
2026-01-20 22:01:12 +01:00
|
|
|
use embassy_time::{Duration, Instant};
|
2026-01-24 00:42:16 +01:00
|
|
|
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
|
|
|
|
use esp_hal::rng::Trng;
|
|
|
|
|
use esp_storage::FlashStorage;
|
2026-01-20 02:55:17 +01:00
|
|
|
use log::{info, warn};
|
2026-01-24 00:42:16 +01:00
|
|
|
use rmk::futures::TryFutureExt;
|
2026-01-20 02:55:17 +01:00
|
|
|
use slint::SharedString;
|
|
|
|
|
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreUserKey};
|
2026-01-10 19:21:13 +01:00
|
|
|
|
|
|
|
|
#[cfg(feature = "limit-fps")]
|
|
|
|
|
use crate::FRAME_DURATION_MIN;
|
2026-01-20 22:01:12 +01:00
|
|
|
use crate::{SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, ui::backend::SlintBackend, util::DurationExt};
|
2026-01-10 19:21:13 +01:00
|
|
|
|
2025-12-31 22:24:26 +01:00
|
|
|
pub mod backend;
|
2026-01-10 19:21:13 +01:00
|
|
|
pub mod window_adapter;
|
2025-12-31 22:24:26 +01:00
|
|
|
|
2025-12-29 19:36:00 +01:00
|
|
|
slint::include_modules!();
|
2026-01-10 19:21:13 +01:00
|
|
|
|
2026-01-24 00:42:16 +01:00
|
|
|
type PartitionAcid =
|
|
|
|
|
Partition<'static, CriticalSectionRawMutex, BlockingAsync<FlashStorage<'static>>>;
|
|
|
|
|
|
|
|
|
|
// Workaround for alignment requirements.
|
|
|
|
|
#[repr(C, align(4))]
|
|
|
|
|
struct AlignedBuf<const N: usize>(pub [u8; N]);
|
|
|
|
|
|
|
|
|
|
struct EkvFlash<T> {
|
|
|
|
|
flash: T,
|
|
|
|
|
buffer: Box<AlignedBuf<{ ekv::config::PAGE_SIZE }>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> EkvFlash<T> {
|
|
|
|
|
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<T> Deref for EkvFlash<T> {
|
|
|
|
|
type Target = T;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.flash
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> DerefMut for EkvFlash<T> {
|
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
|
&mut self.flash
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: NorFlash + ReadNorFlash> ekv::flash::Flash for EkvFlash<T> {
|
|
|
|
|
type Error = T::Error;
|
|
|
|
|
|
|
|
|
|
fn page_count(&self) -> usize {
|
|
|
|
|
ekv::config::MAX_PAGE_COUNT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn erase(
|
|
|
|
|
&mut self,
|
|
|
|
|
page_id: PageID,
|
|
|
|
|
) -> Result<(), <EkvFlash<T> 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<(), <EkvFlash<T> 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<(), <EkvFlash<T> 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 19:21:13 +01:00
|
|
|
#[embassy_executor::task]
|
2026-01-24 00:42:16 +01:00
|
|
|
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:?}"),
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-10 19:21:13 +01:00
|
|
|
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
|
|
|
|
|
|
|
|
|
|
let main = AppWindow::new().unwrap();
|
|
|
|
|
|
2026-01-20 02:55:17 +01:00
|
|
|
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 {
|
2026-01-20 22:01:12 +01:00
|
|
|
let user_key_start = Instant::now();
|
2026-01-20 02:55:17 +01:00
|
|
|
let user_key = &*spectre_api_sys::spectre_user_key(
|
|
|
|
|
c"test".as_ptr(),
|
|
|
|
|
c_string.as_ptr(),
|
|
|
|
|
SpectreAlgorithm::Current,
|
|
|
|
|
);
|
2026-01-20 22:01:12 +01:00
|
|
|
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();
|
2026-01-20 02:55:17 +01:00
|
|
|
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(),
|
|
|
|
|
);
|
2026-01-20 22:01:12 +01:00
|
|
|
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()
|
|
|
|
|
);
|
2026-01-20 02:55:17 +01:00
|
|
|
|
|
|
|
|
// TODO: Free memory
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-24 00:42:16 +01:00
|
|
|
run_event_loop(main).await;
|
|
|
|
|
}
|
2026-01-10 19:21:13 +01:00
|
|
|
|
2026-01-24 00:42:16 +01:00
|
|
|
/// 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;
|
2026-01-10 19:21:13 +01:00
|
|
|
}
|
2026-01-24 00:42:16 +01:00
|
|
|
|
|
|
|
|
#[expect(unreachable_code)]
|
|
|
|
|
main.hide().unwrap();
|
2026-01-10 19:21:13 +01:00
|
|
|
}
|