amebazii/types/
nvdm.rs

1use itertools::Itertools;
2// This module is roughly based on the LinkIt SDK (Public), available here:
3// https://github.com/hermeszhang/linkit_sdk_public/
4// and source code here:
5// https://github.com/dangkhoalk95/demoMT/blob/master/middleware/MTK/nvdm_core
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    error::Error,
11    types::{from_stream, BinarySize, FromStream, ToStream},
12};
13
14/// Enum representing the type of an NVDM data item.
15#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
16#[repr(u8)]
17pub enum NvdmDataItemType {
18    /// Raw binary data with no implicit encoding.
19    RawData = 1,
20
21    /// Null-terminated or length-delimited UTF-8 string data.
22    String = 2,
23
24    // any other type
25    Unknown = 0,
26}
27
28impl TryFrom<u8> for NvdmDataItemType {
29    type Error = Error;
30
31    /// Attempts to convert a raw `u8` value into an [`NvdmDataItemType`].
32    ///
33    /// # Parameters
34    /// - `value`: The raw byte value read from NVDM storage.
35    ///
36    /// # Returns
37    /// - `Ok(NvdmDataItemType)` if the value corresponds to a known data item type.
38    fn try_from(value: u8) -> Result<Self, Self::Error> {
39        match value {
40            1 => Ok(NvdmDataItemType::RawData),
41            2 => Ok(NvdmDataItemType::String),
42            _ => Ok(NvdmDataItemType::Unknown),
43        }
44    }
45}
46
47/// Enum representing the lifecycle status of an NVDM data item.
48#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
49#[repr(u8)]
50pub enum DataItemStatus {
51    /// The data item is marked for deletion.
52    Delete = 248,
53
54    /// The data item is valid and fully written.
55    Valid = 252,
56
57    /// The data item is currently being written.
58    Writing = 254,
59
60    /// The data item slot is empty and unused.
61    Empty = 255,
62
63    /// any other status
64    Unknown = 0,
65}
66
67impl TryFrom<u8> for DataItemStatus {
68    type Error = Error;
69
70    /// Attempts to convert a raw `u8` value into a [`DataItemStatus`].
71    ///
72    /// # Parameters
73    /// - `value`: The raw status byte read from flash or NVDM metadata.
74    ///
75    /// # Returns
76    /// - `Ok(DataItemStatus)` if the value matches a known status.
77    fn try_from(value: u8) -> Result<Self, Self::Error> {
78        match value {
79            248 => Ok(DataItemStatus::Delete),
80            252 => Ok(DataItemStatus::Valid),
81            254 => Ok(DataItemStatus::Writing),
82            255 => Ok(DataItemStatus::Empty),
83            _ => Ok(DataItemStatus::Unknown),
84        }
85    }
86}
87
88/// Enum representing the status of a Physical Erase Block (PEB).
89///
90/// This enum maps directly to `peb_status_t` from the C implementation.
91/// The values are encoded as raw bytes in flash metadata and therefore
92/// occupy the upper range of a `u8`.
93///
94/// The ordering and spacing of the values reflect different phases of
95/// the PEB lifecycle, including erase, activation, data transfer, and
96/// reclaim operations. These values must not be renumbered.
97#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
98#[repr(u8)]
99pub enum PebStatus {
100    /// The PEB is currently being erased.
101    Erasing = 128,
102
103    /// The PEB is being reclaimed after becoming obsolete.
104    Reclaiming = 192,
105
106    /// The PEB is active and contains valid data.
107    Actived = 224,
108
109    /// The PEB has finished transferring its data to another block.
110    Transfered = 240,
111
112    /// The PEB is in the process of transferring its data.
113    Transfering = 248,
114
115    /// The PEB is in the process of becoming active.
116    Activing = 252,
117
118    /// The PEB is empty and contains no valid data.
119    Empty = 254,
120
121    /// the block status is undefined, it maybe has erased or not erased completely
122    Virgin = 255,
123
124    /// any other state
125    Unknown = 0,
126}
127
128impl TryFrom<u8> for PebStatus {
129    type Error = Error;
130
131    /// Attempts to convert a raw `u8` value into a [`PebStatus`].
132    ///
133    /// # Parameters
134    /// - `value`: The raw status byte read from flash metadata.
135    ///
136    /// # Returns
137    /// - `Ok(PebStatus)` if the value matches a known PEB status.
138    fn try_from(value: u8) -> Result<Self, Self::Error> {
139        match value {
140            128 => Ok(PebStatus::Erasing),
141            192 => Ok(PebStatus::Reclaiming),
142            224 => Ok(PebStatus::Actived),
143            240 => Ok(PebStatus::Transfered),
144            248 => Ok(PebStatus::Transfering),
145            252 => Ok(PebStatus::Activing),
146            254 => Ok(PebStatus::Empty),
147            255 => Ok(PebStatus::Virgin),
148            _ => Ok(PebStatus::Unknown),
149        }
150    }
151}
152
153/// Data item header.
154///
155/// This struct represents the on-flash header for an NVDM data item.
156/// It contains metadata describing the state, identity, size, and
157/// storage layout of the data item.
158///
159/// The layout and field meanings map directly to `struct data_item_header_t`
160/// in the original C implementation and are used by the NVDM storage
161/// and recovery logic.
162#[derive(Debug)]
163pub struct DataItemHeader {
164    /// Status of the data item.
165    ///
166    /// This field encodes the lifecycle state of the data item, such as
167    /// whether it is valid, being written, deleted, or empty.
168    pub status: DataItemStatus,
169
170    /// Physical block number where the data item is stored.
171    ///
172    /// This value typically identifies the PEB or page index associated
173    /// with the data item.
174    pub pnum: u8,
175
176    /// Reserved field.
177    ///
178    /// This field is reserved for future use and should be ignored.
179    /// It is preserved to maintain binary compatibility with the
180    /// on-flash data layout.
181    pub reserved: u16,
182
183    /// Offset to the data item value.
184    ///
185    /// This field specifies the byte offset from the start of the data
186    /// item header to the actual value data.
187    pub offset: u16,
188
189    /// Size of the group name, in bytes.
190    ///
191    /// This value indicates the length of the group name associated
192    /// with the data item.
193    pub group_name_size: u8,
194
195    /// Size of the data item name, in bytes.
196    ///
197    /// This value indicates the length of the data item name associated
198    /// with the data item.
199    pub data_item_name_size: u8,
200
201    /// Size of the data item value, in bytes.
202    ///
203    /// This field specifies the length of the stored value data.
204    pub value_size: u16,
205
206    /// Index of the data item.
207    ///
208    /// This field is typically used to distinguish between multiple
209    /// instances of data items with the same name.
210    pub index: u8,
211
212    /// Type of the data item value.
213    ///
214    /// This field specifies how the value data should be interpreted,
215    /// such as raw binary data or string data.
216    pub item_type: NvdmDataItemType,
217
218    /// Sequence number of the data item.
219    ///
220    /// This monotonically increasing value is used to determine the
221    /// most recent version of a data item during recovery or scanning.
222    pub sequence_number: u32,
223
224    /// Hash of the data item name.
225    ///
226    /// This field stores a hash value derived from the group name and
227    /// data item name, enabling faster lookups during NVDM operations.
228    pub hash_name: u32,
229}
230
231impl Default for DataItemHeader {
232    /// Creates a default `DataItemHeader` instance with safe invalid values.
233    ///
234    /// Default semantics:
235    /// - `status`: `DataItemStatus::Empty` (unused slot)
236    /// - `pnum`: `0xFF` (invalid physical block number)
237    /// - `reserved`: `0`
238    /// - `offset`: `0`
239    /// - `group_name_size`: `0`
240    /// - `data_item_name_size`: `0`
241    /// - `value_size`: `0`
242    /// - `index`: `0`
243    /// - `item_type`: `NvdmDataItemType::RawData`
244    /// - `sequence_number`: `0`
245    /// - `hash_name`: `0`
246    fn default() -> Self {
247        DataItemHeader {
248            status: DataItemStatus::Empty,
249            pnum: 0xFF,
250            reserved: 0,
251            offset: 0,
252            group_name_size: 0,
253            data_item_name_size: 0,
254            value_size: 0,
255            index: 0,
256            item_type: NvdmDataItemType::RawData,
257            sequence_number: 0,
258            hash_name: 0,
259        }
260    }
261}
262
263impl BinarySize for DataItemHeader {
264    /// Returns the binary size of the `DataItemHeader` in bytes.
265    ///
266    /// Layout size:
267    /// - Fixed-size, packed
268    /// - Total: 20 bytes
269    #[inline]
270    fn binary_size() -> usize {
271        20
272    }
273}
274
275impl FromStream for DataItemHeader {
276    /// Reads a `DataItemHeader` from a binary stream.
277    ///
278    /// # Parameters
279    /// - `reader`: A mutable reference to a reader implementing `Read`.
280    ///
281    /// # Returns
282    /// - `Ok(())` if the header was successfully parsed.
283    /// - `Err(Error)` if an I/O or conversion error occurs.
284    fn read_from<R>(&mut self, reader: &mut R) -> Result<(), Error>
285    where
286        R: std::io::Read,
287    {
288        self.status = DataItemStatus::try_from(reader.read_u8()?)?;
289        self.pnum = reader.read_u8()?;
290        self.reserved = reader.read_u16::<LittleEndian>()?;
291        self.offset = reader.read_u16::<LittleEndian>()?;
292        self.group_name_size = reader.read_u8()?;
293        self.data_item_name_size = reader.read_u8()?;
294        self.value_size = reader.read_u16::<LittleEndian>()?;
295        self.index = reader.read_u8()?;
296        self.item_type = NvdmDataItemType::try_from(reader.read_u8()?)?;
297        self.sequence_number = reader.read_u32::<LittleEndian>()?;
298        self.hash_name = reader.read_u32::<LittleEndian>()?;
299
300        Ok(())
301    }
302}
303
304impl ToStream for DataItemHeader {
305    /// Serializes the `DataItemHeader` to a binary stream.
306    ///
307    /// # Parameters
308    /// - `writer`: A mutable reference to a writer implementing `Write`.
309    ///
310    /// # Returns
311    /// - `Ok(())` if serialization succeeds.
312    /// - `Err(Error)` if an I/O error occurs.
313    fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
314    where
315        W: std::io::Write,
316    {
317        writer.write_u8(self.status as u8)?;
318        writer.write_u8(self.pnum)?;
319        writer.write_u16::<LittleEndian>(self.reserved)?;
320        writer.write_u16::<LittleEndian>(self.offset)?;
321        writer.write_u8(self.group_name_size)?;
322        writer.write_u8(self.data_item_name_size)?;
323        writer.write_u16::<LittleEndian>(self.value_size)?;
324        writer.write_u8(self.index)?;
325        writer.write_u8(self.item_type as u8)?;
326        writer.write_u32::<LittleEndian>(self.sequence_number)?;
327        writer.write_u32::<LittleEndian>(self.hash_name)?;
328
329        Ok(())
330    }
331}
332/// Represents a data item with header information, names, a value payload,
333/// and a checksum for validation.
334#[derive(Debug)]
335pub struct DataItem {
336    header: DataItemHeader,
337    checksum: u16,
338    group_name: String,
339    item_name: String,
340    value: Vec<u8>,
341}
342
343impl Default for DataItem {
344    /// Creates a new `DataItem` with default values.
345    ///
346    /// # Returns
347    /// - A `DataItem` instance where all fields are initialized to their
348    ///   default states, including an empty group and item names,
349    ///   zero checksum, and an empty value vector.
350    fn default() -> Self {
351        DataItem {
352            header: DataItemHeader::default(),
353            checksum: 0,
354            group_name: String::new(),
355            item_name: String::new(),
356            value: Vec::new(),
357        }
358    }
359}
360
361impl DataItem {
362    /// Calculates the total binary size of the `DataItem`.
363    ///
364    /// This includes the fixed header size, sizes of the group name,
365    /// item name, value, and the checksum (2 bytes).
366    ///
367    /// # Returns
368    /// - The full size of the item as a `u32` value.
369    pub fn item_size(&self) -> u32 {
370        return DataItemHeader::binary_size() as u32
371            + self.header.value_size as u32
372            + self.header.data_item_name_size as u32
373            + self.header.group_name_size as u32
374            + 0x02; // checksum size
375    }
376
377    /// Provides a reference to the `DataItemHeader` of this item.
378    ///
379    /// # Returns
380    /// - A reference to the contained `DataItemHeader`.
381    pub fn item_header(&self) -> &DataItemHeader {
382        &self.header
383    }
384
385    /// Returns the name of the item as a string slice.
386    ///
387    /// # Returns
388    /// - A string slice representing the item name.
389    pub fn name(&self) -> &str {
390        return &self.item_name;
391    }
392
393    /// Returns the group name of the item as a string slice.
394    ///
395    /// # Returns
396    /// - A string slice representing the group name.
397    pub fn group(&self) -> &str {
398        return &self.group_name;
399    }
400
401    /// Returns a byte slice containing the item's value data.
402    ///
403    /// # Returns
404    /// - A slice of bytes representing the value of the data item.
405    pub fn data(&self) -> &[u8] {
406        return &self.value;
407    }
408}
409
410impl FromStream for DataItem {
411    /// Reads a `DataItem` from a binary stream.
412    ///
413    /// This method reads the header first, then if the status is not
414    /// `Empty` or `Unknown`, it reads the group name, item name,
415    /// value bytes, and checksum from the stream.
416    ///
417    /// The group and item names are read as UTF-8 strings and exclude
418    /// the trailing null byte.
419    ///
420    /// # Parameters
421    /// - `reader`: A mutable reference to a reader implementing `Read` and `Seek`.
422    ///
423    /// # Returns
424    /// - `Ok(())` if the item was read successfully.
425    /// - `Err(Error)` if any I/O error occurs or UTF-8 conversion fails.
426    fn read_from<R>(&mut self, reader: &mut R) -> Result<(), Error>
427    where
428        R: std::io::Read + std::io::Seek,
429    {
430        self.header.read_from(reader)?;
431        if self.header.status == DataItemStatus::Empty
432            || self.header.status == DataItemStatus::Unknown
433        {
434            return Ok(());
435        }
436
437        let mut group_name_raw = vec![0; self.header.group_name_size as usize];
438        reader.read_exact(&mut group_name_raw)?;
439        self.group_name = String::from_utf8(group_name_raw[..group_name_raw.len() - 1].to_vec())?;
440
441        let mut item_name_raw = vec![0; self.header.data_item_name_size as usize];
442        reader.read_exact(&mut item_name_raw)?;
443        self.item_name = String::from_utf8(item_name_raw[..item_name_raw.len() - 1].to_vec())?;
444
445        self.value = vec![0; self.header.value_size as usize];
446        reader.read_exact(&mut self.value)?;
447        self.checksum = reader.read_u16::<LittleEndian>()?;
448        Ok(())
449    }
450}
451
452pub const PEB_MAGIC: &[u8; 4] = b"NVDM";
453
454/// PEB header.
455///
456/// This struct represents the on-flash header of a Physical Erase Block (PEB).
457/// It stores metadata required for flash management, wear leveling, and
458/// recovery logic.
459///
460/// The layout maps directly to `struct peb_header_t` in the original C
461/// implementation and must not be altered.
462#[derive(Debug)]
463pub struct PebHeader {
464    /// Magic value identifying a valid PEB header. (NVDM)
465    ///
466    /// This field is used to validate that the flash block contains
467    /// a properly initialized PEB header.
468    pub magic: [u8; 4],
469
470    /// Erase count of the PEB.
471    ///
472    /// This value tracks how many times the block has been erased and
473    /// is typically used for wear-leveling decisions.
474    pub erase_count: u32,
475
476    /// Current status of the PEB.
477    ///
478    /// This field encodes the lifecycle state of the PEB, such as
479    /// virgin, active, transferring, or reclaiming.
480    pub status: PebStatus,
481
482    /// Reserved byte specific to PEB metadata.
483    ///
484    /// This field is reserved for future use and should be preserved
485    /// as-is to maintain binary compatibility.
486    pub peb_reserved: u8,
487
488    /// Version of the PEB header format.
489    ///
490    /// This field allows future extensions of the PEB header layout.
491    pub version: u8,
492
493    /// Reserved field.
494    ///
495    /// This field is reserved and should be written as zero.
496    pub reserved: u8,
497}
498
499impl Default for PebHeader {
500    /// Creates a default `PebHeader` instance with invalid / erased values.
501    ///
502    /// Default semantics:
503    /// - `magic`: `0xFFFF_FFFF` (invalid magic)
504    /// - `erase_count`: `0`
505    /// - `status`: `PebStatus::Virgin`
506    /// - `peb_reserved`: `0xFF`
507    /// - `version`: `0`
508    /// - `reserved`: `0`
509    fn default() -> Self {
510        PebHeader {
511            magic: *PEB_MAGIC,
512            erase_count: 0,
513            status: PebStatus::Virgin,
514            peb_reserved: 0xFF,
515            version: 0,
516            reserved: 0,
517        }
518    }
519}
520
521impl BinarySize for PebHeader {
522    /// Returns the binary size of the `PebHeader` in bytes.
523    ///
524    /// Layout:
525    /// - Fixed-size
526    /// - Total: 12 bytes
527    #[inline]
528    fn binary_size() -> usize {
529        12
530    }
531}
532
533impl FromStream for PebHeader {
534    /// Reads a `PebHeader` from a binary stream.
535    ///
536    /// # Parameters
537    /// - `reader`: A mutable reference to a reader implementing `Read`.
538    ///
539    /// # Returns
540    /// - `Ok(())` if the header was successfully parsed.
541    /// - `Err(Error)` if an I/O or conversion error occurs.
542    fn read_from<R>(&mut self, reader: &mut R) -> Result<(), Error>
543    where
544        R: std::io::Read,
545    {
546        reader.read_exact(&mut self.magic)?;
547        if self.magic != *PEB_MAGIC {
548            return Err(Error::InvalidState(format!(
549                "Invalid peb_header_t magic, expected NVDM, got {:?}",
550                self.magic,
551            )));
552        }
553
554        self.erase_count = reader.read_u32::<LittleEndian>()?;
555        self.status = PebStatus::try_from(reader.read_u8()?)?;
556        self.peb_reserved = reader.read_u8()?;
557        self.version = reader.read_u8()?;
558        self.reserved = reader.read_u8()?;
559
560        Ok(())
561    }
562}
563
564impl ToStream for PebHeader {
565    /// Serializes the `PebHeader` to a binary stream.
566    ///
567    /// # Parameters
568    /// - `writer`: A mutable reference to a writer implementing `Write`.
569    ///
570    /// # Returns
571    /// - `Ok(())` if serialization succeeds.
572    /// - `Err(Error)` if an I/O error occurs.
573    fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
574    where
575        W: std::io::Write,
576    {
577        writer.write_all(&self.magic)?;
578        writer.write_u32::<LittleEndian>(self.erase_count)?;
579        writer.write_u8(self.status as u8)?;
580        writer.write_u8(self.peb_reserved)?;
581        writer.write_u8(self.version)?;
582        writer.write_u8(self.reserved)?;
583
584        Ok(())
585    }
586}
587
588/// /* This macro defines size of PEB, normally it is size of flash block */
589pub const NVDM_PORT_PEB_SIZE: u32 = 4096;
590
591/// Represents the Non-Volatile Data Management (NVDM) system, which manages
592/// user data storage in flash memory.
593///
594/// NVDM supports data retention after power off and organizes data items
595/// into groups, enabling classification and orderly management of items.
596pub struct NVDM {
597    // config
598    peb_size: u32,
599    items: Vec<DataItem>,
600}
601
602impl Default for NVDM {
603    /// Creates a new `NVDM` instance with default configuration.
604    ///
605    /// The `peb_size` is set to the default port-specific PEB size, and
606    /// the items vector is initialized empty.
607    ///
608    /// # Returns
609    /// - An `NVDM` instance with default parameters.
610    fn default() -> Self {
611        return NVDM {
612            peb_size: NVDM_PORT_PEB_SIZE,
613            items: Vec::new(),
614        };
615    }
616}
617
618impl NVDM {
619    /// Constructs a new `NVDM` with the specified physical erase block (PEB) size.
620    ///
621    /// Other fields are initialized with default values.
622    ///
623    /// # Parameters
624    /// - `peb_size`: The size of a physical erase block in bytes.
625    ///
626    /// # Returns
627    /// - An `NVDM` instance configured with the given PEB size.
628    pub fn from_peb_size(peb_size: u32) -> Self {
629        NVDM {
630            peb_size,
631            ..Default::default()
632        }
633    }
634
635    /// Computes the flash address based on PEB number and offset within the PEB.
636    ///
637    /// # Parameters
638    /// - `pnum`: The physical erase block number.
639    /// - `offset`: The offset within the PEB.
640    ///
641    /// # Returns
642    /// - The calculated flash address as a `u32`.
643    #[inline]
644    pub fn nvdm_port_get_peb_address(&self, pnum: u32, offset: u32) -> u32 {
645        pnum * self.peb_size + offset
646    }
647
648    /// Retrieves a reference to a data item matching the specified group,
649    /// name, and status.
650    ///
651    /// # Parameters
652    /// - `group`: The group name to match.
653    /// - `name`: The item name to match.
654    /// - `status`: The status of the data item to match.
655    ///
656    /// # Returns
657    /// - `Some(&DataItem)` if an item matching all criteria exists.
658    /// - `None` if no matching item is found.
659    pub fn get_item(&self, group: &str, name: &str, status: DataItemStatus) -> Option<&DataItem> {
660        self.items.iter().find(|&item| {
661            item.group() == group && name == item.name() && item.item_header().status == status
662        })
663    }
664
665    /// Retrieves all data items within a specified group and having a
666    /// specified status.
667    ///
668    /// # Parameters
669    /// - `group`: The group name to filter by.
670    /// - `status`: The status of the data items to include.
671    ///
672    /// # Returns
673    /// - A vector of references to matching `DataItem`s.
674    pub fn get_items_by_group(&self, group: &str, status: DataItemStatus) -> Vec<&DataItem> {
675        self.items
676            .iter()
677            .filter(|&item| item.group() == group && item.item_header().status == status)
678            .collect()
679    }
680
681    /// Returns a list of unique group names present in the stored data items.
682    ///
683    /// # Returns
684    /// - A vector of string slices representing distinct group names.
685    pub fn get_groups(&self) -> Vec<&str> {
686        self.items
687            .iter()
688            .map(|item| item.group())
689            .unique()
690            .collect()
691    }
692}
693
694impl FromStream for NVDM {
695    /// Reads the NVDM data from a binary stream, parsing all physical
696    /// erase blocks (PEBs) and extracting valid data items.
697    ///
698    /// Iterates through each PEB, checking if it is active, then reads
699    /// data items within, adding valid items to the internal collection.
700    ///
701    /// # Parameters
702    /// - `reader`: A mutable reference to a reader implementing `Read` and `Seek`.
703    ///
704    /// # Returns
705    /// - `Ok(())` if all data was successfully read.
706    /// - `Err(Error)` if an I/O or parsing error occurs.
707    fn read_from<R>(&mut self, reader: &mut R) -> Result<(), Error>
708    where
709        R: std::io::Read + std::io::Seek,
710    {
711        // read all PEBs and insert all (valid) items
712        let start = reader.stream_position()?;
713        let end = reader.seek(std::io::SeekFrom::End(0))?;
714        reader.seek(std::io::SeekFrom::Start(start))?;
715
716        // data size we can use
717        let size = end - start;
718        for pnum in 0..(size / self.peb_size as u64) {
719            let address = start + self.nvdm_port_get_peb_address(pnum as u32, 0) as u64;
720            reader.seek(std::io::SeekFrom::Start(address))?;
721
722            let header: PebHeader = from_stream(reader)?;
723            if header.status == PebStatus::Actived {
724                let mut offset = 0;
725                while offset < self.peb_size - 0x20 {
726                    let item: DataItem = from_stream(reader)?;
727                    match item.item_header().status {
728                        DataItemStatus::Delete
729                        | DataItemStatus::Valid
730                        | DataItemStatus::Writing => {
731                            offset += item.item_size();
732                            self.items.push(item);
733                        }
734                        _ => break,
735                    }
736                }
737            }
738        }
739        Ok(())
740    }
741}