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::{string::StringView, 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)]
181#[non_exhaustive]
182pub enum FieldType {
183 Bool,
184 U8,
185 String,
189}
190
191#[derive(Serialize)]
192pub struct ConfigField {
193 #[serde(rename = "n")]
194 pub name: &'static str,
195 #[serde(rename = "c")]
197 pub requires_touch_confirmation: bool,
198 #[serde(rename = "r")]
200 pub requires_reboot: bool,
201 #[serde(rename = "d")]
203 pub destructive: bool,
204 #[serde(rename = "t")]
206 pub ty: FieldType,
207}
208
209impl Config for () {
210 fn field(&mut self, _key: &str) -> Option<ConfigValueMut<'_>> {
211 None
212 }
213
214 fn reset_client_config(&mut self, _key: &str) -> ResetConfigResult {
215 ResetConfigResult::WrongKey
216 }
217
218 fn migration_version(&self) -> Option<u32> {
219 None
220 }
221
222 fn set_migration_version(&mut self, _version: u32) -> bool {
223 false
224 }
225
226 fn list_available_fields(&self) -> &'static [ConfigField] {
227 &[]
228 }
229}
230
231#[derive(Debug, Serialize)]
232#[non_exhaustive]
233pub enum ConfigValueMut<'a> {
234 Bool(&'a mut bool),
235 U8(&'a mut u8),
236 String(&'a mut StringView),
238}
239
240impl<'a> ConfigValueMut<'a> {
241 fn set(&mut self, value: &str) -> Result<(), ConfigError> {
242 fn set_value<T: FromStr>(target: &mut T, s: &str) -> Result<(), ConfigError> {
243 *target = s.parse().map_err(|_| ConfigError::InvalidValue)?;
244 Ok(())
245 }
246
247 match self {
248 Self::Bool(r) => set_value(*r, value),
249 Self::U8(r) => set_value(*r, value),
250 Self::String(r) => {
251 if value.len() > r.capacity() {
254 return Err(ConfigError::DataTooLong);
255 }
256 r.clear();
257 r.push_str(value).map_err(|_| ConfigError::DataTooLong)
258 }
259 }
260 }
261}
262
263impl<'a> Display for ConfigValueMut<'a> {
264 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
265 match self {
266 Self::Bool(value) => write!(f, "{value}"),
267 Self::U8(value) => write!(f, "{value}"),
268 Self::String(value) => f.write_str(value),
269 }
270 }
271}
272
273#[derive(Debug, FromRepr)]
274#[repr(u8)]
275pub enum ConfigError {
276 ReadFailed = 1,
277 WriteFailed = 2,
278 DeserializationFailed = 3,
279 SerializationFailed = 4,
280 InvalidKey = 5,
281 InvalidValue = 6,
282 DataTooLong = 7,
283 NotConfirmed = 8,
284}
285
286const _: () = assert!(
287 ConfigError::from_repr(0).is_none(),
288 "ConfigError may not have a variant with discriminant zero as zero indicates success.",
289);
290
291impl From<ConfigError> for u8 {
292 fn from(error: ConfigError) -> u8 {
293 error as _
294 }
295}
296
297pub fn get<C: Config>(
298 config: &mut C,
299 key: &str,
300 response: &mut VecView<u8>,
301) -> Result<(), ConfigError> {
302 let field = config.field(key).ok_or(ConfigError::InvalidKey)?;
303 write!(response, "{field}").map_err(|_| ConfigError::DataTooLong)
304}
305
306pub fn set<C: Config>(config: &mut C, key: &str, value: &str) -> Result<(), ConfigError> {
307 config
308 .field(key)
309 .ok_or(ConfigError::InvalidKey)?
310 .set(value)?;
311 Ok(())
312}
313
314pub fn load<F: Filestore, C: Config>(store: &mut F) -> Result<C, ConfigError> {
315 let Some(data) = load_if_exists(store, LOCATION, FILENAME)? else {
316 return Ok(Default::default());
317 };
318 cbor_deserialize(&data).map_err(|_| ConfigError::DeserializationFailed)
319}
320
321pub fn save_filestore<F: Filestore, C: Config>(
322 store: &mut F,
323 config: &C,
324) -> Result<(), ConfigError> {
325 if config == &C::default() {
326 if store.exists(FILENAME, LOCATION) {
327 store
328 .remove_file(FILENAME, LOCATION)
329 .map_err(|_| ConfigError::WriteFailed)?;
330 }
331 } else {
332 let mut data = Message::new();
333 cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?;
334 store
335 .write(FILENAME, LOCATION, &data)
336 .map_err(|_| ConfigError::SerializationFailed)?;
337 }
338 Ok(())
339}
340
341pub fn save<T: FilesystemClient, C: Config>(client: &mut T, config: &C) -> Result<(), ConfigError> {
342 if config == &Default::default() {
343 if exists(client, LOCATION, FILENAME)? {
344 try_syscall!(client.remove_file(LOCATION, FILENAME.into()))
345 .map_err(|_| ConfigError::WriteFailed)?;
346 }
347 } else {
348 let mut data = Message::new();
349 cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?;
350 try_syscall!(client.write_file(LOCATION, FILENAME.into(), data, None))
351 .map_err(|_| ConfigError::WriteFailed)?;
352 }
353 Ok(())
354}
355
356fn exists<T: FilesystemClient>(
357 client: &mut T,
358 location: Location,
359 path: &Path,
360) -> Result<bool, ConfigError> {
361 try_syscall!(client.entry_metadata(location, path.into()))
362 .map(|r| r.metadata.is_some())
363 .map_err(|_| ConfigError::ReadFailed)
364}
365
366fn load_if_exists<F: Filestore>(
367 store: &mut F,
368 location: Location,
369 path: &Path,
370) -> Result<Option<Message>, ConfigError> {
371 store.read(path, location).map(Some).or_else(|_| {
372 if store.exists(path, location) {
373 Err(ConfigError::ReadFailed)
374 } else {
375 Ok(None)
376 }
377 })
378}
379
380#[cfg(test)]
381mod tests {
382 use hex_literal::hex;
383
384 use super::*;
385
386 #[test]
387 fn config_field() {
388 let fields = &[ConfigField {
389 name: "test_name",
390 requires_touch_confirmation: true,
391 requires_reboot: false,
392 destructive: true,
393 ty: FieldType::Bool,
394 }];
395 let mut bytes: heapless::Vec<u8, 100> = Default::default();
396 cbor_smol::cbor_serialize_to(fields, &mut bytes).unwrap();
397 assert_eq!(
398 &bytes,
399 &hex!("81A5616E69746573745F6E616D656163F56172F46164F5617400")
400 );
401 }
402
403 #[test]
405 fn field_type_ids() {
406 for (ty, id) in [
407 (FieldType::Bool, hex!("00").as_slice()),
408 (FieldType::U8, hex!("01").as_slice()),
409 (FieldType::String, hex!("02").as_slice()),
410 ] {
411 let mut bytes: heapless::Vec<u8, 8> = Default::default();
412 cbor_smol::cbor_serialize_to(&ty, &mut bytes).unwrap();
413 assert_eq!(bytes.as_slice(), id);
414 }
415 }
416
417 #[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)]
418 struct TestConfig {
419 label: heapless::String<8>,
420 }
421
422 impl Config for TestConfig {
423 fn field(&mut self, key: &str) -> Option<ConfigValueMut<'_>> {
424 match key {
425 "label" => Some(ConfigValueMut::String(self.label.as_mut_view())),
426 _ => None,
427 }
428 }
429
430 fn migration_version(&self) -> Option<u32> {
431 None
432 }
433
434 fn set_migration_version(&mut self, _version: u32) -> bool {
435 false
436 }
437
438 fn list_available_fields(&self) -> &'static [ConfigField] {
439 &[]
440 }
441 }
442
443 fn get_field(config: &mut TestConfig, key: &str) -> Result<heapless::String<32>, ConfigError> {
444 let mut response: heapless::Vec<u8, 32> = Default::default();
445 get(config, key, response.as_mut_view())?;
446 Ok(core::str::from_utf8(&response).unwrap().try_into().unwrap())
447 }
448
449 #[test]
450 fn string_field() {
451 let mut config = TestConfig::default();
452 assert_eq!(get_field(&mut config, "label").unwrap(), "");
453
454 set(&mut config, "label", "Backup").unwrap();
455 assert_eq!(config.label, "Backup");
456 assert_eq!(get_field(&mut config, "label").unwrap(), "Backup");
457
458 set(&mut config, "label", "12345678").unwrap();
459 assert_eq!(config.label, "12345678");
460 set(&mut config, "label", "").unwrap();
461 assert_eq!(config.label, "");
462 }
463
464 #[test]
465 fn string_field_too_long() {
466 let mut config = TestConfig::default();
467 set(&mut config, "label", "old").unwrap();
468
469 let error = set(&mut config, "label", "123456789").unwrap_err();
470 assert!(matches!(error, ConfigError::DataTooLong), "{error:?}");
471 assert_eq!(config.label, "old");
473 }
474
475 #[test]
477 fn string_field_arbitrary_utf8() {
478 let mut config = TestConfig::default();
479
480 for value in ["a\nb", "\x1b[2J", "\u{202e}", "\u{2028}", "Χ’ΧΧ¨"] {
481 set(&mut config, "label", value).unwrap_or_else(|e| panic!("{value:?}: {e:?}"));
482 assert_eq!(config.label, value);
483 }
484 }
485
486 #[test]
488 fn string_field_multibyte() {
489 let mut config = TestConfig::default();
490
491 set(&mut config, "label", "ππ").unwrap();
493 assert_eq!(config.label, "ππ");
494 assert_eq!(config.label.len(), 8);
495
496 let error = set(&mut config, "label", "δΈδΈδΈ").unwrap_err();
497 assert!(matches!(error, ConfigError::DataTooLong), "{error:?}");
498 assert_eq!(config.label, "ππ");
499 }
500}