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}