More GUI progress
This commit is contained in:
parent
9c2a614aff
commit
8426852d7c
11
firmware/Cargo.lock
generated
11
firmware/Cargo.lock
generated
|
|
@ -53,6 +53,7 @@ dependencies = [
|
||||||
"rmk",
|
"rmk",
|
||||||
"rtt-target",
|
"rtt-target",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
"sha2",
|
"sha2",
|
||||||
"slint",
|
"slint",
|
||||||
"slint-build",
|
"slint-build",
|
||||||
|
|
@ -6499,6 +6500,16 @@ dependencies = [
|
||||||
"typeid",
|
"typeid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_bytes"
|
||||||
|
version = "0.11.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ embedded-storage-async = "0.4.1"
|
||||||
postcard = { version = "1.1", default-features = false, features = ["alloc", "postcard-derive"] } # TODO: defmt
|
postcard = { version = "1.1", default-features = false, features = ["alloc", "postcard-derive"] } # TODO: defmt
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
# serde_with = { version = "3.16", default-features = false, features = ["alloc", "macros"] }
|
# serde_with = { version = "3.16", default-features = false, features = ["alloc", "macros"] }
|
||||||
|
serde_bytes = { version = "0.11.19", default-features = false, features = ["alloc"] }
|
||||||
chrono = { version = "0.4.43", default-features = false, features = ["alloc", "serde"] } # TODO: defmt
|
chrono = { version = "0.4.43", default-features = false, features = ["alloc", "serde"] } # TODO: defmt
|
||||||
|
|
||||||
# Crates for serial UART CLI
|
# Crates for serial UART CLI
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
|
|
||||||
// Memory allocation regions.
|
// Memory allocation regions.
|
||||||
// These can be debugged using `xtensa-esp32s3-elf-size -A <path-to-binary>`.
|
// These can be debugged using `xtensa-esp32s3-elf-size -A <path-to-binary>`.
|
||||||
|
// A panic such as `memory allocation of 3740121773 bytes failed` is caused by a heap overflow. The size is `DEEDBAAD` in hex.
|
||||||
|
|
||||||
/// Total heap size
|
/// Total heap size
|
||||||
const HEAP_SIZE: usize = 128 * 1024;
|
const HEAP_SIZE: usize = 128 * 1024;
|
||||||
|
|
@ -158,7 +159,7 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
// Use the internal DRAM as the heap.
|
// Use the internal DRAM as the heap.
|
||||||
// Memory reclaimed from the esp-idf bootloader.
|
// Memory reclaimed from the esp-idf bootloader.
|
||||||
const HEAP_SIZE_RECLAIMED: usize = 64 * 1024;
|
const HEAP_SIZE_RECLAIMED: usize = 72 * 1024;
|
||||||
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: HEAP_SIZE_RECLAIMED);
|
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: HEAP_SIZE_RECLAIMED);
|
||||||
esp_alloc::heap_allocator!(size: HEAP_SIZE - HEAP_SIZE_RECLAIMED);
|
esp_alloc::heap_allocator!(size: HEAP_SIZE - HEAP_SIZE_RECLAIMED);
|
||||||
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
|
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
|
cell::RefCell,
|
||||||
|
ffi::CStr,
|
||||||
iter::Chain,
|
iter::Chain,
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
};
|
};
|
||||||
|
|
@ -9,6 +11,7 @@ use alloc::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
|
format,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec,
|
vec,
|
||||||
|
|
@ -24,11 +27,14 @@ use esp_hal::rng::Trng;
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use password_hash::Key;
|
||||||
use rmk::futures::TryFutureExt;
|
use rmk::futures::TryFutureExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_bytes::Bytes;
|
||||||
use slint::{ModelRc, SharedString, StandardListViewItem, VecModel};
|
use slint::{ModelRc, SharedString, StandardListViewItem, VecModel};
|
||||||
use spectre_api_sys::{
|
use spectre_api_sys::{
|
||||||
SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreResultType, SpectreUserKey,
|
SpectreAlgorithm, SpectreCounter, SpectreKeyID, SpectreKeyPurpose, SpectreResultType,
|
||||||
|
SpectreUserKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
|
|
@ -39,6 +45,7 @@ use crate::{
|
||||||
AcidDatabase, DbKey, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers,
|
AcidDatabase, DbKey, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers,
|
||||||
PartitionAcid, ReadTransactionExt,
|
PartitionAcid, ReadTransactionExt,
|
||||||
},
|
},
|
||||||
|
ffi::alloc::__spre_free,
|
||||||
ui::backend::SlintBackend,
|
ui::backend::SlintBackend,
|
||||||
util::DurationExt,
|
util::DurationExt,
|
||||||
};
|
};
|
||||||
|
|
@ -48,7 +55,7 @@ pub mod window_adapter;
|
||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
struct SpectreUsersConfig {
|
struct SpectreUsersConfig {
|
||||||
users: Vec<SpectreUserConfig>,
|
users: Vec<SpectreUserConfig>,
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +63,10 @@ struct SpectreUsersConfig {
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
struct SpectreUserConfig {
|
struct SpectreUserConfig {
|
||||||
username: String,
|
username: String,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
encrypted_key: Key,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
key_id: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ReprConvert: Copy {
|
trait ReprConvert: Copy {
|
||||||
|
|
@ -149,6 +160,29 @@ struct SpectreSite {
|
||||||
config: SpectreSiteConfig,
|
config: SpectreSiteConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spectre_derive_user_key(username: &CStr, password: &CStr) -> SpectreUserKey {
|
||||||
|
let user_key_start = Instant::now();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let user_key = &*spectre_api_sys::spectre_user_key(
|
||||||
|
username.as_ptr(),
|
||||||
|
password.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 user_key_stack = user_key.clone();
|
||||||
|
|
||||||
|
// TODO: Erase memory before freeing
|
||||||
|
__spre_free(user_key as *const _ as *mut _);
|
||||||
|
|
||||||
|
user_key_stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
||||||
let db = AcidDatabase::mount(flash_part_acid).await;
|
let db = AcidDatabase::mount(flash_part_acid).await;
|
||||||
|
|
@ -156,6 +190,8 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
|
||||||
let value = SpectreUsersConfig {
|
let value = SpectreUsersConfig {
|
||||||
users: vec![SpectreUserConfig {
|
users: vec![SpectreUserConfig {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
encrypted_key: [0; _],
|
||||||
|
key_id: [0; _],
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,13 +278,303 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
|
||||||
|
|
||||||
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = Rc::new(RefCell::new(State {
|
||||||
|
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({
|
main.on_login_pw_accepted({
|
||||||
let main = main.clone_strong();
|
let state = state.clone();
|
||||||
move |username, password| {
|
move |username, password| {
|
||||||
info!("username = {username:?}, password = {password:?}");
|
state
|
||||||
main.set_app_state(AppState::UserSites);
|
.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 },
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -262,42 +588,6 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
|
||||||
// },
|
// },
|
||||||
// )));
|
// )));
|
||||||
|
|
||||||
main.on_site_pw_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;
|
run_event_loop(main).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import { IconButton } from "widgets/icon-button.slint";
|
||||||
export component LoginView inherits VerticalLayout {
|
export component LoginView inherits VerticalLayout {
|
||||||
padding: Style.spacing;
|
padding: Style.spacing;
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
|
in property <[string]> usernames <=> combo_box_username.model;
|
||||||
|
callback users_clicked();
|
||||||
callback pw_accepted(string, string);
|
callback pw_accepted(string, string);
|
||||||
Rectangle { }
|
Rectangle { }
|
||||||
|
|
||||||
|
|
@ -12,11 +14,12 @@ export component LoginView inherits VerticalLayout {
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
IconButton {
|
IconButton {
|
||||||
icon: @image-url("images/users.svg");
|
icon: @image-url("images/users.svg");
|
||||||
|
clicked => {
|
||||||
|
users_clicked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
combo_box_username := ComboBox {
|
combo_box_username := ComboBox { }
|
||||||
model: ["first", "second"];
|
|
||||||
}
|
|
||||||
|
|
||||||
line_edit_user_pw := LineEdit {
|
line_edit_user_pw := LineEdit {
|
||||||
input-type: InputType.password;
|
input-type: InputType.password;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,3 @@
|
||||||
/*
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
VerticalBox,
|
|
||||||
LineEdit,
|
|
||||||
GridBox,
|
|
||||||
TabWidget,
|
|
||||||
Button,
|
|
||||||
} from "std-widgets.slint";
|
|
||||||
*/
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
VerticalBox,
|
VerticalBox,
|
||||||
|
|
@ -18,58 +8,90 @@ import {
|
||||||
ComboBox,
|
ComboBox,
|
||||||
} from "std-widgets.slint";
|
} from "std-widgets.slint";
|
||||||
|
|
||||||
|
// Implementations of standard widgets can be found at https://github.com/slint-ui/slint/tree/master/internal/compiler/widgets
|
||||||
|
|
||||||
// See https://github.com/slint-ui/slint/issues/4956 for issues with fonts.
|
// See https://github.com/slint-ui/slint/issues/4956 for issues with fonts.
|
||||||
import "../fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf";
|
import "../fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf";
|
||||||
import { UserSitesView } from "user-sites-view.slint";
|
import { UserSitesView } from "user-sites-view.slint";
|
||||||
import { Style } from "globals.slint";
|
import { Style } from "globals.slint";
|
||||||
import { UsersView } from "users-view.slint";
|
import { UsersView } from "users-view.slint";
|
||||||
import { LoginView } from "login-view.slint";
|
import { LoginView } from "login-view.slint";
|
||||||
|
import { UserEditView } from "user-edit-view.slint";
|
||||||
|
|
||||||
export enum AppState {
|
export enum AppState {
|
||||||
login,
|
login,
|
||||||
user-sites,
|
|
||||||
users,
|
users,
|
||||||
|
user-edit,
|
||||||
|
user-sites,
|
||||||
}
|
}
|
||||||
|
|
||||||
export component AppWindow inherits Window {
|
export component AppWindow inherits Window {
|
||||||
|
// Special characters to generate pre-render glyphs for.
|
||||||
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨";
|
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨";
|
||||||
|
// in property <string> dummy_identicon_symbols: "╔╚╰═█░▒▓☺☻╗╝╯═◈◎◐◑◒◓☀☁☂☃☄★☆☎☏⎈⌂☘☢☣☕⌚⌛⏰⚡⛄⛅☔♔♕♖♗♘♙♚♛♜♝♞♟♨♩♪♫⚐⚑⚔⚖⚙⚠⌘⏎✄✆✈✉✌";
|
||||||
default-font-family: "IBM Plex Mono";
|
default-font-family: "IBM Plex Mono";
|
||||||
default-font-size: 16pt;
|
default-font-size: 16pt;
|
||||||
height: 368px;
|
height: 368px;
|
||||||
width: 960px;
|
width: 960px;
|
||||||
|
forward-focus: focus-scope;
|
||||||
in property <AppState> app-state: AppState.login;
|
in property <AppState> app-state: AppState.login;
|
||||||
|
callback escape();
|
||||||
|
callback enter-view(view: AppState);
|
||||||
// Login View
|
// Login View
|
||||||
callback login_pw_accepted <=> login_view.pw_accepted;
|
callback login_pw_accepted <=> login_view.pw_accepted;
|
||||||
// Sites View
|
// Users View
|
||||||
|
callback users_edit_user <=> users_view.edit_user;
|
||||||
|
// User Edit View
|
||||||
|
in property <string> user_edit_username <=> user_edit_view.username;
|
||||||
|
in-out property <string> user_edit_identicon <=> user_edit_view.identicon;
|
||||||
|
callback user_edit_compute_identicon <=> user_edit_view.compute_identicon;
|
||||||
|
callback user_edit_confirm <=> user_edit_view.confirm;
|
||||||
|
// User Sites View
|
||||||
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
|
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
|
||||||
callback site_pw_edited <=> user_sites_view.pw_edited;
|
callback site_pw_edited <=> user_sites_view.pw_edited;
|
||||||
callback site_pw_accepted <=> user_sites_view.pw_accepted;
|
callback site_pw_accepted <=> user_sites_view.pw_accepted;
|
||||||
// Sites View
|
focus-scope := FocusScope {
|
||||||
in property <[StandardListViewItem]> usernames <=> users_view.model;
|
key-pressed(event) => {
|
||||||
callback user_username_edited <=> users_view.pw_edited;
|
if event.text == "\u{1b}" {
|
||||||
callback user_username_accepted <=> users_view.pw_accepted;
|
root.escape();
|
||||||
VerticalBox {
|
EventResult.accept
|
||||||
width: 960px;
|
} else {
|
||||||
height: 368px;
|
EventResult.reject
|
||||||
padding: 0px;
|
|
||||||
padding-top: 120px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
Rectangle {
|
|
||||||
height: 240px;
|
|
||||||
background: #00141d;
|
|
||||||
// For debugging bounds.
|
|
||||||
// border-color: #ffcf00;
|
|
||||||
// border-width: 1px;
|
|
||||||
login_view := LoginView {
|
|
||||||
visible: app-state == AppState.login;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
vertical-box := VerticalBox {
|
||||||
|
width: 960px;
|
||||||
|
height: 368px;
|
||||||
|
padding: 0px;
|
||||||
|
padding-top: 120px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
Rectangle {
|
||||||
|
height: 240px;
|
||||||
|
background: #00141d;
|
||||||
|
// For debugging bounds.
|
||||||
|
// border-color: #ffcf00;
|
||||||
|
// border-width: 1px;
|
||||||
|
login_view := LoginView {
|
||||||
|
visible: app-state == AppState.login;
|
||||||
|
users_clicked => {
|
||||||
|
enter-view(AppState.users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_sites_view := UserSitesView {
|
users_view := UsersView {
|
||||||
visible: app-state == AppState.user-sites;
|
visible: app-state == AppState.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
users_view := UsersView {
|
user_edit_view := UserEditView {
|
||||||
visible: app-state == AppState.users;
|
visible: app-state == AppState.user-edit;
|
||||||
|
cancel => {
|
||||||
|
root.escape();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_sites_view := UserSitesView {
|
||||||
|
visible: app-state == AppState.user-sites;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
firmware/acid-firmware/ui/user-edit-view.slint
Normal file
64
firmware/acid-firmware/ui/user-edit-view.slint
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { LineEdit, StandardListView, Button } from "std-widgets.slint";
|
||||||
|
import { Style } from "globals.slint";
|
||||||
|
import { IconButton } from "widgets/icon-button.slint";
|
||||||
|
|
||||||
|
export component UserEditView inherits HorizontalLayout {
|
||||||
|
padding: Style.spacing;
|
||||||
|
spacing: Style.spacing;
|
||||||
|
in property <string> username;
|
||||||
|
in-out property <string> identicon;
|
||||||
|
callback compute_identicon(encrypted_key: string, password: string);
|
||||||
|
callback confirm(encrypted_key: string);
|
||||||
|
callback cancel <=> button_cancel.clicked;
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: Style.spacing;
|
||||||
|
Rectangle { }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Enter " + username + "'s encrypted key and press Enter:";
|
||||||
|
}
|
||||||
|
|
||||||
|
line_edit_encrypted_key := LineEdit {
|
||||||
|
input-type: InputType.text;
|
||||||
|
placeholder-text: "Encrypted key";
|
||||||
|
edited(text) => {
|
||||||
|
root.identicon = "";
|
||||||
|
}
|
||||||
|
accepted(text) => {
|
||||||
|
line_edit_password.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_edit_password := LineEdit {
|
||||||
|
input-type: InputType.text;
|
||||||
|
placeholder-text: "Password";
|
||||||
|
edited(text) => {
|
||||||
|
root.identicon = "";
|
||||||
|
}
|
||||||
|
accepted(text) => {
|
||||||
|
compute_identicon(line_edit_encrypted_key.text, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: identicon.is-empty ? "" : ("Check the identicon: " + identicon);
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: Style.spacing;
|
||||||
|
button_cancel := Button {
|
||||||
|
text: "Cancel";
|
||||||
|
}
|
||||||
|
|
||||||
|
button_confirm := Button {
|
||||||
|
text: "Confirm";
|
||||||
|
enabled: !identicon.is-empty;
|
||||||
|
clicked => {
|
||||||
|
confirm(line_edit_encrypted_key.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,26 +5,42 @@ import { IconButton } from "widgets/icon-button.slint";
|
||||||
export component UsersView inherits HorizontalLayout {
|
export component UsersView inherits HorizontalLayout {
|
||||||
padding: Style.spacing;
|
padding: Style.spacing;
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
in property <[StandardListViewItem]> model <=> list_view_sites.model;
|
callback edit_user(username: string, new: bool);
|
||||||
in-out property <int> current-item <=> list_view_sites.current-item;
|
|
||||||
callback pw_edited <=> line_edit_site_pw.edited;
|
|
||||||
callback pw_accepted <=> line_edit_site_pw.accepted;
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing * 2;
|
||||||
Text {
|
VerticalLayout {
|
||||||
text: "Username:";
|
spacing: Style.spacing;
|
||||||
|
Text {
|
||||||
|
text: "Add new user:";
|
||||||
|
}
|
||||||
|
|
||||||
|
line_edit_site_pw := LineEdit {
|
||||||
|
input-type: InputType.text;
|
||||||
|
placeholder-text: "Full Name";
|
||||||
|
accepted(text) => {
|
||||||
|
edit_user(text, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
line_edit_site_pw := LineEdit {
|
VerticalLayout {
|
||||||
input-type: InputType.text;
|
spacing: Style.spacing;
|
||||||
placeholder-text: "Full Name";
|
Text {
|
||||||
}
|
text: "Edit existing user:";
|
||||||
|
}
|
||||||
|
|
||||||
list_view_sites := StandardListView {
|
FocusScope {
|
||||||
model: [
|
forward-focus: list_view_sites;
|
||||||
{ text: "Test" },
|
key-pressed(event) => {
|
||||||
{ text: "Test" },
|
if event.text == "\u{0D}" /* enter */ || event.text == " " /* space */ {
|
||||||
];
|
edit_user(list_view_sites.current-item, false);
|
||||||
|
EventResult.accept
|
||||||
|
} else {
|
||||||
|
EventResult.reject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list_view_sites := StandardListView { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue