2025-12-29 19:36:00 +01:00
|
|
|
// #![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,
|
2026-01-26 19:21:49 +01:00
|
|
|
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};
|
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-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};
|
2026-01-26 19:21:49 +01:00
|
|
|
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
|
|
|
|
2025-12-29 19:36:00 +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();
|
|
|
|
|
|
2026-01-26 19:21:49 +01:00
|
|
|
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 {
|
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
|
|
|
}
|