Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

[Unreleased]: https://github.com/trussed-dev/admin-app/compare/0.4.0...HEAD
[Unreleased]: https://github.com/trussed-dev/admin-app/compare/0.5.0...HEAD

-

## [0.5.0] 2026-09-16

[0.5.0]: https://github.com/trussed-dev/admin-app/compare/0.4.0...0.5.0

- Remove `ResetSignal`. It is now in `trussed-core`.

## [0.4.0] 2026-09-16

[0.4.0]: https://github.com/trussed-dev/admin-app/compare/0.3.0...0.4.0
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "admin-app"
version = "0.4.0"
version = "0.5.0"
authors = ["Conor Patrick <conor@solokeys.com>", "Nicolas Stalder <nicolas@solokeys.com>"]
repository = "https://github.com/solokeys/admin-app"
edition = "2021"
Expand All @@ -18,9 +18,8 @@ iso7816 = "0.2"
littlefs2 = { version = "0.8", optional = true }
littlefs2-core = { version = "0.1", features = ["heapless-bytes05"] }
serde = { version = "1.0.180", default-features = false }
strum_macros = "0.25.2"
trussed = { version = "0.2", default-features = false }
trussed-core = { version = "0.2", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] }
trussed-core = { version = "0.2.3", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] }

embedded-hal = { version = "0.2.7", optional = true }
hex-literal = "0.4.1"
Expand Down
150 changes: 42 additions & 108 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,116 +1,20 @@
use core::{
fmt::{self, Display, Formatter, Write as _},
str::FromStr,
sync::atomic::{AtomicU8, Ordering},
};

use cbor_smol::{cbor_deserialize, cbor_serialize_to};
use heapless::{string::StringView, VecView};
use littlefs2_core::{path, Path};
use serde::{de::DeserializeOwned, Serialize};
use strum_macros::FromRepr;
use trussed::store::Filestore;
use trussed_core::{
reset_signal::ResetSignalAllocation,
try_syscall,
types::{Location, Message},
FilesystemClient,
};

#[derive(Debug)]
/// Structure meant to be stored in a `static` to signal applications that they have been factory-resetted by the admin app
///
/// It is expected to have one such structure for each application supporting factory-reset by the admin-app
///
/// ```rust,ignore
///# use admin_app::{ResetSignalAllocation, ConfigValueMut};
///# use littlefs2::{path::Path, path};
/// #[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)]
/// struct Config {
/// use_new_backend: bool,
///};
/// static OPCARD_RESET: ResetSignalAllocation = ResetSignalAllocation::new();
/// impl admin_app::Config for Config {
/// fn field(&mut self, key: &str) -> Option<ConfigValueMut<'_>> {
/// match key {
/// "opcard.use_new_backend" => Some(ConfigValueMut::Bool(&mut self.use_new_backend)),
/// _ => None,
/// }
/// }
/// /// Client ID to factory-reset if the associated configuration option is changed
/// fn reset_client_id(&self, key: &str) -> Option<(&'static Path, &'static ResetSignalAllocation)> {
/// match key {
/// "opcard" => Some((path!("opcard"), &OPCARD_RESET)),
/// "opcard.use_new_backend" =>Some((path!("opcard"), &OPCARD_RESET)),
/// _ => None,
/// }
/// }
/// }
/// ```
pub struct ResetSignalAllocation(AtomicU8);

impl Default for ResetSignalAllocation {
fn default() -> Self {
Self::new()
}
}

impl ResetSignalAllocation {
pub const fn new() -> Self {
Self(AtomicU8::new(ResetSignal::None as u8))
}

pub fn load(&self) -> ResetSignal {
let v = self.0.load(Ordering::Relaxed);
ResetSignal::from_repr(v).expect("A reset signal value")
}

pub fn set_factory_reset(&self) -> bool {
self.0
.compare_exchange(
ResetSignal::None as u8,
ResetSignal::FactoryReset as u8,
Ordering::Relaxed,
Ordering::Relaxed,
)
.is_ok()
}

pub fn set_config_changed(&self) {
self.0
.store(ResetSignal::ConfigChanged as u8, Ordering::Relaxed)
}

/// Factory reset can be acknowledged so that the application can restart working
///
/// A configuration change cannot be acknowledged as it requires a power cycle to be taken into account.
pub fn ack_factory_reset(&self) -> bool {
self.0
.compare_exchange(
ResetSignal::FactoryReset as u8,
ResetSignal::None as u8,
Ordering::Relaxed,
Ordering::Relaxed,
)
.is_ok()
}
}

#[derive(Debug, FromRepr, Default)]
#[repr(u8)]
pub enum ResetSignal {
#[default]
/// The App can continue operating
None,
/// The app has had it state factory reseted by the admin app
///
/// It should delete any runtime state it is currently holding, then [`acknowledge`](ResetSignalAllocation::ack_factory_reset) the reset and continue working.
FactoryReset,
/// A configuration relevant to the application has been changed.
///
/// The application must reject all incoming request and store no persistent state until a power cycle.
ConfigChanged,
}

const LOCATION: Location = Location::Internal;
const FILENAME: &Path = path!("config");

Expand Down Expand Up @@ -270,19 +174,49 @@ impl<'a> Display for ConfigValueMut<'a> {
}
}

#[derive(Debug, FromRepr)]
#[repr(u8)]
pub enum ConfigError {
ReadFailed = 1,
WriteFailed = 2,
DeserializationFailed = 3,
SerializationFailed = 4,
InvalidKey = 5,
InvalidValue = 6,
DataTooLong = 7,
NotConfirmed = 8,
macro_rules! enum_u8 {
(
$(#[$outer:meta])*
$vis:vis enum $name:ident {
$($(#[$attr:meta])* $var:ident = $num:expr),+
$(,)*
}
) => {
$(#[$outer])*
#[repr(u8)]
$vis enum $name {
$(
$(#[$attr])*
$var = $num,
)*
}

impl $name {
const fn from_repr(val: u8) -> Option<$name> {
match val {
$(
$num => Some($name::$var),
)*
_ => None,
}
}
}
}
}

enum_u8!(
#[derive(Debug)]
pub enum ConfigError {
ReadFailed = 1,
WriteFailed = 2,
DeserializationFailed = 3,
SerializationFailed = 4,
InvalidKey = 5,
InvalidValue = 6,
DataTooLong = 7,
NotConfirmed = 8,
}
);
const _: () = assert!(
ConfigError::from_repr(0).is_none(),
"ConfigError may not have a variant with discriminant zero as zero indicates success.",
Expand Down
5 changes: 1 addition & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ mod config;
pub mod migrations;

pub use admin::{App, Data, StatusBytes};
pub use config::{
Config, ConfigError, ConfigField, ConfigValueMut, FieldType, ResetConfigResult, ResetSignal,
ResetSignalAllocation,
};
pub use config::{Config, ConfigError, ConfigField, ConfigValueMut, FieldType, ResetConfigResult};
use trussed_manage::ManageClient;
#[cfg(feature = "se050")]
use trussed_se050_manage::Se050ManageClient;
Expand Down
Loading