1pub const PARTITION_TABLE_MAX_LEN: usize = 0xC00;
12
13const PARTITION_TABLE_OFFSET: u32 =
14 esp_config::esp_config_int!(u32, "ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET");
15
16const RAW_ENTRY_LEN: usize = 32;
17const ENTRY_MAGIC: u16 = 0x50aa;
18const MD5_MAGIC: u16 = 0xebeb;
19
20const OTA_SUBTYPE_OFFSET: u8 = 0x10;
21
22#[derive(Clone, Copy)]
24pub struct PartitionEntry<'a> {
25 pub(crate) binary: &'a [u8; RAW_ENTRY_LEN],
26}
27
28impl<'a> PartitionEntry<'a> {
29 fn new(binary: &'a [u8; RAW_ENTRY_LEN]) -> Self {
30 Self { binary }
31 }
32
33 pub fn magic(&self) -> u16 {
35 u16::from_le_bytes(unwrap!(self.binary[..2].try_into()))
36 }
37
38 pub fn raw_type(&self) -> u8 {
40 self.binary[2]
41 }
42
43 pub fn raw_subtype(&self) -> u8 {
45 self.binary[3]
46 }
47
48 pub fn offset(&self) -> u32 {
50 u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into()))
51 }
52
53 pub fn len(&self) -> u32 {
55 u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into()))
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.len() == 0
61 }
62
63 pub fn label(&self) -> &'a [u8] {
65 &self.binary[12..][..16]
66 }
67
68 pub fn label_as_str(&self) -> &'a str {
70 let array = self.label();
71 let len = array
72 .iter()
73 .position(|b| *b == 0 || *b == 0xff)
74 .unwrap_or(array.len());
75 unsafe {
76 core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len))
77 }
78 }
79
80 pub fn flags(&self) -> u32 {
83 u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into()))
84 }
85
86 pub fn is_read_only(&self) -> bool {
88 self.flags() & 0b01 != 0
89 }
90
91 pub fn is_encrypted(&self) -> bool {
93 self.flags() & 0b10 != 0
94 }
95
96 pub fn partition_type(&self) -> PartitionType {
98 match self.raw_type() {
99 0 => PartitionType::App(unwrap!(self.raw_subtype().try_into())),
100 1 => PartitionType::Data(unwrap!(self.raw_subtype().try_into())),
101 2 => PartitionType::Bootloader(unwrap!(self.raw_subtype().try_into())),
102 3 => PartitionType::PartitionTable(unwrap!(self.raw_subtype().try_into())),
103 _ => unreachable!(),
104 }
105 }
106
107 pub fn as_embedded_storage<F>(self, flash: &'a mut F) -> FlashRegion<'a, F>
111 where
112 F: embedded_storage::ReadStorage,
113 {
114 FlashRegion { raw: self, flash }
115 }
116}
117
118impl core::fmt::Debug for PartitionEntry<'_> {
119 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120 f.debug_struct("PartitionEntry")
121 .field("magic", &self.magic())
122 .field("raw_type", &self.raw_type())
123 .field("raw_subtype", &self.raw_subtype())
124 .field("offset", &self.offset())
125 .field("len", &self.len())
126 .field("label", &self.label_as_str())
127 .field("flags", &self.flags())
128 .field("is_read_only", &self.is_read_only())
129 .field("is_encrypted", &self.is_encrypted())
130 .finish()
131 }
132}
133
134#[cfg(feature = "defmt")]
135impl defmt::Format for PartitionEntry<'_> {
136 fn format(&self, fmt: defmt::Formatter) {
137 defmt::write!(
138 fmt,
139 "PartitionEntry (\
140 magic = {}, \
141 raw_type = {}, \
142 raw_subtype = {}, \
143 offset = {}, \
144 len = {}, \
145 label = {}, \
146 flags = {}, \
147 is_read_only = {}, \
148 is_encrypted = {}\
149 )",
150 self.magic(),
151 self.raw_type(),
152 self.raw_subtype(),
153 self.offset(),
154 self.len(),
155 self.label_as_str(),
156 self.flags(),
157 self.is_read_only(),
158 self.is_encrypted()
159 )
160 }
161}
162
163#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::Display)]
165#[cfg_attr(feature = "defmt", derive(defmt::Format))]
166#[non_exhaustive]
167pub enum Error {
168 Invalid,
170 OutOfBounds,
172 StorageError,
174 WriteProtected,
176 InvalidPartition {
178 expected_size: usize,
179 expected_type: PartitionType,
180 },
181 InvalidState,
183 InvalidArgument,
185}
186
187impl core::error::Error for Error {}
188
189#[derive(Debug)]
191#[cfg_attr(feature = "defmt", derive(defmt::Format))]
192pub struct PartitionTable<'a> {
193 binary: &'a [[u8; RAW_ENTRY_LEN]],
194 entries: usize,
195}
196
197impl<'a> PartitionTable<'a> {
198 fn new(binary: &'a [u8]) -> Result<Self, Error> {
199 if binary.len() > PARTITION_TABLE_MAX_LEN {
200 return Err(Error::Invalid);
201 }
202
203 let (binary, rem) = binary.as_chunks::<RAW_ENTRY_LEN>();
204 if !rem.is_empty() {
205 return Err(Error::Invalid);
206 }
207
208 if binary.is_empty() {
209 return Ok(Self {
210 binary: &[],
211 entries: 0,
212 });
213 }
214
215 let mut raw_table = Self {
216 binary,
217 entries: binary.len(),
218 };
219
220 #[cfg(feature = "validation")]
221 {
222 let (hash, index) = {
223 let mut i = 0;
224 loop {
225 if let Ok(entry) = raw_table.get_partition(i) {
226 if entry.magic() == MD5_MAGIC {
227 break (&entry.binary[16..][..16], i);
228 }
229
230 i += 1;
231 if i >= raw_table.entries {
232 return Err(Error::Invalid);
233 }
234 }
235 }
236 };
237
238 let mut hasher = crate::crypto::Md5::new();
239
240 for i in 0..index {
241 hasher.update(&raw_table.binary[i]);
242 }
243 let calculated_hash = hasher.finalize();
244
245 if calculated_hash != hash {
246 return Err(Error::Invalid);
247 }
248 }
249
250 let entries = {
251 let mut i = 0;
252 loop {
253 if let Ok(entry) = raw_table.get_partition(i) {
254 if entry.magic() != ENTRY_MAGIC {
255 break;
256 }
257
258 i += 1;
259
260 if i == raw_table.entries {
261 break;
262 }
263 } else {
264 return Err(Error::Invalid);
265 }
266 }
267 i
268 };
269
270 raw_table.entries = entries;
271
272 Ok(raw_table)
273 }
274
275 pub fn len(&self) -> usize {
277 self.entries
278 }
279
280 pub fn is_empty(&self) -> bool {
282 self.entries == 0
283 }
284
285 pub fn get_partition(&self, index: usize) -> Result<PartitionEntry<'a>, Error> {
287 if index >= self.entries {
288 return Err(Error::OutOfBounds);
289 }
290 Ok(PartitionEntry::new(&self.binary[index]))
291 }
292
293 pub fn find_partition(&self, pt: PartitionType) -> Result<Option<PartitionEntry<'a>>, Error> {
295 for i in 0..self.entries {
296 let entry = self.get_partition(i)?;
297 if entry.partition_type() == pt {
298 return Ok(Some(entry));
299 }
300 }
301 Ok(None)
302 }
303
304 pub fn iter(&self) -> impl Iterator<Item = PartitionEntry<'a>> {
306 (0..self.entries).filter_map(|i| self.get_partition(i).ok())
307 }
308
309 #[cfg(feature = "std")]
310 pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
312 Err(Error::Invalid)
313 }
314
315 #[cfg(not(feature = "std"))]
316 pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
318 cfg_if::cfg_if! {
322 if #[cfg(feature = "esp32")] {
323 let paddr = unsafe {
324 ((0x3FF10000 as *const u32).read_volatile() & 0xff) << 16
325 };
326 } else if #[cfg(feature = "esp32s2")] {
327 let paddr = unsafe {
328 (((0x61801000 + 128 * 4) as *const u32).read_volatile() & 0xff) << 16
329 };
330 } else if #[cfg(feature = "esp32s3")] {
331 let paddr = unsafe {
333 ((0x600C5000 as *const u32).read_volatile() & 0xff) << 16
334 };
335 } else if #[cfg(any(feature = "esp32c2", feature = "esp32c3"))] {
336 let paddr = unsafe {
337 ((0x600c5000 as *const u32).read_volatile() & 0xff) << 16
338 };
339 } else if #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] {
340 let paddr = unsafe {
341 ((0x60002000 + 0x380) as *mut u32).write_volatile(0);
342 (((0x60002000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16
343 };
344 }
345 }
346
347 for id in 0..self.len() {
348 let entry = self.get_partition(id)?;
349 if entry.offset() == paddr {
350 return Ok(Some(entry));
351 }
352 }
353
354 Ok(None)
355 }
356}
357
358#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
360#[cfg_attr(feature = "defmt", derive(defmt::Format))]
361pub enum PartitionType {
362 App(AppPartitionSubType),
364 Data(DataPartitionSubType),
366 Bootloader(BootloaderPartitionSubType),
368 PartitionTable(PartitionTablePartitionSubType),
370}
371
372#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
374#[cfg_attr(feature = "defmt", derive(defmt::Format))]
375#[repr(u8)]
376pub enum RawPartitionType {
377 App = 0,
379 Data,
381 Bootloader,
383 PartitionTable,
385}
386
387#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
389#[cfg_attr(feature = "defmt", derive(defmt::Format))]
390#[repr(u8)]
391pub enum AppPartitionSubType {
392 Factory = 0,
394 Ota0 = OTA_SUBTYPE_OFFSET,
396 Ota1,
398 Ota2,
400 Ota3,
402 Ota4,
404 Ota5,
406 Ota6,
408 Ota7,
410 Ota8,
412 Ota9,
414 Ota10,
416 Ota11,
418 Ota12,
420 Ota13,
422 Ota14,
424 Ota15,
426 Test,
428}
429
430impl AppPartitionSubType {
431 pub(crate) fn ota_app_number(&self) -> u8 {
432 *self as u8 - OTA_SUBTYPE_OFFSET
433 }
434
435 pub(crate) fn from_ota_app_number(number: u8) -> Result<Self, Error> {
436 if number > 16 {
437 return Err(Error::InvalidArgument);
438 }
439 Self::try_from(number + OTA_SUBTYPE_OFFSET)
440 }
441}
442
443impl TryFrom<u8> for AppPartitionSubType {
444 type Error = Error;
445
446 fn try_from(value: u8) -> Result<Self, Self::Error> {
447 AppPartitionSubType::from_repr(value).ok_or(Error::Invalid)
448 }
449}
450
451#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
453#[cfg_attr(feature = "defmt", derive(defmt::Format))]
454#[repr(u8)]
455pub enum DataPartitionSubType {
456 Ota = 0,
460 Phy,
463 Nvs,
465 Coredump,
467 NvsKeys,
469 EfuseEm,
471 Undefined,
474 Fat = 0x81,
476 Spiffs = 0x82,
478 LittleFs = 0x83,
480}
481
482impl TryFrom<u8> for DataPartitionSubType {
483 type Error = Error;
484
485 fn try_from(value: u8) -> Result<Self, Self::Error> {
486 DataPartitionSubType::from_repr(value).ok_or(Error::Invalid)
487 }
488}
489
490#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
492#[cfg_attr(feature = "defmt", derive(defmt::Format))]
493#[repr(u8)]
494pub enum BootloaderPartitionSubType {
495 Primary = 0,
497 Ota = 1,
500}
501
502impl TryFrom<u8> for BootloaderPartitionSubType {
503 type Error = Error;
504
505 fn try_from(value: u8) -> Result<Self, Self::Error> {
506 BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid)
507 }
508}
509
510#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
512#[cfg_attr(feature = "defmt", derive(defmt::Format))]
513#[repr(u8)]
514pub enum PartitionTablePartitionSubType {
515 Primary = 0,
517 Ota = 1,
520}
521
522impl TryFrom<u8> for PartitionTablePartitionSubType {
523 type Error = Error;
524
525 fn try_from(value: u8) -> Result<Self, Self::Error> {
526 PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid)
527 }
528}
529
530pub fn read_partition_table<'a>(
535 flash: &mut impl embedded_storage::Storage,
536 storage: &'a mut [u8],
537) -> Result<PartitionTable<'a>, Error> {
538 flash
539 .read(PARTITION_TABLE_OFFSET, storage)
540 .map_err(|_e| Error::StorageError)?;
541
542 PartitionTable::new(storage)
543}
544
545#[derive(Debug)]
550#[cfg_attr(feature = "defmt", derive(defmt::Format))]
551pub struct FlashRegion<'a, F> {
552 pub(crate) raw: PartitionEntry<'a>,
553 pub(crate) flash: &'a mut F,
554}
555
556impl<F> FlashRegion<'_, F> {
557 pub fn partition_size(&self) -> usize {
559 self.raw.len() as _
560 }
561
562 fn range(&self) -> core::ops::Range<u32> {
563 self.raw.offset()..self.raw.offset() + self.raw.len()
564 }
565
566 fn in_range(&self, start: u32, len: usize) -> bool {
567 self.range().contains(&start) && (start + len as u32 <= self.range().end)
568 }
569}
570
571impl<F> embedded_storage::Region for FlashRegion<'_, F> {
572 fn contains(&self, address: u32) -> bool {
573 self.range().contains(&address)
574 }
575}
576
577impl<F> embedded_storage::ReadStorage for FlashRegion<'_, F>
578where
579 F: embedded_storage::ReadStorage,
580{
581 type Error = Error;
582
583 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
584 let address = offset + self.raw.offset();
585
586 if !self.in_range(address, bytes.len()) {
587 return Err(Error::OutOfBounds);
588 }
589
590 self.flash
591 .read(address, bytes)
592 .map_err(|_e| Error::StorageError)
593 }
594
595 fn capacity(&self) -> usize {
596 self.partition_size()
597 }
598}
599
600impl<F> embedded_storage::Storage for FlashRegion<'_, F>
601where
602 F: embedded_storage::Storage,
603{
604 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
605 let address = offset + self.raw.offset();
606
607 if self.raw.is_read_only() {
608 return Err(Error::WriteProtected);
609 }
610
611 if !self.in_range(address, bytes.len()) {
612 return Err(Error::OutOfBounds);
613 }
614
615 self.flash
616 .write(address, bytes)
617 .map_err(|_e| Error::StorageError)
618 }
619}
620
621impl embedded_storage::nor_flash::NorFlashError for Error {
622 fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind {
623 match self {
624 Error::OutOfBounds => embedded_storage::nor_flash::NorFlashErrorKind::OutOfBounds,
625 _ => embedded_storage::nor_flash::NorFlashErrorKind::Other,
626 }
627 }
628}
629
630impl<F> embedded_storage::nor_flash::ErrorType for FlashRegion<'_, F> {
631 type Error = Error;
632}
633
634impl<F> embedded_storage::nor_flash::ReadNorFlash for FlashRegion<'_, F>
635where
636 F: embedded_storage::nor_flash::ReadNorFlash,
637{
638 const READ_SIZE: usize = F::READ_SIZE;
639
640 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
641 let address = offset + self.raw.offset();
642
643 if !self.in_range(address, bytes.len()) {
644 return Err(Error::OutOfBounds);
645 }
646
647 self.flash
648 .read(address, bytes)
649 .map_err(|_e| Error::StorageError)
650 }
651
652 fn capacity(&self) -> usize {
653 self.partition_size()
654 }
655}
656
657impl<F> embedded_storage::nor_flash::NorFlash for FlashRegion<'_, F>
658where
659 F: embedded_storage::nor_flash::NorFlash,
660{
661 const WRITE_SIZE: usize = F::WRITE_SIZE;
662
663 const ERASE_SIZE: usize = F::ERASE_SIZE;
664
665 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
666 let address_from = from + self.raw.offset();
667 let address_to = to + self.raw.offset();
668
669 if self.raw.is_read_only() {
670 return Err(Error::WriteProtected);
671 }
672
673 if !self.range().contains(&address_from) {
674 return Err(Error::OutOfBounds);
675 }
676
677 if !self.range().contains(&address_to) {
678 return Err(Error::OutOfBounds);
679 }
680
681 self.flash
682 .erase(address_from, address_to)
683 .map_err(|_e| Error::StorageError)
684 }
685
686 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
687 let address = offset + self.raw.offset();
688
689 if self.raw.is_read_only() {
690 return Err(Error::WriteProtected);
691 }
692
693 if !self.in_range(address, bytes.len()) {
694 return Err(Error::OutOfBounds);
695 }
696
697 self.flash
698 .write(address, bytes)
699 .map_err(|_e| Error::StorageError)
700 }
701}
702
703impl<F> embedded_storage::nor_flash::MultiwriteNorFlash for FlashRegion<'_, F> where
704 F: embedded_storage::nor_flash::MultiwriteNorFlash
705{
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin");
713 static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin");
714
715 #[test]
716 fn read_simple() {
717 let pt = PartitionTable::new(SIMPLE).unwrap();
718
719 assert_eq!(3, pt.len());
720
721 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
722 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
723 assert_eq!(0, pt.get_partition(2).unwrap().raw_type());
724
725 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
726 assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype());
727 assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype());
728
729 assert_eq!(
730 PartitionType::Data(DataPartitionSubType::Nvs),
731 pt.get_partition(0).unwrap().partition_type()
732 );
733 assert_eq!(
734 PartitionType::Data(DataPartitionSubType::Phy),
735 pt.get_partition(1).unwrap().partition_type()
736 );
737 assert_eq!(
738 PartitionType::App(AppPartitionSubType::Factory),
739 pt.get_partition(2).unwrap().partition_type()
740 );
741
742 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
743 assert_eq!(0xf000, pt.get_partition(1).unwrap().offset());
744 assert_eq!(0x10000, pt.get_partition(2).unwrap().offset());
745
746 assert_eq!(0x6000, pt.get_partition(0).unwrap().len());
747 assert_eq!(0x1000, pt.get_partition(1).unwrap().len());
748 assert_eq!(0x100000, pt.get_partition(2).unwrap().len());
749
750 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
751 assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str());
752 assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str());
753
754 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
755 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
756 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
757
758 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
759 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
760 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
761 }
762
763 #[test]
764 fn read_ota() {
765 let pt = PartitionTable::new(OTA).unwrap();
766
767 assert_eq!(6, pt.len());
768
769 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
770 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
771 assert_eq!(1, pt.get_partition(2).unwrap().raw_type());
772 assert_eq!(0, pt.get_partition(3).unwrap().raw_type());
773 assert_eq!(0, pt.get_partition(4).unwrap().raw_type());
774 assert_eq!(0, pt.get_partition(5).unwrap().raw_type());
775
776 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
777 assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype());
778 assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype());
779 assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype());
780 assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype());
781 assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype());
782
783 assert_eq!(
784 PartitionType::Data(DataPartitionSubType::Nvs),
785 pt.get_partition(0).unwrap().partition_type()
786 );
787 assert_eq!(
788 PartitionType::Data(DataPartitionSubType::Ota),
789 pt.get_partition(1).unwrap().partition_type()
790 );
791 assert_eq!(
792 PartitionType::Data(DataPartitionSubType::Phy),
793 pt.get_partition(2).unwrap().partition_type()
794 );
795 assert_eq!(
796 PartitionType::App(AppPartitionSubType::Factory),
797 pt.get_partition(3).unwrap().partition_type()
798 );
799 assert_eq!(
800 PartitionType::App(AppPartitionSubType::Ota0),
801 pt.get_partition(4).unwrap().partition_type()
802 );
803 assert_eq!(
804 PartitionType::App(AppPartitionSubType::Ota1),
805 pt.get_partition(5).unwrap().partition_type()
806 );
807
808 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
809 assert_eq!(0xd000, pt.get_partition(1).unwrap().offset());
810 assert_eq!(0xf000, pt.get_partition(2).unwrap().offset());
811 assert_eq!(0x10000, pt.get_partition(3).unwrap().offset());
812 assert_eq!(0x110000, pt.get_partition(4).unwrap().offset());
813 assert_eq!(0x210000, pt.get_partition(5).unwrap().offset());
814
815 assert_eq!(0x4000, pt.get_partition(0).unwrap().len());
816 assert_eq!(0x2000, pt.get_partition(1).unwrap().len());
817 assert_eq!(0x1000, pt.get_partition(2).unwrap().len());
818 assert_eq!(0x100000, pt.get_partition(3).unwrap().len());
819 assert_eq!(0x100000, pt.get_partition(4).unwrap().len());
820 assert_eq!(0x100000, pt.get_partition(5).unwrap().len());
821
822 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
823 assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str());
824 assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str());
825 assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str());
826 assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str());
827 assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str());
828
829 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
830 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
831 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
832 assert_eq!(false, pt.get_partition(3).unwrap().is_read_only());
833 assert_eq!(false, pt.get_partition(4).unwrap().is_read_only());
834 assert_eq!(false, pt.get_partition(5).unwrap().is_read_only());
835
836 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
837 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
838 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
839 assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted());
840 assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted());
841 assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted());
842 }
843
844 #[test]
845 fn empty_byte_array() {
846 let pt = PartitionTable::new(&[]).unwrap();
847
848 assert_eq!(0, pt.len());
849 assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds)));
850 }
851
852 #[test]
853 fn validation_fails_wo_hash() {
854 assert!(matches!(
855 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]),
856 Err(Error::Invalid)
857 ));
858 }
859
860 #[test]
861 fn validation_fails_wo_hash_max_entries() {
862 let mut data = [0u8; PARTITION_TABLE_MAX_LEN];
863 for i in 0..96 {
864 data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]);
865 }
866
867 assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid)));
868 }
869
870 #[test]
871 fn validation_succeeds_with_enough_entries() {
872 assert_eq!(
873 3,
874 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4])
875 .unwrap()
876 .len()
877 );
878 }
879}
880
881#[cfg(test)]
882mod storage_tests {
883 use embedded_storage::{ReadStorage, Storage};
884
885 use super::*;
886
887 struct MockFlash {
888 data: [u8; 0x10000],
889 }
890
891 impl MockFlash {
892 fn new() -> Self {
893 let mut data = [23u8; 0x10000];
894 data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN as usize]
895 .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin"));
896 Self { data }
897 }
898 }
899
900 impl embedded_storage::Storage for MockFlash {
901 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
902 self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes);
903 Ok(())
904 }
905 }
906
907 impl embedded_storage::ReadStorage for MockFlash {
908 type Error = crate::partitions::Error;
909 fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> {
910 let l = buffer.len();
911 buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]);
912 Ok(())
913 }
914
915 fn capacity(&self) -> usize {
916 unimplemented!()
917 }
918 }
919
920 #[test]
921 fn can_read_write_all_of_nvs() {
922 let mut storage = MockFlash::new();
923
924 let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
925 let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
926
927 let nvs = pt
928 .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
929 .unwrap()
930 .unwrap();
931 let mut nvs_partition = nvs.as_embedded_storage(&mut storage);
932 assert_eq!(nvs_partition.raw.offset(), 36864);
933
934 assert_eq!(nvs_partition.capacity(), 24576);
935
936 let mut buffer = [0u8; 24576];
937 nvs_partition.read(0, &mut buffer).unwrap();
938 assert!(buffer.iter().all(|v| *v == 23));
939 buffer.fill(42);
940 nvs_partition.write(0, &mut buffer).unwrap();
941 let mut buffer = [0u8; 24576];
942 nvs_partition.read(0, &mut buffer).unwrap();
943 assert!(buffer.iter().all(|v| *v == 42));
944 }
945
946 #[test]
947 fn cannot_read_write_more_than_partition_size() {
948 let mut storage = MockFlash::new();
949
950 let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
951 let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
952
953 let nvs = pt
954 .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
955 .unwrap()
956 .unwrap();
957 let mut nvs_partition = nvs.as_embedded_storage(&mut storage);
958 assert_eq!(nvs_partition.raw.offset(), 36864);
959
960 assert_eq!(nvs_partition.capacity(), 24576);
961
962 let mut buffer = [0u8; 24577];
963 assert!(nvs_partition.read(0, &mut buffer) == Err(Error::OutOfBounds));
964 }
965}