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
15pub(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
23const _: () = 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 Uninitialized = u32::MAX,
71
72 Active = PageState::Uninitialized as u32 & !PSB_INIT,
74
75 Full = PageState::Active as u32 & !PSB_FULL,
77
78 Freeing = PageState::Full as u32 & !PSB_FREEING,
80
81 Corrupt = PageState::Freeing as u32 & !PSB_CORRUPT,
85
86 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, 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 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#[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 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}