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
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))]
165#[non_exhaustive]
166pub enum Error {
167 Invalid,
169 OutOfBounds,
171 StorageError,
173 WriteProtected,
175 InvalidPartition {
177 expected_size: usize,
178 expected_type: PartitionType,
179 },
180 InvalidState,
182 InvalidArgument,
184}
185
186impl core::error::Error for Error {}
187
188#[derive(Debug)]
190#[cfg_attr(feature = "defmt", derive(defmt::Format))]
191pub struct PartitionTable<'a> {
192 binary: &'a [[u8; RAW_ENTRY_LEN]],
193 entries: usize,
194}
195
196impl<'a> PartitionTable<'a> {
197 fn new(binary: &'a [u8]) -> Result<Self, Error> {
198 if binary.len() > PARTITION_TABLE_MAX_LEN {
199 return Err(Error::Invalid);
200 }
201
202 let (binary, rem) = binary.as_chunks::<RAW_ENTRY_LEN>();
203 if !rem.is_empty() {
204 return Err(Error::Invalid);
205 }
206
207 if binary.is_empty() {
208 return Ok(Self {
209 binary: &[],
210 entries: 0,
211 });
212 }
213
214 let mut raw_table = Self {
215 binary,
216 entries: binary.len(),
217 };
218
219 #[cfg(feature = "validation")]
220 {
221 let (hash, index) = {
222 let mut i = 0;
223 loop {
224 if let Ok(entry) = raw_table.get_partition(i) {
225 if entry.magic() == MD5_MAGIC {
226 break (&entry.binary[16..][..16], i);
227 }
228
229 i += 1;
230 if i >= raw_table.entries {
231 return Err(Error::Invalid);
232 }
233 }
234 }
235 };
236
237 let mut hasher = crate::crypto::Md5::new();
238
239 for i in 0..index {
240 hasher.update(&raw_table.binary[i]);
241 }
242 let calculated_hash = hasher.finalize();
243
244 if calculated_hash != hash {
245 return Err(Error::Invalid);
246 }
247 }
248
249 let entries = {
250 let mut i = 0;
251 loop {
252 if let Ok(entry) = raw_table.get_partition(i) {
253 if entry.magic() != ENTRY_MAGIC {
254 break;
255 }
256
257 i += 1;
258
259 if i == raw_table.entries {
260 break;
261 }
262 } else {
263 return Err(Error::Invalid);
264 }
265 }
266 i
267 };
268
269 raw_table.entries = entries;
270
271 Ok(raw_table)
272 }
273
274 pub fn len(&self) -> usize {
276 self.entries
277 }
278
279 pub fn is_empty(&self) -> bool {
281 self.entries == 0
282 }
283
284 pub fn get_partition(&self, index: usize) -> Result<PartitionEntry<'a>, Error> {
286 if index >= self.entries {
287 return Err(Error::OutOfBounds);
288 }
289 Ok(PartitionEntry::new(&self.binary[index]))
290 }
291
292 pub fn find_partition(&self, pt: PartitionType) -> Result<Option<PartitionEntry<'a>>, Error> {
294 for i in 0..self.entries {
295 let entry = self.get_partition(i)?;
296 if entry.partition_type() == pt {
297 return Ok(Some(entry));
298 }
299 }
300 Ok(None)
301 }
302
303 pub fn iter(&self) -> impl Iterator<Item = PartitionEntry<'a>> {
305 (0..self.entries).filter_map(|i| self.get_partition(i).ok())
306 }
307
308 #[cfg(feature = "std")]
309 pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
311 Err(Error::Invalid)
312 }
313
314 #[cfg(not(feature = "std"))]
315 pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
317 cfg_if::cfg_if! {
321 if #[cfg(feature = "esp32")] {
322 let paddr = unsafe {
323 ((0x3FF10000 as *const u32).read_volatile() & 0xff) << 16
324 };
325 } else if #[cfg(feature = "esp32s2")] {
326 let paddr = unsafe {
327 (((0x61801000 + 128 * 4) as *const u32).read_volatile() & 0xff) << 16
328 };
329 } else if #[cfg(feature = "esp32s3")] {
330 let paddr = unsafe {
332 ((0x600C5000 as *const u32).read_volatile() & 0xff) << 16
333 };
334 } else if #[cfg(any(feature = "esp32c2", feature = "esp32c3"))] {
335 let paddr = unsafe {
336 ((0x600c5000 as *const u32).read_volatile() & 0xff) << 16
337 };
338 } else if #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] {
339 let paddr = unsafe {
340 ((0x60002000 + 0x380) as *mut u32).write_volatile(0);
341 (((0x60002000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16
342 };
343 }
344 }
345
346 for id in 0..self.len() {
347 let entry = self.get_partition(id)?;
348 if entry.offset() == paddr {
349 return Ok(Some(entry));
350 }
351 }
352
353 Ok(None)
354 }
355}
356
357#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
359#[cfg_attr(feature = "defmt", derive(defmt::Format))]
360pub enum PartitionType {
361 App(AppPartitionSubType),
363 Data(DataPartitionSubType),
365 Bootloader(BootloaderPartitionSubType),
367 PartitionTable(PartitionTablePartitionSubType),
369}
370
371#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
373#[cfg_attr(feature = "defmt", derive(defmt::Format))]
374#[repr(u8)]
375pub enum RawPartitionType {
376 App = 0,
378 Data,
380 Bootloader,
382 PartitionTable,
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 AppPartitionSubType {
391 Factory = 0,
393 Ota0 = OTA_SUBTYPE_OFFSET,
395 Ota1,
397 Ota2,
399 Ota3,
401 Ota4,
403 Ota5,
405 Ota6,
407 Ota7,
409 Ota8,
411 Ota9,
413 Ota10,
415 Ota11,
417 Ota12,
419 Ota13,
421 Ota14,
423 Ota15,
425 Test,
427}
428
429impl AppPartitionSubType {
430 pub(crate) fn ota_app_number(&self) -> u8 {
431 *self as u8 - OTA_SUBTYPE_OFFSET
432 }
433
434 pub(crate) fn from_ota_app_number(number: u8) -> Result<Self, Error> {
435 if number > 16 {
436 return Err(Error::InvalidArgument);
437 }
438 Self::try_from(number + OTA_SUBTYPE_OFFSET)
439 }
440}
441
442impl TryFrom<u8> for AppPartitionSubType {
443 type Error = Error;
444
445 fn try_from(value: u8) -> Result<Self, Self::Error> {
446 AppPartitionSubType::from_repr(value).ok_or(Error::Invalid)
447 }
448}
449
450#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
452#[cfg_attr(feature = "defmt", derive(defmt::Format))]
453#[repr(u8)]
454pub enum DataPartitionSubType {
455 Ota = 0,
459 Phy,
462 Nvs,
464 Coredump,
466 NvsKeys,
468 EfuseEm,
470 Undefined,
473 Fat = 0x81,
475 Spiffs = 0x82,
477 LittleFs = 0x83,
479}
480
481impl TryFrom<u8> for DataPartitionSubType {
482 type Error = Error;
483
484 fn try_from(value: u8) -> Result<Self, Self::Error> {
485 DataPartitionSubType::from_repr(value).ok_or(Error::Invalid)
486 }
487}
488
489#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
491#[cfg_attr(feature = "defmt", derive(defmt::Format))]
492#[repr(u8)]
493pub enum BootloaderPartitionSubType {
494 Primary = 0,
496 Ota = 1,
499}
500
501impl TryFrom<u8> for BootloaderPartitionSubType {
502 type Error = Error;
503
504 fn try_from(value: u8) -> Result<Self, Self::Error> {
505 BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid)
506 }
507}
508
509#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
511#[cfg_attr(feature = "defmt", derive(defmt::Format))]
512#[repr(u8)]
513pub enum PartitionTablePartitionSubType {
514 Primary = 0,
516 Ota = 1,
519}
520
521impl TryFrom<u8> for PartitionTablePartitionSubType {
522 type Error = Error;
523
524 fn try_from(value: u8) -> Result<Self, Self::Error> {
525 PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid)
526 }
527}
528
529pub fn read_partition_table<'a>(
534 flash: &mut impl embedded_storage::Storage,
535 storage: &'a mut [u8],
536) -> Result<PartitionTable<'a>, Error> {
537 flash
538 .read(PARTITION_TABLE_OFFSET, storage)
539 .map_err(|_e| Error::StorageError)?;
540
541 PartitionTable::new(storage)
542}
543
544#[derive(Debug)]
549#[cfg_attr(feature = "defmt", derive(defmt::Format))]
550pub struct FlashRegion<'a, F> {
551 pub(crate) raw: &'a PartitionEntry<'a>,
552 pub(crate) flash: &'a mut F,
553}
554
555impl<F> FlashRegion<'_, F> {
556 pub fn partition_size(&self) -> usize {
558 self.raw.len() as _
559 }
560
561 fn range(&self) -> core::ops::Range<u32> {
562 self.raw.offset()..self.raw.offset() + self.raw.len()
563 }
564
565 fn in_range(&self, start: u32, len: usize) -> bool {
566 self.range().contains(&start) && (start + len as u32 <= self.range().end)
567 }
568}
569
570impl<F> embedded_storage::Region for FlashRegion<'_, F> {
571 fn contains(&self, address: u32) -> bool {
572 self.range().contains(&address)
573 }
574}
575
576impl<F> embedded_storage::ReadStorage for FlashRegion<'_, F>
577where
578 F: embedded_storage::ReadStorage,
579{
580 type Error = Error;
581
582 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
583 let address = offset + self.raw.offset();
584
585 if !self.in_range(address, bytes.len()) {
586 return Err(Error::OutOfBounds);
587 }
588
589 self.flash
590 .read(address, bytes)
591 .map_err(|_e| Error::StorageError)
592 }
593
594 fn capacity(&self) -> usize {
595 self.partition_size()
596 }
597}
598
599impl<F> embedded_storage::Storage for FlashRegion<'_, F>
600where
601 F: embedded_storage::Storage,
602{
603 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
604 let address = offset + self.raw.offset();
605
606 if self.raw.is_read_only() {
607 return Err(Error::WriteProtected);
608 }
609
610 if !self.in_range(address, bytes.len()) {
611 return Err(Error::OutOfBounds);
612 }
613
614 self.flash
615 .write(address, bytes)
616 .map_err(|_e| Error::StorageError)
617 }
618}
619
620impl embedded_storage::nor_flash::NorFlashError for Error {
621 fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind {
622 match self {
623 Error::OutOfBounds => embedded_storage::nor_flash::NorFlashErrorKind::OutOfBounds,
624 _ => embedded_storage::nor_flash::NorFlashErrorKind::Other,
625 }
626 }
627}
628
629impl<F> embedded_storage::nor_flash::ErrorType for FlashRegion<'_, F> {
630 type Error = Error;
631}
632
633impl<F> embedded_storage::nor_flash::ReadNorFlash for FlashRegion<'_, F>
634where
635 F: embedded_storage::nor_flash::ReadNorFlash,
636{
637 const READ_SIZE: usize = F::READ_SIZE;
638
639 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
640 let address = offset + self.raw.offset();
641
642 if !self.in_range(address, bytes.len()) {
643 return Err(Error::OutOfBounds);
644 }
645
646 self.flash
647 .read(address, bytes)
648 .map_err(|_e| Error::StorageError)
649 }
650
651 fn capacity(&self) -> usize {
652 self.partition_size()
653 }
654}
655
656impl<F> embedded_storage::nor_flash::NorFlash for FlashRegion<'_, F>
657where
658 F: embedded_storage::nor_flash::NorFlash,
659{
660 const WRITE_SIZE: usize = F::WRITE_SIZE;
661
662 const ERASE_SIZE: usize = F::ERASE_SIZE;
663
664 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
665 let address_from = from + self.raw.offset();
666 let address_to = to + self.raw.offset();
667
668 if self.raw.is_read_only() {
669 return Err(Error::WriteProtected);
670 }
671
672 if !self.range().contains(&address_from) {
673 return Err(Error::OutOfBounds);
674 }
675
676 if !self.range().contains(&address_to) {
677 return Err(Error::OutOfBounds);
678 }
679
680 self.flash
681 .erase(address_from, address_to)
682 .map_err(|_e| Error::StorageError)
683 }
684
685 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
686 let address = offset + self.raw.offset();
687
688 if self.raw.is_read_only() {
689 return Err(Error::WriteProtected);
690 }
691
692 if !self.in_range(address, bytes.len()) {
693 return Err(Error::OutOfBounds);
694 }
695
696 self.flash
697 .write(address, bytes)
698 .map_err(|_e| Error::StorageError)
699 }
700}
701
702impl<F> embedded_storage::nor_flash::MultiwriteNorFlash for FlashRegion<'_, F> where
703 F: embedded_storage::nor_flash::MultiwriteNorFlash
704{
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710
711 static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin");
712 static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin");
713
714 #[test]
715 fn read_simple() {
716 let pt = PartitionTable::new(SIMPLE).unwrap();
717
718 assert_eq!(3, pt.len());
719
720 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
721 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
722 assert_eq!(0, pt.get_partition(2).unwrap().raw_type());
723
724 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
725 assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype());
726 assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype());
727
728 assert_eq!(
729 PartitionType::Data(DataPartitionSubType::Nvs),
730 pt.get_partition(0).unwrap().partition_type()
731 );
732 assert_eq!(
733 PartitionType::Data(DataPartitionSubType::Phy),
734 pt.get_partition(1).unwrap().partition_type()
735 );
736 assert_eq!(
737 PartitionType::App(AppPartitionSubType::Factory),
738 pt.get_partition(2).unwrap().partition_type()
739 );
740
741 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
742 assert_eq!(0xf000, pt.get_partition(1).unwrap().offset());
743 assert_eq!(0x10000, pt.get_partition(2).unwrap().offset());
744
745 assert_eq!(0x6000, pt.get_partition(0).unwrap().len());
746 assert_eq!(0x1000, pt.get_partition(1).unwrap().len());
747 assert_eq!(0x100000, pt.get_partition(2).unwrap().len());
748
749 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
750 assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str());
751 assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str());
752
753 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
754 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
755 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
756
757 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
758 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
759 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
760 }
761
762 #[test]
763 fn read_ota() {
764 let pt = PartitionTable::new(OTA).unwrap();
765
766 assert_eq!(6, pt.len());
767
768 assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
769 assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
770 assert_eq!(1, pt.get_partition(2).unwrap().raw_type());
771 assert_eq!(0, pt.get_partition(3).unwrap().raw_type());
772 assert_eq!(0, pt.get_partition(4).unwrap().raw_type());
773 assert_eq!(0, pt.get_partition(5).unwrap().raw_type());
774
775 assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
776 assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype());
777 assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype());
778 assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype());
779 assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype());
780 assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype());
781
782 assert_eq!(
783 PartitionType::Data(DataPartitionSubType::Nvs),
784 pt.get_partition(0).unwrap().partition_type()
785 );
786 assert_eq!(
787 PartitionType::Data(DataPartitionSubType::Ota),
788 pt.get_partition(1).unwrap().partition_type()
789 );
790 assert_eq!(
791 PartitionType::Data(DataPartitionSubType::Phy),
792 pt.get_partition(2).unwrap().partition_type()
793 );
794 assert_eq!(
795 PartitionType::App(AppPartitionSubType::Factory),
796 pt.get_partition(3).unwrap().partition_type()
797 );
798 assert_eq!(
799 PartitionType::App(AppPartitionSubType::Ota0),
800 pt.get_partition(4).unwrap().partition_type()
801 );
802 assert_eq!(
803 PartitionType::App(AppPartitionSubType::Ota1),
804 pt.get_partition(5).unwrap().partition_type()
805 );
806
807 assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
808 assert_eq!(0xd000, pt.get_partition(1).unwrap().offset());
809 assert_eq!(0xf000, pt.get_partition(2).unwrap().offset());
810 assert_eq!(0x10000, pt.get_partition(3).unwrap().offset());
811 assert_eq!(0x110000, pt.get_partition(4).unwrap().offset());
812 assert_eq!(0x210000, pt.get_partition(5).unwrap().offset());
813
814 assert_eq!(0x4000, pt.get_partition(0).unwrap().len());
815 assert_eq!(0x2000, pt.get_partition(1).unwrap().len());
816 assert_eq!(0x1000, pt.get_partition(2).unwrap().len());
817 assert_eq!(0x100000, pt.get_partition(3).unwrap().len());
818 assert_eq!(0x100000, pt.get_partition(4).unwrap().len());
819 assert_eq!(0x100000, pt.get_partition(5).unwrap().len());
820
821 assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
822 assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str());
823 assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str());
824 assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str());
825 assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str());
826 assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str());
827
828 assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
829 assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
830 assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
831 assert_eq!(false, pt.get_partition(3).unwrap().is_read_only());
832 assert_eq!(false, pt.get_partition(4).unwrap().is_read_only());
833 assert_eq!(false, pt.get_partition(5).unwrap().is_read_only());
834
835 assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
836 assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
837 assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
838 assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted());
839 assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted());
840 assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted());
841 }
842
843 #[test]
844 fn empty_byte_array() {
845 let pt = PartitionTable::new(&[]).unwrap();
846
847 assert_eq!(0, pt.len());
848 assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds)));
849 }
850
851 #[test]
852 fn validation_fails_wo_hash() {
853 assert!(matches!(
854 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]),
855 Err(Error::Invalid)
856 ));
857 }
858
859 #[test]
860 fn validation_fails_wo_hash_max_entries() {
861 let mut data = [0u8; PARTITION_TABLE_MAX_LEN];
862 for i in 0..96 {
863 data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]);
864 }
865
866 assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid)));
867 }
868
869 #[test]
870 fn validation_succeeds_with_enough_entries() {
871 assert_eq!(
872 3,
873 PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4])
874 .unwrap()
875 .len()
876 );
877 }
878}
879
880#[cfg(test)]
881mod storage_tests {
882 use embedded_storage::{ReadStorage, Storage};
883
884 use super::*;
885
886 struct MockFlash {
887 data: [u8; 0x10000],
888 }
889
890 impl MockFlash {
891 fn new() -> Self {
892 let mut data = [23u8; 0x10000];
893 data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN as usize]
894 .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin"));
895 Self { data }
896 }
897 }
898
899 impl embedded_storage::Storage for MockFlash {
900 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
901 self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes);
902 Ok(())
903 }
904 }
905
906 impl embedded_storage::ReadStorage for MockFlash {
907 type Error = crate::partitions::Error;
908 fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> {
909 let l = buffer.len();
910 buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]);
911 Ok(())
912 }
913
914 fn capacity(&self) -> usize {
915 unimplemented!()
916 }
917 }
918
919 #[test]
920 fn can_read_write_all_of_nvs() {
921 let mut storage = MockFlash::new();
922
923 let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
924 let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
925
926 let nvs = pt
927 .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
928 .unwrap()
929 .unwrap();
930 let mut nvs_partition = nvs.as_embedded_storage(&mut storage);
931 assert_eq!(nvs_partition.raw.offset(), 36864);
932
933 assert_eq!(nvs_partition.capacity(), 24576);
934
935 let mut buffer = [0u8; 24576];
936 nvs_partition.read(0, &mut buffer).unwrap();
937 assert!(buffer.iter().all(|v| *v == 23));
938 buffer.fill(42);
939 nvs_partition.write(0, &mut buffer).unwrap();
940 let mut buffer = [0u8; 24576];
941 nvs_partition.read(0, &mut buffer).unwrap();
942 assert!(buffer.iter().all(|v| *v == 42));
943 }
944
945 #[test]
946 fn cannot_read_write_more_than_partition_size() {
947 let mut storage = MockFlash::new();
948
949 let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
950 let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
951
952 let nvs = pt
953 .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
954 .unwrap()
955 .unwrap();
956 let mut nvs_partition = nvs.as_embedded_storage(&mut storage);
957 assert_eq!(nvs_partition.raw.offset(), 36864);
958
959 assert_eq!(nvs_partition.capacity(), 24576);
960
961 let mut buffer = [0u8; 24577];
962 assert!(nvs_partition.read(0, &mut buffer) == Err(Error::OutOfBounds));
963 }
964}