1use core::{
2 fmt::{self, Display, Formatter, Write as _},
3 str::FromStr,
4 sync::atomic::{AtomicU8, Ordering},
5};
6
7use cbor_smol::{cbor_deserialize, cbor_serialize_to};
8use heapless::VecView;
9use littlefs2_core::{path, Path};
10use serde::{de::DeserializeOwned, Serialize};
11use strum_macros::FromRepr;
12use trussed::store::Filestore;
13use trussed_core::{
14 try_syscall,
15 types::{Location, Message},
16 FilesystemClient,
17};
18
19#[derive(Debug)]
20pub struct ResetSignalAllocation(AtomicU8);
50
51impl Default for ResetSignalAllocation {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl ResetSignalAllocation {
58 pub const fn new() -> Self {
59 Self(AtomicU8::new(ResetSignal::None as u8))
60 }
61
62 pub fn load(&self) -> ResetSignal {
63 let v = self.0.load(Ordering::Relaxed);
64 ResetSignal::from_repr(v).expect("A reset signal value")
65 }
66
67 pub fn set_factory_reset(&self) -> bool {
68 self.0
69 .compare_exchange(
70 ResetSignal::None as u8,
71 ResetSignal::FactoryReset as u8,
72 Ordering::Relaxed,
73 Ordering::Relaxed,
74 )
75 .is_ok()
76 }
77
78 pub fn set_config_changed(&self) {
79 self.0
80 .store(ResetSignal::ConfigChanged as u8, Ordering::Relaxed)
81 }
82
83 pub fn ack_factory_reset(&self) -> bool {
87 self.0
88 .compare_exchange(
89 ResetSignal::FactoryReset as u8,
90 ResetSignal::None as u8,
91 Ordering::Relaxed,
92 Ordering::Relaxed,
93 )
94 .is_ok()
95 }
96}
97
98#[derive(Debug, FromRepr, Default)]
99#[repr(u8)]
100pub enum ResetSignal {
101 #[default]
102 None,
104 FactoryReset,
108 ConfigChanged,
112}
113
114const LOCATION: Location = Location::Internal;
115const FILENAME: &Path = path!("config");
116
117#[derive(Debug, Clone, Copy)]
118pub enum ResetConfigResult {
119 Changed,
121 Unchanged,
123 WrongKey,
125}
126
127impl ResetConfigResult {
128 pub fn is_changed(&self) -> bool {
129 matches!(self, Self::Changed)
130 }
131 pub fn is_unchanged(&self) -> bool {
132 matches!(self, Self::Unchanged)
133 }
134 pub fn is_error(&self) -> bool {
135 matches!(self, Self::WrongKey)
136 }
137}
138
139pub trait Config: Default + PartialEq + DeserializeOwned + Serialize {
140 fn field(&mut self, key: &str) -> Option<ConfigValueMut<'_>>;
141
142 fn reset_client_id(
151 &self,
152 _key: &str,
153 ) -> Option<(&'static Path, &'static ResetSignalAllocation)> {
154 None
155 }
156
157 fn reset_client_config(&mut self, _key: &str) -> ResetConfigResult {
161 ResetConfigResult::WrongKey
162 }
163
164 fn migration_version(&self) -> Option<u32>;
168
169 fn set_migration_version(&mut self, _version: u32) -> bool;
173
174 fn list_available_fields(&self) -> &'static [ConfigField];
175}
176
177#[derive(Serialize)]
179#[non_exhaustive]
180pub enum FieldType {
181 Bool,
182 U8,
183}
184
185#[derive(Serialize)]
186pub struct ConfigField {
187 #[serde(rename = "n")]
188 pub name: &'static str,
189 #[serde(rename = "c")]
191 pub requires_touch_confirmation: bool,
192 #[serde(rename = "r")]
194 pub requires_reboot: bool,
195 #[serde(rename = "d")]
197 pub destructive: bool,
198 #[serde(rename = "t")]
200 pub ty: FieldType,
201}
202
203impl Config for () {
204 fn field(&mut self, _key: &str) -> Option<ConfigValueMut<'_>> {
205 None
206 }
207
208 fn reset_client_config(&mut self, _key: &str) -> ResetConfigResult {
209 ResetConfigResult::WrongKey
210 }
211
212 fn migration_version(&self) -> Option<u32> {
213 None
214 }
215
216 fn set_migration_version(&mut self, _version: u32) -> bool {
217 false
218 }
219
220 fn list_available_fields(&self) -> &'static [ConfigField] {
221 &[]
222 }
223}
224
225#[derive(Debug, Serialize)]
226pub enum ConfigValueMut<'a> {
227 Bool(&'a mut bool),
228 U8(&'a mut u8),
229}
230
231impl<'a> ConfigValueMut<'a> {
232 fn set(&mut self, value: &str) -> Result<(), ConfigError> {
233 fn set_value<T: FromStr>(target: &mut T, s: &str) -> Result<(), ConfigError> {
234 *target = s.parse().map_err(|_| ConfigError::InvalidValue)?;
235 Ok(())
236 }
237
238 match self {
239 Self::Bool(r) => set_value(*r, value),
240 Self::U8(r) => set_value(*r, value),
241 }
242 }
243}
244
245impl<'a> Display for ConfigValueMut<'a> {
246 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
247 match self {
248 Self::Bool(value) => write!(f, "{value}"),
249 Self::U8(value) => write!(f, "{value}"),
250 }
251 }
252}
253
254#[derive(Debug, FromRepr)]
255#[repr(u8)]
256pub enum ConfigError {
257 ReadFailed = 1,
258 WriteFailed = 2,
259 DeserializationFailed = 3,
260 SerializationFailed = 4,
261 InvalidKey = 5,
262 InvalidValue = 6,
263 DataTooLong = 7,
264 NotConfirmed = 8,
265}
266
267const _: () = assert!(
268 ConfigError::from_repr(0).is_none(),
269 "ConfigError may not have a variant with discriminant zero as zero indicates success.",
270);
271
272impl From<ConfigError> for u8 {
273 fn from(error: ConfigError) -> u8 {
274 error as _
275 }
276}
277
278pub fn get<C: Config>(
279 config: &mut C,
280 key: &str,
281 response: &mut VecView<u8>,
282) -> Result<(), ConfigError> {
283 let field = config.field(key).ok_or(ConfigError::InvalidKey)?;
284 write!(response, "{field}").map_err(|_| ConfigError::DataTooLong)
285}
286
287pub fn set<C: Config>(config: &mut C, key: &str, value: &str) -> Result<(), ConfigError> {
288 config
289 .field(key)
290 .ok_or(ConfigError::InvalidKey)?
291 .set(value)?;
292 Ok(())
293}
294
295pub fn load<F: Filestore, C: Config>(store: &mut F) -> Result<C, ConfigError> {
296 let Some(data) = load_if_exists(store, LOCATION, FILENAME)? else {
297 return Ok(Default::default());
298 };
299 cbor_deserialize(&data).map_err(|_| ConfigError::DeserializationFailed)
300}
301
302pub fn save_filestore<F: Filestore, C: Config>(
303 store: &mut F,
304 config: &C,
305) -> Result<(), ConfigError> {
306 if config == &C::default() {
307 if store.exists(FILENAME, LOCATION) {
308 store
309 .remove_file(FILENAME, LOCATION)
310 .map_err(|_| ConfigError::WriteFailed)?;
311 }
312 } else {
313 let mut data = Message::new();
314 cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?;
315 store
316 .write(FILENAME, LOCATION, &data)
317 .map_err(|_| ConfigError::SerializationFailed)?;
318 }
319 Ok(())
320}
321
322pub fn save<T: FilesystemClient, C: Config>(client: &mut T, config: &C) -> Result<(), ConfigError> {
323 if config == &Default::default() {
324 if exists(client, LOCATION, FILENAME)? {
325 try_syscall!(client.remove_file(LOCATION, FILENAME.into()))
326 .map_err(|_| ConfigError::WriteFailed)?;
327 }
328 } else {
329 let mut data = Message::new();
330 cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?;
331 try_syscall!(client.write_file(LOCATION, FILENAME.into(), data, None))
332 .map_err(|_| ConfigError::WriteFailed)?;
333 }
334 Ok(())
335}
336
337fn exists<T: FilesystemClient>(
338 client: &mut T,
339 location: Location,
340 path: &Path,
341) -> Result<bool, ConfigError> {
342 try_syscall!(client.entry_metadata(location, path.into()))
343 .map(|r| r.metadata.is_some())
344 .map_err(|_| ConfigError::ReadFailed)
345}
346
347fn load_if_exists<F: Filestore>(
348 store: &mut F,
349 location: Location,
350 path: &Path,
351) -> Result<Option<Message>, ConfigError> {
352 store.read(path, location).map(Some).or_else(|_| {
353 if store.exists(path, location) {
354 Err(ConfigError::ReadFailed)
355 } else {
356 Ok(None)
357 }
358 })
359}
360
361#[cfg(test)]
362mod tests {
363 use hex_literal::hex;
364
365 use super::*;
366
367 #[test]
368 fn config_field() {
369 let fields = &[ConfigField {
370 name: "test_name",
371 requires_touch_confirmation: true,
372 requires_reboot: false,
373 destructive: true,
374 ty: FieldType::Bool,
375 }];
376 let mut bytes: heapless::Vec<u8, 100> = Default::default();
377 cbor_smol::cbor_serialize_to(fields, &mut bytes).unwrap();
378 assert_eq!(
379 &bytes,
380 &hex!("81A5616E69746573745F6E616D656163F56172F46164F5617400")
381 );
382 }
383}