cfg_noodle/lib.rs
1//! Configuration management
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
4#![warn(missing_docs)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::undocumented_unsafe_blocks)]
7#![allow(async_fn_in_trait)]
8#![allow(
9 clippy::uninlined_format_args,
10 reason = "Having inlined variables does not work with defmt"
11)]
12
13pub mod data_portability;
14pub mod error;
15pub mod flash;
16pub mod safety_guide;
17pub mod worker_task;
18
19// re-export some dependencies
20pub use minicbor;
21pub use mutex;
22pub use mutex_traits;
23pub use sequential_storage;
24
25#[cfg(any(test, feature = "std"))]
26#[doc(hidden)]
27pub mod test_utils;
28
29mod storage_list;
30mod storage_node;
31
32use core::num::NonZeroU32;
33
34use minicbor::{CborLen, Encode, len_with};
35#[doc(inline)]
36pub use storage_list::StorageList;
37
38#[doc(inline)]
39pub use storage_node::{State, StorageListNode, StorageListNodeHandle};
40
41#[allow(unused)]
42pub(crate) mod logging {
43 #[cfg(feature = "std")]
44 pub use log::*;
45
46 #[cfg(feature = "defmt")]
47 pub use defmt::*;
48
49 #[cfg(all(feature = "std", feature = "defmt"))]
50 compile_error!("Cannot enable both 'std' and 'defmt' features simultaneously");
51
52 /// No-op macros when no logging feature is enabled
53 #[cfg(not(any(feature = "std", feature = "defmt")))]
54 macro_rules! trace {
55 ($s:literal $(, $x:expr)* $(,)?) => {
56 {
57 let _ = ($( & $x ),*);
58 }
59 };
60 }
61
62 #[cfg(not(any(feature = "std", feature = "defmt")))]
63 macro_rules! debug {
64 ($s:literal $(, $x:expr)* $(,)?) => {
65 {
66 let _ = ($( & $x ),*);
67 }
68 };
69 }
70
71 #[cfg(not(any(feature = "std", feature = "defmt")))]
72 macro_rules! info {
73 ($s:literal $(, $x:expr)* $(,)?) => {
74 {
75 let _ = ($( & $x ),*);
76 }
77 };
78 }
79
80 #[cfg(not(any(feature = "std", feature = "defmt")))]
81 macro_rules! log_warn {
82 ($s:literal $(, $x:expr)* $(,)?) => {
83 {
84 let _ = ($( & $x ),*);
85 }
86 };
87 }
88
89 #[cfg(not(any(feature = "std", feature = "defmt")))]
90 macro_rules! error {
91 ($s:literal $(, $x:expr)* $(,)?) => {{
92 let _ = ($( & $x ),*);
93 }
94 };
95 }
96
97 #[cfg(not(any(feature = "std", feature = "defmt")))]
98 pub(crate) use {debug, error, info, log_warn as warn, trace};
99
100 /// A marker trait that requires `T: defmt::Format` when the `defmt` feature is enabled
101 #[cfg(not(feature = "defmt"))]
102 pub trait MaybeDefmtFormat {}
103
104 /// A marker trait that requires `T: defmt::Format` when the `defmt` feature is enabled
105 #[cfg(feature = "defmt")]
106 pub trait MaybeDefmtFormat: defmt::Format {}
107
108 #[cfg(not(feature = "defmt"))]
109 impl<T> MaybeDefmtFormat for T {}
110
111 #[cfg(feature = "defmt")]
112 impl<T: defmt::Format> MaybeDefmtFormat for T {}
113}
114
115/// Const helper to compute the maximum of two usize values
116const fn max(a: usize, b: usize) -> usize {
117 if a > b { a } else { b }
118}
119
120/// Constant values
121///
122/// Currently, this is largely to encode the header byte of [`Elem`]s, which
123/// use the upper 4 bits for "version" (currently only [`ELEM_VERSION_V0`] is supported),
124/// and the lower 4 bits for "discriminant".
125///
126/// [`ELEM_VERSION_V0`]: consts::ELEM_VERSION_V0
127pub mod consts {
128 /// Mask for the "Version" portion of the element byte
129 pub const ELEM_VERSION_MASK: u8 = 0b1111_0000;
130 /// Mask for the "Discriminant" portion of the element byte
131 pub const ELEM_DISCRIMINANT_MASK: u8 = 0b0000_1111;
132
133 /// Current Elem version
134 pub const ELEM_VERSION_V0: u8 = 0b0000_0000;
135
136 /// Discriminant used to mark Start elements on disk
137 pub const ELEM_DISCRIMINANT_START: u8 = 0b0000_0000;
138 /// Discriminant used to mark Data elements on disk
139 pub const ELEM_DISCRIMINANT_DATA: u8 = 0b0000_0001;
140 /// Discriminant used to mark End elements on disk
141 pub const ELEM_DISCRIMINANT_END: u8 = 0b0000_0010;
142}
143
144/// Serialized Data Element
145///
146/// Includes header, key, and value
147#[cfg_attr(feature = "defmt", derive(defmt::Format))]
148#[derive(Debug, PartialEq)]
149pub struct SerData<'a> {
150 hdr_key_val: &'a [u8],
151}
152
153impl<'a> SerData<'a> {
154 /// Create a new Serialized Data Element.
155 ///
156 /// `data[0]` MUST be the header position, with key+val starting
157 /// at `data[1]`. The header will be overwritten with the data discriminant
158 ///
159 /// Returns None if the slice is empty.
160 pub fn new(data: &'a mut [u8]) -> Option<Self> {
161 let f = data.first_mut()?;
162 *f = consts::ELEM_VERSION_V0 | consts::ELEM_DISCRIMINANT_DATA;
163
164 Some(Self { hdr_key_val: data })
165 }
166
167 /// Create a Serialized Data Element from an existing slice. The
168 /// discriminant will NOT be written.
169 ///
170 /// Returns None if the slice is empty.
171 pub fn from_existing(data: &'a [u8]) -> Option<Self> {
172 if data.is_empty() {
173 None
174 } else {
175 Some(Self { hdr_key_val: data })
176 }
177 }
178
179 /// Obtain the header
180 pub fn hdr(&self) -> u8 {
181 // SAFETY: We checked the slice is not empty
182 unsafe { *self.hdr_key_val.get_unchecked(0) }
183 }
184
185 /// Get the key+val portion of the SerData
186 ///
187 /// Will return an empty slice if the slice was originally empty
188 pub fn key_val(&self) -> &[u8] {
189 self.hdr_key_val.get(1..).unwrap_or(&[])
190 }
191
192 /// Get the split key and value. This is the same data as [Self::key_val], but one parsing step further.
193 ///
194 /// The value contains the cbor bytes
195 pub fn kv_pair(&self) -> Result<KvPair<'_>, Error> {
196 let item = self.key_val();
197
198 let Ok(key) = minicbor::decode::<&str>(item) else {
199 return Err(Error::Deserialization);
200 };
201 let len = len_with(key, &mut ());
202 let Some(remain) = item.get(len..) else {
203 return Err(Error::Deserialization);
204 };
205 Ok(KvPair { key, body: remain })
206 }
207}
208
209/// A key-value pair
210pub struct KvPair<'a> {
211 /// The key of the pair
212 pub key: &'a str,
213 /// The body of the pair
214 pub body: &'a [u8],
215}
216
217/// A single element stored in flash
218#[cfg_attr(feature = "defmt", derive(defmt::Format))]
219#[derive(Debug, PartialEq)]
220pub enum Elem<'a> {
221 /// Start element
222 Start {
223 /// The "write record" sequence number
224 seq_no: NonZeroU32,
225 },
226 /// Data element
227 Data {
228 /// Contains the serialized key and value for the current data element
229 data: SerData<'a>,
230 },
231 /// End element
232 End {
233 /// The "write record" sequence number. Must match `Elem::Start { seq_no }`
234 /// to properly end a "write record".
235 seq_no: NonZeroU32,
236 /// The CRC32 of ALL `Elem::Data { data }` fields, in the other they appear
237 /// in the FIFO queue.
238 calc_crc: u32,
239 },
240}
241
242/// A storage backend representing a FIFO queue of elements
243pub trait NdlDataStorage {
244 /// The type of iterator returned by this implementation
245 type Iter<'this>: NdlElemIter<Error = Self::Error>
246 where
247 Self: 'this;
248 /// The error returned when pushing fails
249 type Error: MaybeDefmtFormat;
250
251 /// Returns an iterator over all elements, back to front.
252 ///
253 /// This method MUST be cancellation safe, and cancellation MUST NOT lead to
254 /// data loss.
255 async fn iter_elems<'this>(
256 &'this mut self,
257 ) -> Result<Self::Iter<'this>, <Self::Iter<'this> as NdlElemIter>::Error>;
258
259 /// Insert an element at the FRONT of the list.
260 ///
261 /// On success, returns the size (in bytes) of the pushed element in storage.
262 ///
263 /// This method MUST be cancellation safe, however if cancelled, it is not
264 /// specified whether the item has been successfully written or not.
265 /// Cancellation MUST NOT lead to data loss, other than the element currently
266 /// being written.
267 async fn push(&mut self, data: &Elem<'_>) -> Result<usize, Self::Error>;
268
269 /// Return the maximum size of an `Elem` that may be stored in the list in bytes.
270 ///
271 /// This includes a one byte element header, the CBOR-serialized key, and the
272 /// CBOR-serialized value.
273 const MAX_ELEM_SIZE: usize;
274
275 /// Checks whether the size of the `key` and `node` fit into the maximium
276 /// element size of this `NdlDataStorage`.
277 ///
278 /// This function can be used to check if writing the node to flash is
279 /// possible before the node is serialized at a later stage.
280 fn check_node_size<T>(key: &str, node: T) -> bool
281 where
282 T: CborLen<()> + Encode<()>,
283 {
284 Self::MAX_ELEM_SIZE >= 1 + len_with(key, &mut ()) + len_with(node, &mut ())
285 }
286}
287
288impl<T: NdlDataStorage> NdlDataStorage for &mut T {
289 type Iter<'this>
290 = T::Iter<'this>
291 where
292 Self: 'this;
293 type Error = T::Error;
294
295 fn iter_elems<'this>(
296 &'this mut self,
297 ) -> impl Future<Output = Result<Self::Iter<'this>, <Self::Iter<'this> as NdlElemIter>::Error>>
298 {
299 T::iter_elems(self)
300 }
301
302 fn push(&mut self, data: &Elem<'_>) -> impl Future<Output = Result<usize, Self::Error>> {
303 T::push(self, data)
304 }
305
306 const MAX_ELEM_SIZE: usize = T::MAX_ELEM_SIZE;
307}
308
309/// An iterator over `Elem`s stored in the queue.
310pub trait NdlElemIter {
311 /// Items yielded by this iterator
312 type Item<'this, 'buf>: NdlElemIterNode<Error = Self::Error>
313 where
314 Self: 'this,
315 Self: 'buf;
316 /// The error returned when next/skip_to_seq or NdlDataStorage::iter_elems fails
317 type Error;
318
319 /// Obtain the next item, in oldest-to-newest order.
320 ///
321 /// The item returned MAY not be a valid Element, however access is still provided
322 /// to allow invalidation of this node when relevant.
323 ///
324 /// This method returns:
325 ///
326 /// - `Ok(Some(item))`: There is an item here, but it may or may not contain a valid
327 /// Elem when [`NdlElemIterNode::data()`] is called.
328 /// - `Ok(None)`: The end of the iterator has been reached successfully
329 /// - `Err(e)`: An error occurred while reading from the storage
330 ///
331 /// This method MUST be cancellation safe, however cancellation of this function
332 /// may require re-creation of the iterator (e.g. the iterator may return a
333 /// latched Error of some kind after cancellation). Cancellation MUST NOT lead
334 /// to data loss.
335 async fn next<'iter, 'buf>(
336 &'iter mut self,
337 buf: &'buf mut [u8],
338 ) -> Result<Option<Self::Item<'iter, 'buf>>, Self::Error>
339 where
340 Self: 'buf,
341 Self: 'iter;
342}
343
344/// A single element yielded from a NdlElemIter implementation
345#[allow(clippy::len_without_is_empty)]
346pub trait NdlElemIterNode {
347 /// Error encountered while invalidating an element
348 type Error;
349
350 /// Returns the present element.
351 ///
352 /// If the contained item is NOT a valid element, `None` is returned here.
353 /// This means that the storage did not consider this item invalid, however we
354 /// are unable to decode it as a valid [`Elem`].
355 fn data(&self) -> Option<Elem<'_>>;
356
357 /// The length (in bytes) of the element in storage, including any overhead
358 ///
359 /// This should return the length even if [`Self::data()`] returns `None`.
360 fn len(&self) -> usize;
361
362 /// Invalidate the element.
363 ///
364 /// If this operation succeeds, the current element should NEVER be returned from
365 /// future calls to `NdlElemIter::next()`. Implementors are free to decide how this
366 /// is done. Invalidating an element MAY require time-expensive work, such as a write
367 /// or erase (for example if all nodes of a flash sector have now been invalidated),
368 /// so this should not be called in time-sensitive code.
369 ///
370 /// This method MUST be cancellation-safe, but in the case of cancellation, may
371 /// require time-expensive recovery, so cancellation of this method should be
372 /// avoided in the normal case. If this method is cancelled, the element may or
373 /// may not be invalidated, however other currently-valid data MUST NOT be lost.
374 async fn invalidate(self) -> Result<(), Self::Error>;
375}
376
377use crc::{CRC_32_CKSUM, Crc, Digest, NoTable};
378
379use crate::{error::Error, logging::MaybeDefmtFormat};
380
381/// CRC32 implementation
382///
383/// Currently uses [`CRC_32_CKSUM`] from the [`crc`] crate with no table.
384///
385/// Wrapped for semver reasons
386pub struct Crc32(Digest<'static, u32, NoTable>);
387
388impl Crc32 {
389 const CRC: Crc<u32, NoTable> = Crc::<u32, NoTable>::new(&CRC_32_CKSUM);
390
391 /// Create new initial CRC digest
392 pub const fn new() -> Self {
393 Self(Self::CRC.digest())
394 }
395
396 /// Update the CRC with data
397 pub fn update(&mut self, data: &[u8]) {
398 self.0.update(data)
399 }
400
401 /// Finalize the CRC, producing the output
402 pub fn finalize(self) -> u32 {
403 self.0.finalize()
404 }
405}
406
407impl Default for Crc32 {
408 fn default() -> Self {
409 Self::new()
410 }
411}