esp_nvs/
raw.rs

1use crate::Key;
2use crate::error::Error;
3use crate::internal::{ThinPageHeader, ThinPageState, VersionOffset};
4use crate::platform::{AlignedOps, FnCrc32, Platform};
5use crate::u24::u24;
6#[cfg(feature = "debug-logs")]
7use alloc::format;
8use alloc::vec;
9use core::fmt::{Debug, Formatter};
10use core::mem::{size_of, transmute};
11use core::slice::from_raw_parts;
12#[cfg(feature = "defmt")]
13use defmt::trace;
14
15// -1 is for the leading item of type BLOB_DATA or SZ (for str)
16pub(crate) const MAX_BLOB_DATA_PER_PAGE: usize = (ENTRIES_PER_PAGE - 1) * size_of::<Item>();
17pub(crate) const MAX_BLOB_SIZE: usize =
18    MAX_BLOB_DATA_PER_PAGE * (u8::MAX as usize - VersionOffset::V1 as usize);
19pub(crate) const FLASH_SECTOR_SIZE: usize = 4096;
20pub(crate) const ENTRY_STATE_BITMAP_SIZE: usize = 32;
21pub(crate) const ENTRIES_PER_PAGE: usize = 126;
22
23// Compile-time assertion to ensure page structure size matches flash sector size
24const _: () = assert!(
25    size_of::<PageHeader>() + ENTRY_STATE_BITMAP_SIZE + ENTRIES_PER_PAGE * size_of::<Item>()
26        == FLASH_SECTOR_SIZE,
27    "Page structure size must equal flash sector size"
28);
29
30#[repr(C, packed)]
31pub(crate) struct RawPage {
32    pub(crate) header: PageHeader,
33    pub(crate) entry_state_bitmap: [u8; ENTRY_STATE_BITMAP_SIZE],
34    pub(crate) items: Items,
35}
36
37#[repr(C, packed)]
38#[derive(Copy, Clone)]
39pub(crate) union Items {
40    pub(crate) raw: [u8; ENTRIES_PER_PAGE * size_of::<Item>()],
41    pub(crate) entries: [Item; ENTRIES_PER_PAGE],
42}
43
44#[cfg(feature = "debug-logs")]
45impl Debug for Items {
46    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
47        unsafe { self.entries.fmt(f) }
48    }
49}
50
51#[derive(strum::FromRepr, Debug, PartialEq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53#[repr(u8)]
54pub(crate) enum EntryMapState {
55    Empty = 0b11,
56    Written = 0b10,
57    Erased = 0b00,
58    Illegal = 0b01,
59}
60
61const PSB_INIT: u32 = 0x1;
62const PSB_FULL: u32 = 0x2;
63const PSB_FREEING: u32 = 0x4;
64const PSB_CORRUPT: u32 = 0x8;
65
66#[derive(strum::FromRepr, strum::Display, Debug, PartialEq, Copy, Clone)]
67#[repr(u32)]
68pub(crate) enum PageState {
69    // All bits set, default state after flash erase. Page has not been initialized yet.
70    Uninitialized = u32::MAX,
71
72    // Page is initialized, and will accept writes.
73    Active = PageState::Uninitialized as u32 & !PSB_INIT,
74
75    // Page is marked as full and will not accept new writes.
76    Full = PageState::Active as u32 & !PSB_FULL,
77
78    // Data is being moved from this page to a new one.
79    Freeing = PageState::Full as u32 & !PSB_FREEING,
80
81    // Page was found to be in a corrupt and unrecoverable state.
82    // Instead of being erased immediately, it will be kept for diagnostics and data recovery.
83    // It will be erased once we run out out free pages.
84    Corrupt = PageState::Freeing as u32 & !PSB_CORRUPT,
85
86    // Page object wasn't loaded from flash memory
87    Invalid = 0,
88}
89
90impl From<PageState> for ThinPageState {
91    fn from(val: PageState) -> Self {
92        match val {
93            PageState::Uninitialized => ThinPageState::Uninitialized,
94            PageState::Active => ThinPageState::Active,
95            PageState::Full => ThinPageState::Full,
96            PageState::Freeing => ThinPageState::Freeing,
97            PageState::Corrupt => ThinPageState::Corrupt,
98            PageState::Invalid => ThinPageState::Invalid,
99        }
100    }
101}
102
103const PAGE_STATE_UNINITIALIZED: u32 = PageState::Uninitialized as u32;
104const PAGE_STATE_ACTIVE: u32 = PageState::Active as u32;
105const PAGE_STATE_FULL: u32 = PageState::Full as u32;
106const PAGE_STATE_FREEING: u32 = PageState::Freeing as u32;
107const PAGE_STATE_CORRUPT: u32 = PageState::Corrupt as u32;
108const PAGE_STATE_INVALID: u32 = PageState::Invalid as u32;
109
110impl From<u32> for PageState {
111    fn from(val: u32) -> Self {
112        match val {
113            PAGE_STATE_UNINITIALIZED => PageState::Uninitialized,
114            PAGE_STATE_ACTIVE => PageState::Active,
115            PAGE_STATE_FULL => PageState::Full,
116            PAGE_STATE_FREEING => PageState::Freeing,
117            PAGE_STATE_CORRUPT => PageState::Corrupt,
118            PAGE_STATE_INVALID => PageState::Invalid,
119            _ => PageState::Corrupt,
120        }
121    }
122}
123
124#[derive(strum::FromRepr, strum::Display, Debug, Eq, PartialEq, Copy, Clone)]
125#[repr(u8)]
126#[cfg_attr(feature = "defmt", derive(defmt::Format))]
127pub enum ItemType {
128    U8 = 0x01,
129    I8 = 0x11,
130    U16 = 0x02,
131    I16 = 0x12,
132    U32 = 0x04,
133    I32 = 0x14,
134    U64 = 0x08,
135    I64 = 0x18,
136    Sized = 0x21,
137    Blob = 0x41,
138    BlobData = 0x42,
139    BlobIndex = 0x48,
140    Any = 0xff,
141}
142
143impl ItemType {
144    pub(crate) fn get_primitive_bytes_width(&self) -> Result<usize, Error> {
145        match self {
146            ItemType::U8 | ItemType::I8 => Ok(1),
147            ItemType::U16 | ItemType::I16 => Ok(2),
148            ItemType::U32 | ItemType::I32 => Ok(4),
149            ItemType::U64 | ItemType::I64 => Ok(8),
150            _ => Err(Error::ItemTypeMismatch(*self)),
151        }
152    }
153}
154
155#[repr(C, packed)]
156#[derive(Copy, Clone)]
157pub(crate) struct PageHeader {
158    pub(crate) state: u32,
159    pub(crate) sequence: u32,
160    pub(crate) version: u8,
161    pub(crate) _unused: [u8; 19],
162    pub(crate) crc: u32,
163}
164
165pub(crate) union PageHeaderRaw {
166    pub(crate) page_header: PageHeader,
167    pub(crate) raw: [u8; size_of::<PageHeader>()],
168}
169
170impl From<PageHeader> for ThinPageHeader {
171    fn from(val: PageHeader) -> Self {
172        ThinPageHeader {
173            state: PageState::from(val.state).into(),
174            sequence: val.sequence,
175            version: val.version,
176            crc: val.crc,
177        }
178    }
179}
180
181impl Debug for PageHeader {
182    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
183        let state = self.state;
184        let sequence = self.sequence;
185        let version = self.version;
186        let crc = self.crc;
187        match state {
188            PAGE_STATE_FULL | PAGE_STATE_ACTIVE => {
189                f.write_fmt(format_args!("PageHeader {{ state: {state:?>13}, sequence: {sequence:>4}, version: 0x{version:0>2x}, crc: 0x{crc:0>4x}}}"))
190            }
191            _ => f.write_fmt(format_args!("PageHeader {{ state: {state:?>13} }}"))
192        }
193    }
194}
195
196impl PageHeader {
197    pub(crate) fn calculate_crc32(&self, crc32: FnCrc32) -> u32 {
198        let buf: [u8; 32] = unsafe { transmute(*self) };
199        let interesting_parts = &buf[4..28];
200        crc32(u32::MAX, interesting_parts)
201    }
202}
203
204#[repr(C, packed)]
205#[derive(Copy, Clone, PartialEq)]
206pub(crate) struct Item {
207    pub(crate) namespace_index: u8,
208    pub(crate) type_: ItemType, // name stolen from the Linux kernel
209    pub(crate) span: u8,
210    pub(crate) chunk_index: u8,
211    pub(crate) crc: u32,
212    pub(crate) key: Key,
213    pub(crate) data: ItemData,
214}
215pub(crate) union RawItem {
216    pub(crate) item: Item,
217    pub(crate) raw: [u8; size_of::<Item>()],
218}
219
220impl Item {
221    #[cfg(feature = "debug-logs")]
222    fn get_primitive(&self) -> Result<u64, Error> {
223        let width = match self.type_ {
224            ItemType::I8 | ItemType::I16 | ItemType::I32 | ItemType::I64 => {
225                (self.type_ as u8) - 0x10
226            }
227            ItemType::U8 | ItemType::U16 | ItemType::U32 | ItemType::U64 => self.type_ as u8,
228            _ => return Err(Error::ItemTypeMismatch(self.type_)),
229        };
230
231        let mut mask = 0u64;
232        for i in 0..width {
233            mask |= u64::from(u8::MAX) << (i * 8)
234        }
235        Ok(unsafe { self.data.primitive & mask })
236    }
237}
238
239#[repr(C, packed)]
240#[derive(Copy, Clone)]
241pub(crate) union ItemData {
242    pub(crate) raw: [u8; 8],
243    pub(crate) primitive: u64,
244    pub(crate) sized: ItemDataSized,
245    pub(crate) blob_index: ItemDataBlobIndex,
246}
247
248impl PartialEq for ItemData {
249    fn eq(&self, other: &Self) -> bool {
250        unsafe { self.raw == other.raw }
251    }
252}
253
254#[repr(C, packed)]
255#[derive(Copy, Clone)]
256pub(crate) struct ItemDataSized {
257    pub(crate) size: u16,
258    _reserved: u16,
259    pub(crate) crc: u32,
260}
261
262impl ItemDataSized {
263    pub(crate) fn new(size: u16, crc: u32) -> Self {
264        Self {
265            size,
266            _reserved: u16::MAX,
267            crc,
268        }
269    }
270}
271
272#[cfg(feature = "debug-logs")]
273impl Debug for ItemDataSized {
274    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
275        let size = self.size;
276        let crc = self.crc;
277        f.write_fmt(format_args!(
278            "ItemDataSized {{size: {size}, crc: {crc:0>8x}}}"
279        ))
280    }
281}
282
283#[repr(C, packed)]
284#[derive(Copy, Clone)]
285pub(crate) struct ItemDataBlobIndex {
286    pub(crate) size: u32,
287    pub(crate) chunk_count: u8,
288    pub(crate) chunk_start: u8,
289}
290
291#[cfg(feature = "debug-logs")]
292impl Debug for ItemDataBlobIndex {
293    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
294        let size = self.size;
295        let chunk_count = self.chunk_count;
296        let chunk_start = self.chunk_start;
297        f.write_fmt(format_args!("ItemDataBlobIndex {{size: {size}, chunk_count: {chunk_count}, chunk_start: {chunk_start}}}"))
298    }
299}
300
301impl Item {
302    pub(crate) fn calculate_hash(&self, crc32: FnCrc32) -> u24 {
303        Self::calculate_hash_ref(crc32, self.namespace_index, &self.key, self.chunk_index)
304    }
305
306    /// `calculate_hash_ref` follows the details of the C++ implementation and accepts more collisions in
307    /// favor of memory efficiency
308    pub(crate) fn calculate_hash_ref(
309        crc32: FnCrc32,
310        namespace_index: u8,
311        key: &Key,
312        chunk_index: u8,
313    ) -> u24 {
314        let mut result = u32::MAX;
315        result = crc32(result, &[namespace_index]);
316        result = crc32(result, unsafe {
317            from_raw_parts(key.0.as_ptr(), key.0.len())
318        });
319        result = crc32(result, &[chunk_index]);
320        u24::from_u32(result & 0xFFFFFF)
321    }
322
323    pub(crate) fn calculate_crc32(&self, crc32: FnCrc32) -> u32 {
324        let buf: [u8; 32] = unsafe { transmute(*self) };
325        let mut result = u32::MAX;
326        result = crc32(result, &buf[0..4]);
327        result = crc32(result, self.key.0.as_ref());
328        result = unsafe { crc32(result, &self.data.raw) };
329        result
330    }
331}
332
333#[cfg(feature = "debug-logs")]
334impl Debug for Item {
335    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
336        let ns_idx = self.namespace_index;
337        let type_ = self.type_;
338        let span = self.span;
339        let chunk_index = self.chunk_index;
340        let crc = self.crc;
341        let key = slice_with_nullbytes_to_str(&self.key.0);
342        let value = match type_ {
343            ItemType::Sized | ItemType::BlobData => unsafe { format!("{:?}", self.data.sized) },
344            ItemType::Blob | ItemType::BlobIndex => unsafe {
345                format!("{:?}", self.data.blob_index)
346            },
347            ItemType::I8
348            | ItemType::I16
349            | ItemType::I32
350            | ItemType::I64
351            | ItemType::U8
352            | ItemType::U16
353            | ItemType::U32
354            | ItemType::U64 => format!("{}", self.get_primitive().unwrap()),
355            ItemType::Any => unsafe { format!("{:?}", self.data.raw) },
356        };
357        f.write_fmt(format_args!("Item {{ns_idx: {ns_idx}, type: {type_:<9?}, span: {span}, chunk_idx: {chunk_index:>3}, crc: {crc:0>8x}, key: '{key}', value: {value}}}"))
358    }
359}
360
361/// We know that keys and namespace names are saved in 16 byte arrays. Because they are originally C strings
362/// they are followed by a null terminator in case they are shorter than 16 byte. We have to slice
363/// before the null terminator if we want to transmute them to a str.
364#[cfg(feature = "debug-logs")]
365pub(crate) fn slice_with_nullbytes_to_str(raw: &[u8]) -> &str {
366    let sliced = match raw.iter().position(|&e| e == 0x00) {
367        None => raw,
368        Some(idx) => &raw[..idx],
369    };
370    unsafe { core::str::from_utf8_unchecked(sliced) }
371}
372
373#[inline(always)]
374pub(crate) fn write_aligned<T: Platform>(
375    hal: &mut T,
376    offset: u32,
377    bytes: &[u8],
378) -> Result<(), T::Error> {
379    #[cfg(feature = "defmt")]
380    trace!("write_aligned @{:#08x}: [{}]", offset, bytes.len());
381
382    if bytes.len().is_multiple_of(T::WRITE_SIZE) {
383        hal.write(offset, bytes)
384    } else {
385        let pivot = T::align_write_floor(bytes.len());
386        let header = &bytes[..pivot];
387        let trailer = &bytes[pivot..];
388        hal.write(offset, header)?;
389
390        // no need to write the trailer if remaining data is all ones - this the default state of the flash
391        if bytes[pivot..].iter().any(|&e| e != 0xFF) {
392            let mut buf = vec![0xFFu8; T::WRITE_SIZE];
393            buf[..trailer.len()].copy_from_slice(trailer);
394            hal.write(offset + (pivot as u32), &buf)?
395        }
396
397        Ok(())
398    }
399}