UI code cleanup
This commit is contained in:
parent
2b8dfa7b44
commit
5592708271
|
|
@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use const_gen::*;
|
use const_gen::*;
|
||||||
use embuild::cmd;
|
use embuild::cmd;
|
||||||
use indoc::{formatdoc, writedoc};
|
use indoc::writedoc;
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
use slint_build::{CompilerConfiguration, EmbedResourcesKind};
|
use slint_build::{CompilerConfiguration, EmbedResourcesKind};
|
||||||
use xz2::read::XzEncoder;
|
use xz2::read::XzEncoder;
|
||||||
|
|
@ -63,6 +63,7 @@ fn main() {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct NotBuilt {
|
struct NotBuilt {
|
||||||
|
#[allow(unused)]
|
||||||
lib_build_dir: String,
|
lib_build_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
33
firmware/acid-firmware/src/ui/messages.rs
Normal file
33
firmware/acid-firmware/src/ui/messages.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use slint::SharedString;
|
||||||
|
|
||||||
|
pub enum CallbackMessage {
|
||||||
|
/// The escape key was pressed.
|
||||||
|
Escape,
|
||||||
|
Login(CallbackMessageLogin),
|
||||||
|
Users(CallbackMessageUsers),
|
||||||
|
UserEdit(CallbackMessageUserEdit),
|
||||||
|
UserSites(CallbackMessageUserSites),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CallbackMessageLogin {
|
||||||
|
PwAccepted {
|
||||||
|
username: SharedString,
|
||||||
|
password: SharedString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CallbackMessageUsers {
|
||||||
|
EditUser { username: SharedString, new: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CallbackMessageUserEdit {
|
||||||
|
ComputeIdenticon {
|
||||||
|
encrypted_key: SharedString,
|
||||||
|
password: SharedString,
|
||||||
|
},
|
||||||
|
Confirm {
|
||||||
|
encrypted_key: SharedString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CallbackMessageUserSites {}
|
||||||
|
|
@ -46,120 +46,23 @@ use crate::{
|
||||||
PartitionAcid, ReadTransactionExt,
|
PartitionAcid, ReadTransactionExt,
|
||||||
},
|
},
|
||||||
ffi::alloc::__spre_free,
|
ffi::alloc::__spre_free,
|
||||||
ui::backend::SlintBackend,
|
ui::{
|
||||||
|
backend::SlintBackend,
|
||||||
|
messages::{
|
||||||
|
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit, CallbackMessageUsers,
|
||||||
|
},
|
||||||
|
storage::SpectreUsersConfig,
|
||||||
|
},
|
||||||
util::DurationExt,
|
util::DurationExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
pub mod messages;
|
||||||
|
pub mod storage;
|
||||||
pub mod window_adapter;
|
pub mod window_adapter;
|
||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)]
|
|
||||||
struct SpectreUsersConfig {
|
|
||||||
users: Vec<SpectreUserConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
struct SpectreUserConfig {
|
|
||||||
username: String,
|
|
||||||
#[serde(with = "serde_bytes")]
|
|
||||||
encrypted_key: Key,
|
|
||||||
#[serde(with = "serde_bytes")]
|
|
||||||
key_id: [u8; 32],
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spectre_derive_user_key(username: &CStr, password: &CStr) -> SpectreUserKey {
|
fn spectre_derive_user_key(username: &CStr, password: &CStr) -> SpectreUserKey {
|
||||||
let user_key_start = Instant::now();
|
let user_key_start = Instant::now();
|
||||||
|
|
||||||
|
|
@ -282,334 +185,306 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
|
||||||
.unwrap();
|
.unwrap();
|
||||||
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
|
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
|
||||||
|
|
||||||
struct State {
|
|
||||||
window: AppWindow,
|
|
||||||
db: AcidDatabase,
|
|
||||||
users: SpectreUsersConfig,
|
|
||||||
/// Currently active view.
|
|
||||||
view: AppState,
|
|
||||||
// Retained state for each view.
|
|
||||||
state_login: StateLogin,
|
|
||||||
state_users: StateUsers,
|
|
||||||
state_user_edit: StateUserEdit,
|
|
||||||
state_user_sites: Option<StateUserSites>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
fn process_callback_message(&mut self, message: CallbackMessage) {
|
|
||||||
match self.view {
|
|
||||||
AppState::Login => StateLogin::process_callback_message(self, message),
|
|
||||||
AppState::Users => StateUsers::process_callback_message(self, message),
|
|
||||||
AppState::UserEdit => StateUserEdit::process_callback_message(self, message),
|
|
||||||
AppState::UserSites => StateUserSites::process_callback_message(self, message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_view(&mut self, view: AppState, reset: bool) {
|
|
||||||
self.view = view;
|
|
||||||
self.window.set_app_state(view);
|
|
||||||
|
|
||||||
if reset {
|
|
||||||
match self.view {
|
|
||||||
AppState::Login => self.state_login = Default::default(),
|
|
||||||
AppState::Users => self.state_users = Default::default(),
|
|
||||||
AppState::UserEdit => self.state_user_edit = Default::default(),
|
|
||||||
AppState::UserSites => self.state_user_sites = Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait AppViewTrait {
|
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct StateLogin {}
|
|
||||||
|
|
||||||
impl AppViewTrait for StateLogin {
|
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
|
||||||
match message {
|
|
||||||
CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }) => {
|
|
||||||
let username_c = CString::new(&*username)
|
|
||||||
.expect("Username cannot be converted to a C string.");
|
|
||||||
let password_c = CString::new(&*password)
|
|
||||||
.expect("Password cannot be converted to a C string.");
|
|
||||||
let user_key = spectre_derive_user_key(&username_c, &password_c);
|
|
||||||
|
|
||||||
// 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: Erase memory before freeing
|
|
||||||
// __spre_free(site_key as *const _ as *mut _);
|
|
||||||
let Some(user) = state
|
|
||||||
.users
|
|
||||||
.users
|
|
||||||
.iter()
|
|
||||||
.find(|user| &*user.username == &*username)
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if user.key_id == user_key.keyID.bytes {
|
|
||||||
info!("Correct password entered for user {username:?}.");
|
|
||||||
state.state_user_sites = Some(StateUserSites { username, user_key });
|
|
||||||
state.set_view(AppState::UserSites, true);
|
|
||||||
} else {
|
|
||||||
warn!("Incorrect password entered for user {username:?}.");
|
|
||||||
// TODO: Clear the input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct StateUsers {}
|
|
||||||
|
|
||||||
impl AppViewTrait for StateUsers {
|
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
|
||||||
match message {
|
|
||||||
CallbackMessage::Escape => {
|
|
||||||
state.set_view(AppState::Login, false);
|
|
||||||
}
|
|
||||||
CallbackMessage::Users(CallbackMessageUsers::EditUser { username, new }) => {
|
|
||||||
state.state_user_edit = StateUserEdit {
|
|
||||||
username: username.clone(),
|
|
||||||
new,
|
|
||||||
hashed: None,
|
|
||||||
};
|
|
||||||
state.window.set_user_edit_username(username);
|
|
||||||
state.set_view(AppState::UserEdit, false);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct StateUserEdit {
|
|
||||||
username: SharedString,
|
|
||||||
new: bool,
|
|
||||||
hashed: Option<ProposedPassword>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppViewTrait for StateUserEdit {
|
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
|
||||||
match message {
|
|
||||||
CallbackMessage::Escape => {
|
|
||||||
state.set_view(AppState::Users, false);
|
|
||||||
}
|
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon {
|
|
||||||
encrypted_key,
|
|
||||||
password,
|
|
||||||
}) => {
|
|
||||||
let username_c = CString::new(&*state.state_user_edit.username)
|
|
||||||
.expect("Username cannot be converted to a C string.");
|
|
||||||
let password_c = CString::new(&*password)
|
|
||||||
.expect("Password cannot be converted to a C string.");
|
|
||||||
// let user_key = spectre_derive_user_key(&username_c, &password_c);
|
|
||||||
let identicon: SharedString = unsafe {
|
|
||||||
let identicon = spectre_api_sys::spectre_identicon(
|
|
||||||
username_c.as_ptr(),
|
|
||||||
password_c.as_ptr(),
|
|
||||||
);
|
|
||||||
// TODO: identicon.color
|
|
||||||
format!(
|
|
||||||
"{}{}{}{}",
|
|
||||||
CStr::from_ptr(identicon.leftArm).to_str().unwrap(),
|
|
||||||
CStr::from_ptr(identicon.body).to_str().unwrap(),
|
|
||||||
CStr::from_ptr(identicon.rightArm).to_str().unwrap(),
|
|
||||||
CStr::from_ptr(identicon.accessory).to_str().unwrap()
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
warn!("Identicon: {identicon} ({identicon:?})");
|
|
||||||
state.window.set_user_edit_identicon(identicon.clone());
|
|
||||||
state.state_user_edit.hashed = Some(ProposedPassword {
|
|
||||||
encrypted_key,
|
|
||||||
identicon,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::Confirm { encrypted_key }) => {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ProposedPassword {
|
|
||||||
encrypted_key: SharedString,
|
|
||||||
identicon: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StateUserSites {
|
|
||||||
username: SharedString,
|
|
||||||
user_key: SpectreUserKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppViewTrait for StateUserSites {}
|
|
||||||
|
|
||||||
enum CallbackMessage {
|
|
||||||
/// The escape key was pressed.
|
|
||||||
Escape,
|
|
||||||
Login(CallbackMessageLogin),
|
|
||||||
Users(CallbackMessageUsers),
|
|
||||||
UserEdit(CallbackMessageUserEdit),
|
|
||||||
UserSites(CallbackMessageUserSites),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CallbackMessageLogin {
|
|
||||||
PwAccepted {
|
|
||||||
username: SharedString,
|
|
||||||
password: SharedString,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CallbackMessageUsers {
|
|
||||||
EditUser { username: SharedString, new: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CallbackMessageUserEdit {
|
|
||||||
ComputeIdenticon {
|
|
||||||
encrypted_key: SharedString,
|
|
||||||
password: SharedString,
|
|
||||||
},
|
|
||||||
Confirm {
|
|
||||||
encrypted_key: SharedString,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CallbackMessageUserSites {}
|
|
||||||
|
|
||||||
let main = AppWindow::new().unwrap();
|
let main = AppWindow::new().unwrap();
|
||||||
|
let state = State::new(db, main).await;
|
||||||
|
|
||||||
let state = Rc::new(RefCell::new(State {
|
state.borrow().run_event_loop().await;
|
||||||
window: main.clone_strong(),
|
|
||||||
users: {
|
|
||||||
let read = db.read_transaction().await;
|
|
||||||
let mut buffer = vec![0_u8; 128];
|
|
||||||
match read
|
|
||||||
.read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(bytes) => postcard::from_bytes::<SpectreUsersConfig>(&bytes).unwrap(),
|
|
||||||
Err(ekv::ReadError::KeyNotFound) => Default::default(),
|
|
||||||
Err(error) => panic!("Failed to read the users config: {error:?}"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
db,
|
|
||||||
view: AppState::Login,
|
|
||||||
state_login: Default::default(),
|
|
||||||
state_users: Default::default(),
|
|
||||||
state_user_edit: Default::default(),
|
|
||||||
state_user_sites: Default::default(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
main.on_enter_view({
|
|
||||||
let state = state.clone();
|
|
||||||
move |view| {
|
|
||||||
state.borrow_mut().set_view(view, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on_escape({
|
|
||||||
let state = state.clone();
|
|
||||||
move || {
|
|
||||||
state
|
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::Escape);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on_login_pw_accepted({
|
|
||||||
let state = state.clone();
|
|
||||||
move |username, password| {
|
|
||||||
state
|
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::Login(
|
|
||||||
CallbackMessageLogin::PwAccepted { username, password },
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on_users_edit_user({
|
|
||||||
let state = state.clone();
|
|
||||||
move |username, new| {
|
|
||||||
state
|
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::Users(CallbackMessageUsers::EditUser {
|
|
||||||
username,
|
|
||||||
new,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on_user_edit_compute_identicon({
|
|
||||||
let state = state.clone();
|
|
||||||
move |encrypted_key, password| {
|
|
||||||
state
|
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::UserEdit(
|
|
||||||
CallbackMessageUserEdit::ComputeIdenticon {
|
|
||||||
encrypted_key,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on_user_edit_confirm({
|
|
||||||
let state = state.clone();
|
|
||||||
move |encrypted_key| {
|
|
||||||
state
|
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::UserEdit(
|
|
||||||
CallbackMessageUserEdit::Confirm { encrypted_key },
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// },
|
|
||||||
// )));
|
|
||||||
|
|
||||||
run_event_loop(main).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
|
struct State {
|
||||||
/// async by having only one iteration of the loop run, and `await`ing here.
|
window: AppWindow,
|
||||||
/// The following block is analogous to `main.run()`.
|
db: AcidDatabase,
|
||||||
async fn run_event_loop(main: AppWindow) {
|
users: SpectreUsersConfig,
|
||||||
main.show().unwrap();
|
/// Currently active view.
|
||||||
|
view: AppState,
|
||||||
|
// Retained state for each view.
|
||||||
|
state_login: StateLogin,
|
||||||
|
state_users: StateUsers,
|
||||||
|
state_user_edit: StateUserEdit,
|
||||||
|
state_user_sites: Option<StateUserSites>,
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
impl State {
|
||||||
slint::run_event_loop().unwrap();
|
async fn new(db: AcidDatabase, main: AppWindow) -> Rc<RefCell<Self>> {
|
||||||
SIGNAL_LCD_SUBMIT.signal(());
|
let state = Rc::new(RefCell::new(State {
|
||||||
#[cfg(feature = "limit-fps")]
|
window: main.clone_strong(),
|
||||||
embassy_time::Timer::after(FRAME_DURATION_MIN).await;
|
users: {
|
||||||
SIGNAL_UI_RENDER.wait().await;
|
let read = db.read_transaction().await;
|
||||||
|
let mut buffer = vec![0_u8; 128];
|
||||||
|
match read
|
||||||
|
.read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(bytes) => postcard::from_bytes::<SpectreUsersConfig>(&bytes).unwrap(),
|
||||||
|
Err(ekv::ReadError::KeyNotFound) => Default::default(),
|
||||||
|
Err(error) => panic!("Failed to read the users config: {error:?}"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
db,
|
||||||
|
view: AppState::Login,
|
||||||
|
state_login: Default::default(),
|
||||||
|
state_users: Default::default(),
|
||||||
|
state_user_edit: Default::default(),
|
||||||
|
state_user_sites: Default::default(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
main.on_enter_view({
|
||||||
|
let state = state.clone();
|
||||||
|
move |view| {
|
||||||
|
state.borrow_mut().set_view(view, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_escape({
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.process_callback_message(CallbackMessage::Escape);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_login_pw_accepted({
|
||||||
|
let state = state.clone();
|
||||||
|
move |username, password| {
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.process_callback_message(CallbackMessage::Login(
|
||||||
|
CallbackMessageLogin::PwAccepted { username, password },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_users_edit_user({
|
||||||
|
let state = state.clone();
|
||||||
|
move |username, new| {
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.process_callback_message(CallbackMessage::Users(
|
||||||
|
CallbackMessageUsers::EditUser { username, new },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_user_edit_compute_identicon({
|
||||||
|
let state = state.clone();
|
||||||
|
move |encrypted_key, password| {
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.process_callback_message(CallbackMessage::UserEdit(
|
||||||
|
CallbackMessageUserEdit::ComputeIdenticon {
|
||||||
|
encrypted_key,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_user_edit_confirm({
|
||||||
|
let state = state.clone();
|
||||||
|
move |encrypted_key| {
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.process_callback_message(CallbackMessage::UserEdit(
|
||||||
|
CallbackMessageUserEdit::Confirm { encrypted_key },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// },
|
||||||
|
// )));
|
||||||
|
|
||||||
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(unreachable_code)]
|
fn process_callback_message(&mut self, message: CallbackMessage) {
|
||||||
main.hide().unwrap();
|
match self.view {
|
||||||
|
AppState::Login => StateLogin::process_callback_message(self, message),
|
||||||
|
AppState::Users => StateUsers::process_callback_message(self, message),
|
||||||
|
AppState::UserEdit => StateUserEdit::process_callback_message(self, message),
|
||||||
|
AppState::UserSites => StateUserSites::process_callback_message(self, message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_view(&mut self, view: AppState, reset: bool) {
|
||||||
|
self.view = view;
|
||||||
|
self.window.set_app_state(view);
|
||||||
|
|
||||||
|
if reset {
|
||||||
|
match self.view {
|
||||||
|
AppState::Login => self.state_login = Default::default(),
|
||||||
|
AppState::Users => self.state_users = Default::default(),
|
||||||
|
AppState::UserEdit => self.state_user_edit = Default::default(),
|
||||||
|
AppState::UserSites => self.state_user_sites = Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(&self) -> ! {
|
||||||
|
self.window.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)]
|
||||||
|
self.window.hide().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait AppViewTrait {
|
||||||
|
fn process_callback_message(state: &mut State, message: CallbackMessage) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StateLogin {}
|
||||||
|
|
||||||
|
impl AppViewTrait for StateLogin {
|
||||||
|
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
||||||
|
match message {
|
||||||
|
CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }) => {
|
||||||
|
let username_c =
|
||||||
|
CString::new(&*username).expect("Username cannot be converted to a C string.");
|
||||||
|
let password_c =
|
||||||
|
CString::new(&*password).expect("Password cannot be converted to a C string.");
|
||||||
|
let user_key = spectre_derive_user_key(&username_c, &password_c);
|
||||||
|
|
||||||
|
// 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: Erase memory before freeing
|
||||||
|
// __spre_free(site_key as *const _ as *mut _);
|
||||||
|
let Some(user) = state
|
||||||
|
.users
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.find(|user| &*user.username == &*username)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.key_id == user_key.keyID.bytes {
|
||||||
|
info!("Correct password entered for user {username:?}.");
|
||||||
|
state.state_user_sites = Some(StateUserSites { username, user_key });
|
||||||
|
state.set_view(AppState::UserSites, true);
|
||||||
|
} else {
|
||||||
|
warn!("Incorrect password entered for user {username:?}.");
|
||||||
|
// TODO: Clear the input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StateUsers {}
|
||||||
|
|
||||||
|
impl AppViewTrait for StateUsers {
|
||||||
|
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
||||||
|
match message {
|
||||||
|
CallbackMessage::Escape => {
|
||||||
|
state.set_view(AppState::Login, false);
|
||||||
|
}
|
||||||
|
CallbackMessage::Users(CallbackMessageUsers::EditUser { username, new }) => {
|
||||||
|
state.state_user_edit = StateUserEdit {
|
||||||
|
username: username.clone(),
|
||||||
|
new,
|
||||||
|
hashed: None,
|
||||||
|
};
|
||||||
|
state.window.set_user_edit_username(username);
|
||||||
|
state.set_view(AppState::UserEdit, false);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StateUserEdit {
|
||||||
|
username: SharedString,
|
||||||
|
new: bool,
|
||||||
|
hashed: Option<ProposedPassword>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppViewTrait for StateUserEdit {
|
||||||
|
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
||||||
|
match message {
|
||||||
|
CallbackMessage::Escape => {
|
||||||
|
state.set_view(AppState::Users, false);
|
||||||
|
}
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon {
|
||||||
|
encrypted_key,
|
||||||
|
password,
|
||||||
|
}) => {
|
||||||
|
let username_c = CString::new(&*state.state_user_edit.username)
|
||||||
|
.expect("Username cannot be converted to a C string.");
|
||||||
|
let password_c =
|
||||||
|
CString::new(&*password).expect("Password cannot be converted to a C string.");
|
||||||
|
// let user_key = spectre_derive_user_key(&username_c, &password_c);
|
||||||
|
let identicon: SharedString = unsafe {
|
||||||
|
let identicon = spectre_api_sys::spectre_identicon(
|
||||||
|
username_c.as_ptr(),
|
||||||
|
password_c.as_ptr(),
|
||||||
|
);
|
||||||
|
// TODO: identicon.color
|
||||||
|
format!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
CStr::from_ptr(identicon.leftArm).to_str().unwrap(),
|
||||||
|
CStr::from_ptr(identicon.body).to_str().unwrap(),
|
||||||
|
CStr::from_ptr(identicon.rightArm).to_str().unwrap(),
|
||||||
|
CStr::from_ptr(identicon.accessory).to_str().unwrap()
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
warn!("Identicon: {identicon} ({identicon:?})");
|
||||||
|
state.window.set_user_edit_identicon(identicon.clone());
|
||||||
|
state.state_user_edit.hashed = Some(ProposedPassword {
|
||||||
|
encrypted_key,
|
||||||
|
identicon,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::Confirm { encrypted_key }) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProposedPassword {
|
||||||
|
encrypted_key: SharedString,
|
||||||
|
identicon: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateUserSites {
|
||||||
|
username: SharedString,
|
||||||
|
user_key: SpectreUserKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppViewTrait for StateUserSites {}
|
||||||
|
|
|
||||||
109
firmware/acid-firmware/src/ui/storage.rs
Normal file
109
firmware/acid-firmware/src/ui/storage.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use alloc::{string::String, vec::Vec};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use password_hash::Key;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreResultType};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SpectreUsersConfig {
|
||||||
|
pub users: Vec<SpectreUserConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SpectreUserConfig {
|
||||||
|
pub username: String,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
pub encrypted_key: Key,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
pub key_id: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SpectreSiteConfig {
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
pub algorithm: SpectreAlgorithm,
|
||||||
|
pub counter: SpectreCounter::Type,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
pub result_type: SpectreResultType,
|
||||||
|
pub password: Option<String>,
|
||||||
|
#[serde(with = "with_repr")]
|
||||||
|
pub login_type: SpectreResultType,
|
||||||
|
pub login_name: Option<String>,
|
||||||
|
pub uses: u32,
|
||||||
|
pub 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 {
|
||||||
|
pub username: String,
|
||||||
|
pub site_name: String,
|
||||||
|
pub config: SpectreSiteConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod with_repr {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use spectre_api_sys::{SpectreAlgorithm, SpectreResultType};
|
||||||
|
|
||||||
|
pub 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue