1use embedded_storage::Region;
11
12pub const PARTITION_TABLE_MAX_LEN: usize = 0xC00;
14
15const PARTITION_TABLE_OFFSET: u32 =
16 esp_config::esp_config_int!(u32, "ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET");
17
18const RAW_ENTRY_LEN: usize = 32;
19const ENTRY_MAGIC: u16 = 0x50aa;
20const MD5_MAGIC: u16 = 0xebeb;
21
22pub struct PartitionEntry<'a> {
24 pub(crate) binary: &'a [u8; RAW_ENTRY_LEN],
25}
26
27impl<'a> PartitionEntry<'a> {
28 fn new(binary: &'a [u8; RAW_ENTRY_LEN]) -> Self {
29 Self { binary }
30 }
31
32 pub fn magic(&self) -> u16 {
34 u16::from_le_bytes(unwrap!(self.binary[..2].try_into()))
35 }
36
37 pub fn raw_type(&self) -> u8 {
39 self.binary[2]
40 }
41
42 pub fn raw_subtype(&self) -> u8 {
44 self.binary[3]
45 }
46
47 pub fn offset(&self) -> u32 {
49 u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into()))
50 }
51
52 pub fn len(&self) -> u32 {
54 u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into()))
55 }
56
57 pub fn is_empty(&self) -> bool {
59 self.len() == 0
60 }
61
62 pub fn label(&self) -> &'a [u8] {
64 &self.binary[12..][..16]
65 }
66
67 pub fn label_as_str(&self) -> &'a str {
69 let array = self.label();
70 let len = array
71 .iter()
72 .position(|b| *b == 0 || *b == 0xff)
73 .unwrap_or(array.len());
74 unsafe {
75 core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len))
76 }
77 }
78
79 pub fn flags(&self) -> u32 {
82 u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into()))
83 }
84
85 pub fn is_read_only(&self) -> bool {
87 self.flags() & 0b01 != 0
88 }
89
90 pub fn is_encrypted(&self) -> bool {
92 self.flags() & 0b10 != 0
93 }
94
95 pub fn partition_type(&self) -> PartitionType {
97 match self.raw_type() {
98 0 => PartitionType::App(unwrap!(self.raw_subtype().try_into())),
99 1 => PartitionType::Data(unwrap!(self.raw_subtype().try_into())),
100 2 => PartitionType::Bootloader(unwrap!(self.raw_subtype().try_into())),
101 3 => PartitionType::PartitionTable(unwrap!(self.raw_subtype().try_into())),
102 _ => unreachable!(),
103 }
104 }
105
106 pub fn as_embedded_storage<F>(&'a self, flash: &'a mut F) -> FlashRegion<'a, F>
110 where
111 F: embedded_storage::ReadStorage,
112 {
113 FlashRegion { raw: self, flash }
114 }
115}
116
117impl core::fmt::Debug for PartitionEntry<'_> {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 f.debug_struct("PartitionEntry")
120 .field("magic", &self.magic())
121 .field("raw_type", &self.raw_type())
122 .field("raw_subtype", &self.raw_subtype())
123 .field("offset", &self.offset())
124 .field("len", &self.len())
125 .field("label", &self.label_as_str())
126 .field("flags", &self.flags())
127 .field("is_read_only", &self.is_read_only())
128 .field("is_encrypted", &self.is_encrypted())
129 .finish()
130 }
131}
132
133#[cfg(feature = "defmt")]
134impl defmt::Format for PartitionEntry<'_> {
135 fn format(&self, fmt: defmt::Formatter) {
136 defmt::write!(
137 fmt,
138 "PartitionEntry (\
139 magic = {}, \
140 raw_type = {}, \
141 raw_subtype = {}, \
142 offset = {}, \
143 len = {}, \
144 label = {}, \
145 flags = {}, \
146 is_read_only = {}, \
147 is_encrypted = {}\
148 )",
149 self.magic(),
150 self.raw_type(),
151 self.raw_subtype(),
152 self.offset(),
153 self.len(),
154 self.label_as_str(),
155 self.flags(),
156 self.is_read_only(),
157 self.is_encrypted()
158 )
159 }
160}
161
162#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::Display)]
164#[cfg_attr(feature = "defmt", derive(defmt::Format))]
165pub enum Error {
166 Invalid,
168 OutOfBounds,
170 StorageError,
172 WriteProtected,
174 InvalidPartition {
176 expected_size: usize,
177 expected_type: PartitionType,
178 },
179 InvalidState,
181}
182
183impl core::error::Error for Error {}
184
185pub struct PartitionTable<'a> {
187 binary: &'a [[u8; RAW_ENTRY_LEN]],
188 entries: usize,
189}
190
191impl<'a> PartitionTable<'a> {
192 fn new(binary: &'a [u8]) -> Result<Self, Error> {
193 if binary.len() > PARTITION_TABLE_MAX_LEN {
194 return Err(Error::Invalid);
195 }
196
197 if binary.len() % RAW_ENTRY_LEN != 0 {
198 return Err(Error::Invalid);
199 }
200
201 if binary.is_empty() {
202 return Ok(Self {
203 binary: &[],
204 entries: 0,
205 });
206 }
207
208 let binary = unsafe {
210 core::slice::from_raw_parts(
211 binary.as_ptr() as *const [u8; RAW_ENTRY_LEN],
212 binary.len() / RAW_ENTRY_LEN,
213 )
214 };
215
216 let mut raw_table = Self {
217 binary,
218 entries: binary.len(),
219 };
220
221 #[cfg(feature = "validation")]
222 {
223 let (hash, index) = {
224 let mut i = 0;
225 loop {
226 if let Ok(entry) = raw_table.get_partition(i) {
227 if entry.magic() == MD5_MAGIC {
228 break (&entry.binary[16..][..16], i);
229 }
230
231 i += 1;
232 if i >= raw_table.entries {
233 return Err(Error::Invalid);
234 }
235 }
236 }
237 };
238
239 let mut hasher = crate::crypto::Md5::new();
240
241 for i in 0..index {
242 hasher.update(&raw_table.binary[i]);
243 }
244 let calculated_hash = hasher.finalize();
245
246 if calculated_hash != hash {
247 return Err(Error::Invalid);
248 }
249 }
250
251 let entries = {
252 let mut i = 0;
253 loop {
254 if let Ok(entry) = raw_table.get_partition(i) {
255 if entry.magic() != ENTRY_MAGIC {
256 break;
257 }
258
259 i += 1;
260
261 if i == raw_table.entries {
262 break;
263 }
264 } else {
265 return Err(Error::Invalid);
266 }
267 }
268 i
269 };
270
271 raw_table.entries = entries;
272
273 Ok(raw_table)
274 }
275
276 pub fn len(&self) -> usize {
278 self.entries
279 }
280
281 pub fn is_empty(&self) -> bool {
283 self.entries == 0
284 }
285
286 pub fn get_partition(&self, index: usize) -> Result<PartitionEntry<'a>, Error> {
288 if index >= self.entries {
289 return Err(Error::OutOfBounds);
290 }
291 Ok(PartitionEntry::new(&self.binary[index]))
292 }
293
294 pub fn find_partition(&self, pt: PartitionType) -> Result<Option<PartitionEntry<'a>>, Error> {
296 for i in 0..self.entries {
297 let entry = self.get_partition(i)?;
298 if entry.partition_type() == pt {
299 return Ok(Some(entry));
300 }
301 }
302 Ok(None)
303 }
304}
305
306#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
308#[cfg_attr(feature = "defmt", derive(defmt::Format))]
309pub enum PartitionType {
310 App(AppPartitionSubType),
312 Data(DataPartitionSubType),
314 Bootloader(BootloaderPartitionSubType),
316 PartitionTable(PartitionTablePartitionSubType),
318}
319
320#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
322#[cfg_attr(feature = "defmt", derive(defmt::Format))]
323#[repr(u8)]
324pub enum RawPartitionType {
325 App = 0,
327 Data,
329 Bootloader,
331 PartitionTable,
333}
334
335#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
337#[cfg_attr(feature = "defmt", derive(defmt::Format))]
338#[repr(u8)]
339pub enum AppPartitionSubType {
340 Factory = 0,
342 Ota0 = 0x10,
344 Ota1,
346 Ota2,
348 Ota3,
350 Ota4,
352 Ota5,
354 Ota6,
356 Ota7,
358 Ota8,
360 Ota9,
362 Ota10,
364 Ota11,
366 Ota12,
368 Ota13,
370 Ota14,
372 Ota15,
374 Test,
376}
377
378impl TryFrom<u8> for AppPartitionSubType {
379 type Error = Error;
380
381 fn try_from(value: u8) -> Result<Self, Self::Error> {
382 AppPartitionSubType::from_repr(value).ok_or(Error::Invalid)
383 }
384}
385
386#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
388#[cfg_attr(feature = "defmt", derive(defmt::Format))]
389#[repr(u8)]
390pub enum DataPartitionSubType {
391 Ota = 0,
395 Phy,
398 Nvs,
400 Coredump,
402 NvsKeys,
404 EfuseEm,
406 Undefined,
409 Fat = 0x81,
411 Spiffs = 0x82,
413 LittleFs = 0x83,
415}
416
417impl TryFrom<u8> for DataPartitionSubType {
418 type Error = Error;
419
420 fn try_from(value: u8) -> Result<Self, Self::Error> {
421 DataPartitionSubType::from_repr(value).ok_or(Error::Invalid)
422 }
423}
424
425#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
427#[cfg_attr(feature = "defmt", derive(defmt::Format))]
428#[repr(u8)]
429pub enum BootloaderPartitionSubType {
430 Primary = 0,
432 Ota = 1,
435}
436
437impl TryFrom<u8> for BootloaderPartitionSubType {
438 type Error = Error;
439
440 fn try_from(value: u8) -> Result<Self, Self::Error> {
441 BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid)
442 }
443}
444
445#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
447#[cfg_attr(feature = "defmt", derive(defmt::Format))]
448#[repr(u8)]
449pub enum PartitionTablePartitionSubType {
450 Primary = 0,
452 Ota = 1,
455}
456
457impl TryFrom<u8> for PartitionTablePartitionSubType {
458 type Error = Error;
459
460 fn try_from(value: u8) -> Result<Self, Self::Error> {
461 PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid)
462 }
463}
464
465pub fn read_partition_table<'a>(
470 flash: &mut impl embedded_storage::Storage,
471 storage: &'a mut [u8],
472) -> Result<PartitionTable<'a>, Error> {
473 flash
474 .read(PARTITION_TABLE_OFFSET, storage)
475 .map_err(|_e| Error::StorageError)?;
476
477 PartitionTable::new(storage)
478}
479
480#[derive(Debug)]
485#[cfg_attr(feature = "defmt", derive(defmt::Format))]
486pub struct FlashRegion<'a, F> {
487 pub(crate) raw: &'a PartitionEntry<'a>,
488 pub(crate) flash: &'a mut F,
489}
490
491impl<F> embedded_storage::Region for FlashRegion<'_, F> {
492 fn contains(&self, address: u32) -> bool {
493 address >= self.raw.offset() && address < self.raw.offset() + self.raw.len()
494 }
495}
496
497impl<F> embedded_storage::ReadStorage for FlashRegion<'_, F>
498where
499 F: embedded_storage::ReadStorage,
500{
501 type Error = Error;
502
503 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
504 let address = offset + self.raw.offset();
505
506 if !self.contains(address) {
507 return Err(Error::OutOfBounds);
508 }
509
510 if !self.contains(address + bytes.len() as u32) {
511 return Err(Error::OutOfBounds);
512 }
513
514 self.flash
515 .read(address, bytes)
516 .map_err(|_e| Error::StorageError)
517 }
518
519 fn capacity(&self) -> usize {
520 self.raw.len() as _
521 }
522}
523
524impl<F> embedded_storage::Storage for FlashRegion<'_, F>
525where
526 F: embedded_storage::Storage,
527{
528 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
529 let address = offset + self.raw.offset();
530
531 if self.raw.is_read_only() {
532 return Err(Error::WriteProtected);
533 }
534
535 if !self.contains(address) {
536 return Err(Error::OutOfBounds);
537 }
538
539 if !self.contains(address + bytes.len() as u32) {
540 return Err(Error::OutOfBounds);
541 }
542
543 self.flash
544 .write(address, bytes)
545 .map_err(|_e| Error::StorageError)
546 }
547}
548
549impl embedded_storage::nor_flash::NorFlashError for Error {
550 fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind {
551 match self {
552 Error::OutOfBounds => embedded_storage::nor_flash::NorFlashErrorKind::OutOfBounds,
553 _ => embedded_storage::nor_flash::NorFlashErrorKind::Other,
554 }
555 }
556}
557
558impl<F> embedded_storage::nor_flash::ErrorType for FlashRegion<'_, F> {
559 type Error = Error;
560}
561
562impl<F> embedded_storage::nor_flash::ReadNorFlash for FlashRegion<'_, F>
563where
564 F: embedded_storage::nor_flash::ReadNorFlash,
565{
566 const READ_SIZE: usize = F::READ_SIZE;
567
568 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
569 let address = offset + self.raw.offset();
570
571 if !self.contains(address) {
572 return Err(Error::OutOfBounds);
573 }
574
575 if !self.contains(address + bytes.len() as u32) {
576 return Err(Error::OutOfBounds);
577 }
578
579 self.flash
580 .read(address, bytes)
581 .map_err(|_e| Error::StorageError)
582 }
583
584 fn capacity(&self) -> usize {
585 self.flash.capacity()
586 }
587}
588
589impl<F> embedded_storage::nor_flash::NorFlash for FlashRegion<'_, F>
590where
591 F: embedded_storage::nor_flash::NorFlash,
592{
593 const WRITE_SIZE: usize = F::WRITE_SIZE;
594
595 const ERASE_SIZE: usize = F::ERASE_SIZE;
596
597 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
598 let address_from = from + self.raw.offset();
599 let address_to = to + self.raw.offset();
600
601 if self.raw.is_read_only() {
602 return Err(Error::WriteProtected);
603 }
604
605 if !self.contains(address_from) {
606 return Err(Error::OutOfBounds);
607 }
608
609 if !self.contains(address_to) {
610 return Err(Error::OutOfBounds);
611 }
612
613 self.flash
614 .erase(address_from, address_to)
615 .map_err(|_e| Error::StorageError)
616 }
617
618 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
619 let address = offset + self.raw.offset();
620
621 if self.raw.is_read_only() {
622 return Err(Error::WriteProtected);
623 }
624
625 if !self.contains(address) {
626 return Err(Error::OutOfBounds);
627 }
628
629 if !self.contains(address + bytes.len() as u32) {
630 return Err(Error::OutOfBounds);
631 }
632
633 self.flash
634 .write(address, bytes)
635 .map_err(|_e| Error::StorageError)
636 }
637}
638
639impl<F> embedded_storage::nor_flash::MultiwriteNorFlash for FlashRegion<'_, F> where
640 F: embedded_storage::nor_flash::MultiwriteNorFlash
641{
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647
648 static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin");
649 static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin");
650
651 #[test]
652 fn read_simple() {
653 let pt = PartitionTable::new(SIMPLE).unwrap();
654
655 assert_eq!(3, pt.len());
656
657 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
658 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
659 assert_eq!(0, pt.get_partition(2).unwrap().raw_type());
660
661 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
662 assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype());
663 assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype());
664
665 assert_eq!(
666 PartitionType::Data(DataPartitionSubType::Nvs),
667 pt.get_partition(0).unwrap().partition_type()
668 );
669 assert_eq!(
670 PartitionType::Data(DataPartitionSubType::Phy),
671 pt.get_partition(1).unwrap().partition_type()
672 );
673 assert_eq!(
674 PartitionType::App(AppPartitionSubType::Factory),
675 pt.get_partition(2).unwrap().partition_type()
676 );
677
678 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
679 assert_eq!(0xf000, pt.get_partition(1).unwrap().offset());
680 assert_eq!(0x10000, pt.get_partition(2).unwrap().offset());
681
682 assert_eq!(0x6000, pt.get_partition(0).unwrap().len());
683 assert_eq!(0x1000, pt.get_partition(1).unwrap().len());
684 assert_eq!(0x100000, pt.get_partition(2).unwrap().len());
685
686 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
687 assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str());
688 assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str());
689
690 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
691 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
692 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
693
694 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
695 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
696 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
697 }
698
699 #[test]
700 fn read_ota() {
701 let pt = PartitionTable::new(OTA).unwrap();
702
703 assert_eq!(6, pt.len());
704
705 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
706 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
707 assert_eq!(1, pt.get_partition(2).unwrap().raw_type());
708 assert_eq!(0, pt.get_partition(3).unwrap().raw_type());
709 assert_eq!(0, pt.get_partition(4).unwrap().raw_type());
710 assert_eq!(0, pt.get_partition(5).unwrap().raw_type());
711
712 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
713 assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype());
714 assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype());
715 assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype());
716 assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype());
717 assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype());
718
719 assert_eq!(
720 PartitionType::Data(DataPartitionSubType::Nvs),
721 pt.get_partition(0).unwrap().partition_type()
722 );
723 assert_eq!(
724 PartitionType::Data(DataPartitionSubType::Ota),
725 pt.get_partition(1).unwrap().partition_type()
726 );
727 assert_eq!(
728 PartitionType::Data(DataPartitionSubType::Phy),
729 pt.get_partition(2).unwrap().partition_type()
730 );
731 assert_eq!(
732 PartitionType::App(AppPartitionSubType::Factory),
733 pt.get_partition(3).unwrap().partition_type()
734 );
735 assert_eq!(
736 PartitionType::App(AppPartitionSubType::Ota0),
737 pt.get_partition(4).unwrap().partition_type()
738 );
739 assert_eq!(
740 PartitionType::App(AppPartitionSubType::Ota1),
741 pt.get_partition(5).unwrap().partition_type()
742 );
743
744 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
745 assert_eq!(0xd000, pt.get_partition(1).unwrap().offset());
746 assert_eq!(0xf000, pt.get_partition(2).unwrap().offset());
747 assert_eq!(0x10000, pt.get_partition(3).unwrap().offset());
748 assert_eq!(0x110000, pt.get_partition(4).unwrap().offset());
749 assert_eq!(0x210000, pt.get_partition(5).unwrap().offset());
750
751 assert_eq!(0x4000, pt.get_partition(0).unwrap().len());
752 assert_eq!(0x2000, pt.get_partition(1).unwrap().len());
753 assert_eq!(0x1000, pt.get_partition(2).unwrap().len());
754 assert_eq!(0x100000, pt.get_partition(3).unwrap().len());
755 assert_eq!(0x100000, pt.get_partition(4).unwrap().len());
756 assert_eq!(0x100000, pt.get_partition(5).unwrap().len());
757
758 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
759 assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str());
760 assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str());
761 assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str());
762 assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str());
763 assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str());
764
765 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
766 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
767 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
768 assert_eq!(false, pt.get_partition(3).unwrap().is_read_only());
769 assert_eq!(false, pt.get_partition(4).unwrap().is_read_only());
770 assert_eq!(false, pt.get_partition(5).unwrap().is_read_only());
771
772 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
773 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
774 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
775 assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted());
776 assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted());
777 assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted());
778 }
779
780 #[test]
781 fn empty_byte_array() {
782 let pt = PartitionTable::new(&[]).unwrap();
783
784 assert_eq!(0, pt.len());
785 assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds)));
786 }
787
788 #[test]
789 fn validation_fails_wo_hash() {
790 assert!(matches!(
791 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]),
792 Err(Error::Invalid)
793 ));
794 }
795
796 #[test]
797 fn validation_fails_wo_hash_max_entries() {
798 let mut data = [0u8; PARTITION_TABLE_MAX_LEN];
799 for i in 0..96 {
800 data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]);
801 }
802
803 assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid)));
804 }
805
806 #[test]
807 fn validation_succeeds_with_enough_entries() {
808 assert_eq!(
809 3,
810 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4])
811 .unwrap()
812 .len()
813 );
814 }
815}