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