acid/firmware/acid-firmware/src/ui/mod.rs

321 lines
9.4 KiB
Rust
Raw Normal View History

// #![cfg_attr(not(feature = "simulator"), no_main)]
2026-01-24 21:12:25 +01:00
use core::{
iter::Chain,
ops::{Deref, DerefMut, Range},
};
2026-01-24 00:42:16 +01:00
2026-01-25 01:45:25 +01:00
use alloc::{
borrow::Cow,
boxed::Box,
ffi::CString,
rc::Rc,
2026-01-25 01:45:25 +01:00
string::{String, ToString},
vec,
vec::Vec,
};
use chrono::{DateTime, NaiveDateTime};
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};
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-24 21:12:25 +01:00
use itertools::Itertools;
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-25 01:45:25 +01:00
use serde::{Deserialize, Serialize};
use slint::{ModelRc, SharedString, StandardListViewItem, VecModel};
2026-01-25 01:45:25 +01:00
use spectre_api_sys::{
SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreResultType, SpectreUserKey,
};
2026-01-10 19:21:13 +01:00
#[cfg(feature = "limit-fps")]
use crate::FRAME_DURATION_MIN;
2026-01-24 21:12:25 +01:00
use crate::{
SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
2026-01-25 18:43:07 +01:00
db::{
AcidDatabase, DbKey, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers,
PartitionAcid, ReadTransactionExt,
},
2026-01-24 21:12:25 +01:00
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
slint::include_modules!();
2026-01-10 19:21:13 +01:00
2026-01-25 01:45:25 +01:00
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
struct SpectreUsersConfig {
users: Vec<SpectreUserConfig>,
}
#[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::<Self::Repr, Self>(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::<Self::Repr, Self>(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<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ReprConvert<Repr = u32>,
S: Serializer,
{
serializer.serialize_u32(value.into_repr())
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: ReprConvert<Repr = u32>,
D: Deserializer<'de>,
{
<u32 as Deserialize>::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<String>,
#[serde(with = "with_repr")]
login_type: SpectreResultType,
login_name: Option<String>,
uses: u32,
last_used: NaiveDateTime,
}
2026-01-25 18:43:07 +01:00
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(),
}
}
}
2026-01-25 01:45:25 +01:00
#[derive(Clone, Debug, PartialEq, Eq)]
struct SpectreSite {
username: String,
site_name: String,
config: SpectreSiteConfig,
}
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) {
2026-01-24 21:12:25 +01:00
let db = AcidDatabase::mount(flash_part_acid).await;
2026-01-25 01:45:25 +01:00
let value = SpectreUsersConfig {
users: vec![SpectreUserConfig {
username: "test".to_string(),
}],
};
{
let mut write = db.write_transaction().await;
2026-01-25 18:43:07 +01:00
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();
2026-01-25 01:45:25 +01:00
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
2026-01-25 18:43:07 +01:00
let mut buffer = vec![0; 256];
let slice = read
.read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
2026-01-25 01:45:25 +01:00
.await
.unwrap();
2026-01-25 18:43:07 +01:00
let read_value = postcard::from_bytes::<SpectreUsersConfig>(&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())
2026-01-25 01:45:25 +01:00
.await
.unwrap();
2026-01-25 18:43:07 +01:00
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::<SpectreSiteConfig>(&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
2026-01-25 01:45:25 +01:00
};
2026-01-25 18:43:07 +01:00
info!("read_value = {:#?}", read_value);
2026-01-25 01:45:25 +01:00
assert_eq!(value, read_value, "values do not match");
2026-01-24 00:42:16 +01:00
2026-01-24 21:12:25 +01:00
// TODO:
// * Store a config as a versioned postcard-serialized struct
// * Store accounts and sites as ranges in the DB
2026-01-24 00:42:16 +01:00
2026-01-10 19:21:13 +01:00
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| {
2026-01-20 02:55:17 +01:00
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();
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,
);
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(),
);
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
}