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}