esp_bootloader_esp_idf/
partitions.rs

1//! # Partition Table Support
2//!
3//! ## Overview
4//!
5//! This module allows reading the partition table and conveniently
6//! writing/reading partition contents.
7//!
8//! For more information see <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#built-in-partition-tables>
9
10use embedded_storage::Region;
11
12/// Maximum length of a partition table.
13pub 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
22/// Represents a single partition entry.
23pub 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    /// The magic value of the entry.
33    pub fn magic(&self) -> u16 {
34        u16::from_le_bytes(unwrap!(self.binary[..2].try_into()))
35    }
36
37    /// The partition type in raw representation.
38    pub fn raw_type(&self) -> u8 {
39        self.binary[2]
40    }
41
42    /// The partition sub-type in raw representation.
43    pub fn raw_subtype(&self) -> u8 {
44        self.binary[3]
45    }
46
47    /// Offset of the partition on flash.
48    pub fn offset(&self) -> u32 {
49        u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into()))
50    }
51
52    /// Length of the partition in bytes.
53    pub fn len(&self) -> u32 {
54        u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into()))
55    }
56
57    /// Checks for a zero-length partition.
58    pub fn is_empty(&self) -> bool {
59        self.len() == 0
60    }
61
62    /// The label of the partition.
63    pub fn label(&self) -> &'a [u8] {
64        &self.binary[12..][..16]
65    }
66
67    /// The label of the partition as `&str`.
68    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    /// Raw flags of this partition. You probably want to use
80    /// [Self::is_read_only] and [Self::is_encrypted] instead.
81    pub fn flags(&self) -> u32 {
82        u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into()))
83    }
84
85    /// If the partition is read only.
86    pub fn is_read_only(&self) -> bool {
87        self.flags() & 0b01 != 0
88    }
89
90    /// If the partition is encrypted.
91    pub fn is_encrypted(&self) -> bool {
92        self.flags() & 0b10 != 0
93    }
94
95    /// The partition type (type and sub-type).
96    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    /// Provides a "view" into the partition allowing to read/write the
107    /// partition contents by using the given [embedded_storage::Storage] and/or
108    /// [embedded_storage::ReadStorage] implementation.
109    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/// Errors which can be returned.
163#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::Display)]
164#[cfg_attr(feature = "defmt", derive(defmt::Format))]
165pub enum Error {
166    /// The partition table is invalid.
167    Invalid,
168    /// An operation tries to access data that is out of bounds.
169    OutOfBounds,
170    /// An error which originates from the embedded-storage implementation.
171    StorageError,
172    /// The partition is write protected.
173    WriteProtected,
174    /// The partition is invalid.
175    InvalidPartition {
176        expected_size: usize,
177        expected_type: PartitionType,
178    },
179    /// Invalid tate
180    InvalidState,
181}
182
183impl core::error::Error for Error {}
184
185/// A partition table.
186pub 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        // we checked binary before
209        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 = 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.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.entries {
233                            return Err(Error::Invalid);
234                        }
235                    }
236                }
237            };
238
239            use md5::Digest;
240            let mut hasher = md5::Md5::new();
241
242            for i in 0..index {
243                hasher.update(raw.binary[i]);
244            }
245            let calculated_hash = hasher.finalize();
246
247            if *calculated_hash != *hash {
248                return Err(Error::Invalid);
249            }
250        }
251
252        let entries = {
253            let mut i = 0;
254            loop {
255                if let Ok(entry) = raw.get_partition(i) {
256                    if entry.magic() != ENTRY_MAGIC {
257                        break;
258                    }
259
260                    i += 1;
261
262                    if i == raw.entries {
263                        break;
264                    }
265                } else {
266                    return Err(Error::Invalid);
267                }
268            }
269            i
270        };
271
272        raw.entries = entries;
273
274        Ok(raw)
275    }
276
277    /// Number of partitions contained in the partition table.
278    pub fn len(&self) -> usize {
279        self.entries
280    }
281
282    /// Checks if there are no recognized partitions.
283    pub fn is_empty(&self) -> bool {
284        self.entries == 0
285    }
286
287    /// Get a partition entry.
288    pub fn get_partition(&self, index: usize) -> Result<PartitionEntry<'a>, Error> {
289        if index >= self.entries {
290            return Err(Error::OutOfBounds);
291        }
292        Ok(PartitionEntry::new(&self.binary[index]))
293    }
294
295    /// Get the first partition matching the given partition type.
296    pub fn find_partition(&self, pt: PartitionType) -> Result<Option<PartitionEntry<'a>>, Error> {
297        for i in 0..self.entries {
298            let entry = self.get_partition(i)?;
299            if entry.partition_type() == pt {
300                return Ok(Some(entry));
301            }
302        }
303        Ok(None)
304    }
305}
306
307/// A partition type including the sub-type.
308#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
309#[cfg_attr(feature = "defmt", derive(defmt::Format))]
310pub enum PartitionType {
311    /// Application.
312    App(AppPartitionSubType),
313    /// Data.
314    Data(DataPartitionSubType),
315    /// Bootloader.
316    Bootloader(BootloaderPartitionSubType),
317    /// Partition table.
318    PartitionTable(PartitionTablePartitionSubType),
319}
320
321/// A partition type
322#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
323#[cfg_attr(feature = "defmt", derive(defmt::Format))]
324#[repr(u8)]
325pub enum RawPartitionType {
326    /// Application.
327    App = 0,
328    /// Data.
329    Data,
330    /// Bootloader.
331    Bootloader,
332    /// Partition table.
333    PartitionTable,
334}
335
336/// Sub-types of an application partition.
337#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
338#[cfg_attr(feature = "defmt", derive(defmt::Format))]
339#[repr(u8)]
340pub enum AppPartitionSubType {
341    /// Factory image
342    Factory = 0,
343    /// OTA slot 0
344    Ota0    = 0x10,
345    /// OTA slot 1
346    Ota1,
347    /// OTA slot 2
348    Ota2,
349    /// OTA slot 3
350    Ota3,
351    /// OTA slot 4
352    Ota4,
353    /// OTA slot 5
354    Ota5,
355    /// OTA slot 6
356    Ota6,
357    /// OTA slot 7
358    Ota7,
359    /// OTA slot 8
360    Ota8,
361    /// OTA slot 9
362    Ota9,
363    /// OTA slot 10
364    Ota10,
365    /// OTA slot 11
366    Ota11,
367    /// OTA slot 12
368    Ota12,
369    /// OTA slot 13
370    Ota13,
371    /// OTA slot 14
372    Ota14,
373    /// OTA slot 15
374    Ota15,
375    /// Test image
376    Test,
377}
378
379impl TryFrom<u8> for AppPartitionSubType {
380    type Error = Error;
381
382    fn try_from(value: u8) -> Result<Self, Self::Error> {
383        AppPartitionSubType::from_repr(value).ok_or(Error::Invalid)
384    }
385}
386
387/// Sub-types of the data partition type.
388#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
389#[cfg_attr(feature = "defmt", derive(defmt::Format))]
390#[repr(u8)]
391pub enum DataPartitionSubType {
392    /// Data partition which stores information about the currently selected OTA
393    /// app slot. This partition should be 0x2000 bytes in size. Refer to
394    /// the OTA documentation for more details.
395    Ota      = 0,
396    /// Phy is for storing PHY initialization data. This allows PHY to be
397    /// configured per-device, instead of in firmware.
398    Phy,
399    /// Used for Non-Volatile Storage (NVS).
400    Nvs,
401    /// Used for storing core dumps while using a custom partition table
402    Coredump,
403    /// NvsKeys is used for the NVS key partition. (NVS).
404    NvsKeys,
405    /// Used for emulating eFuse bits using Virtual eFuses.
406    EfuseEm,
407    /// Implicitly used for data partitions with unspecified (empty) subtype,
408    /// but it is possible to explicitly mark them as undefined as well.
409    Undefined,
410    /// FAT Filesystem Support.
411    Fat      = 0x81,
412    /// SPIFFS Filesystem.
413    Spiffs   = 0x82,
414    ///  LittleFS filesystem.
415    LittleFs = 0x83,
416}
417
418impl TryFrom<u8> for DataPartitionSubType {
419    type Error = Error;
420
421    fn try_from(value: u8) -> Result<Self, Self::Error> {
422        DataPartitionSubType::from_repr(value).ok_or(Error::Invalid)
423    }
424}
425
426/// Sub-type of the bootloader partition type.
427#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
428#[cfg_attr(feature = "defmt", derive(defmt::Format))]
429#[repr(u8)]
430pub enum BootloaderPartitionSubType {
431    /// It is the so-called 2nd stage bootloader.
432    Primary = 0,
433    /// It is a temporary bootloader partition used by the bootloader OTA update
434    /// functionality for downloading a new image.
435    Ota     = 1,
436}
437
438impl TryFrom<u8> for BootloaderPartitionSubType {
439    type Error = Error;
440
441    fn try_from(value: u8) -> Result<Self, Self::Error> {
442        BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid)
443    }
444}
445
446/// Sub-type of the partition table type.
447#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
448#[cfg_attr(feature = "defmt", derive(defmt::Format))]
449#[repr(u8)]
450pub enum PartitionTablePartitionSubType {
451    /// It is the primary partition table.
452    Primary = 0,
453    /// It is a temporary partition table partition used by the partition table
454    /// OTA update functionality for downloading a new image.
455    Ota     = 1,
456}
457
458impl TryFrom<u8> for PartitionTablePartitionSubType {
459    type Error = Error;
460
461    fn try_from(value: u8) -> Result<Self, Self::Error> {
462        PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid)
463    }
464}
465
466/// Read the partition table.
467///
468/// Pass an implementation of [embedded_storage::Storage] which can read from
469/// the whole flash and provide storage to read the partition table into.
470pub fn read_partition_table<'a>(
471    flash: &mut impl embedded_storage::Storage,
472    storage: &'a mut [u8],
473) -> Result<PartitionTable<'a>, Error> {
474    flash
475        .read(PARTITION_TABLE_OFFSET, storage)
476        .map_err(|_e| Error::StorageError)?;
477
478    PartitionTable::new(storage)
479}
480
481/// A flash region is a "view" into the partition.
482///
483/// It allows to read and write to the partition without the need to account for
484/// the partition offset.
485#[derive(Debug)]
486#[cfg_attr(feature = "defmt", derive(defmt::Format))]
487pub struct FlashRegion<'a, F> {
488    pub(crate) raw: &'a PartitionEntry<'a>,
489    pub(crate) flash: &'a mut F,
490}
491
492impl<F> embedded_storage::Region for FlashRegion<'_, F> {
493    fn contains(&self, address: u32) -> bool {
494        address >= self.raw.offset() && address < self.raw.offset() + self.raw.len()
495    }
496}
497
498impl<F> embedded_storage::ReadStorage for FlashRegion<'_, F>
499where
500    F: embedded_storage::ReadStorage,
501{
502    type Error = Error;
503
504    fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
505        let address = offset + self.raw.offset();
506
507        if !self.contains(address) {
508            return Err(Error::OutOfBounds);
509        }
510
511        if !self.contains(address + bytes.len() as u32) {
512            return Err(Error::OutOfBounds);
513        }
514
515        self.flash
516            .read(address, bytes)
517            .map_err(|_e| Error::StorageError)
518    }
519
520    fn capacity(&self) -> usize {
521        self.raw.len() as _
522    }
523}
524
525impl<F> embedded_storage::Storage for FlashRegion<'_, F>
526where
527    F: embedded_storage::Storage,
528{
529    fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
530        let address = offset + self.raw.offset();
531
532        if self.raw.is_read_only() {
533            return Err(Error::WriteProtected);
534        }
535
536        if !self.contains(address) {
537            return Err(Error::OutOfBounds);
538        }
539
540        if !self.contains(address + bytes.len() as u32) {
541            return Err(Error::OutOfBounds);
542        }
543
544        self.flash
545            .write(address, bytes)
546            .map_err(|_e| Error::StorageError)
547    }
548}
549
550impl embedded_storage::nor_flash::NorFlashError for Error {
551    fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind {
552        match self {
553            Error::OutOfBounds => embedded_storage::nor_flash::NorFlashErrorKind::OutOfBounds,
554            _ => embedded_storage::nor_flash::NorFlashErrorKind::Other,
555        }
556    }
557}
558
559impl<F> embedded_storage::nor_flash::ErrorType for FlashRegion<'_, F> {
560    type Error = Error;
561}
562
563impl<F> embedded_storage::nor_flash::ReadNorFlash for FlashRegion<'_, F>
564where
565    F: embedded_storage::nor_flash::ReadNorFlash,
566{
567    const READ_SIZE: usize = F::READ_SIZE;
568
569    fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
570        let address = offset + self.raw.offset();
571
572        if !self.contains(address) {
573            return Err(Error::OutOfBounds);
574        }
575
576        if !self.contains(address + bytes.len() as u32) {
577            return Err(Error::OutOfBounds);
578        }
579
580        self.flash
581            .read(address, bytes)
582            .map_err(|_e| Error::StorageError)
583    }
584
585    fn capacity(&self) -> usize {
586        self.flash.capacity()
587    }
588}
589
590impl<F> embedded_storage::nor_flash::NorFlash for FlashRegion<'_, F>
591where
592    F: embedded_storage::nor_flash::NorFlash,
593{
594    const WRITE_SIZE: usize = F::WRITE_SIZE;
595
596    const ERASE_SIZE: usize = F::ERASE_SIZE;
597
598    fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
599        let address_from = from + self.raw.offset();
600        let address_to = to + self.raw.offset();
601
602        if self.raw.is_read_only() {
603            return Err(Error::WriteProtected);
604        }
605
606        if !self.contains(address_from) {
607            return Err(Error::OutOfBounds);
608        }
609
610        if !self.contains(address_to) {
611            return Err(Error::OutOfBounds);
612        }
613
614        self.flash
615            .erase(address_from, address_to)
616            .map_err(|_e| Error::StorageError)
617    }
618
619    fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
620        let address = offset + self.raw.offset();
621
622        if self.raw.is_read_only() {
623            return Err(Error::WriteProtected);
624        }
625
626        if !self.contains(address) {
627            return Err(Error::OutOfBounds);
628        }
629
630        if !self.contains(address + bytes.len() as u32) {
631            return Err(Error::OutOfBounds);
632        }
633
634        self.flash
635            .write(address, bytes)
636            .map_err(|_e| Error::StorageError)
637    }
638}
639
640impl<F> embedded_storage::nor_flash::MultiwriteNorFlash for FlashRegion<'_, F> where
641    F: embedded_storage::nor_flash::MultiwriteNorFlash
642{
643}
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648
649    static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin");
650    static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin");
651
652    #[test]
653    fn read_simple() {
654        let pt = PartitionTable::new(SIMPLE).unwrap();
655
656        assert_eq!(3, pt.len());
657
658        assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
659        assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
660        assert_eq!(0, pt.get_partition(2).unwrap().raw_type());
661
662        assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
663        assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype());
664        assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype());
665
666        assert_eq!(
667            PartitionType::Data(DataPartitionSubType::Nvs),
668            pt.get_partition(0).unwrap().partition_type()
669        );
670        assert_eq!(
671            PartitionType::Data(DataPartitionSubType::Phy),
672            pt.get_partition(1).unwrap().partition_type()
673        );
674        assert_eq!(
675            PartitionType::App(AppPartitionSubType::Factory),
676            pt.get_partition(2).unwrap().partition_type()
677        );
678
679        assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
680        assert_eq!(0xf000, pt.get_partition(1).unwrap().offset());
681        assert_eq!(0x10000, pt.get_partition(2).unwrap().offset());
682
683        assert_eq!(0x6000, pt.get_partition(0).unwrap().len());
684        assert_eq!(0x1000, pt.get_partition(1).unwrap().len());
685        assert_eq!(0x100000, pt.get_partition(2).unwrap().len());
686
687        assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
688        assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str());
689        assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str());
690
691        assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
692        assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
693        assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
694
695        assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
696        assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
697        assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
698    }
699
700    #[test]
701    fn read_ota() {
702        let pt = PartitionTable::new(OTA).unwrap();
703
704        assert_eq!(6, pt.len());
705
706        assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
707        assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
708        assert_eq!(1, pt.get_partition(2).unwrap().raw_type());
709        assert_eq!(0, pt.get_partition(3).unwrap().raw_type());
710        assert_eq!(0, pt.get_partition(4).unwrap().raw_type());
711        assert_eq!(0, pt.get_partition(5).unwrap().raw_type());
712
713        assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
714        assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype());
715        assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype());
716        assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype());
717        assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype());
718        assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype());
719
720        assert_eq!(
721            PartitionType::Data(DataPartitionSubType::Nvs),
722            pt.get_partition(0).unwrap().partition_type()
723        );
724        assert_eq!(
725            PartitionType::Data(DataPartitionSubType::Ota),
726            pt.get_partition(1).unwrap().partition_type()
727        );
728        assert_eq!(
729            PartitionType::Data(DataPartitionSubType::Phy),
730            pt.get_partition(2).unwrap().partition_type()
731        );
732        assert_eq!(
733            PartitionType::App(AppPartitionSubType::Factory),
734            pt.get_partition(3).unwrap().partition_type()
735        );
736        assert_eq!(
737            PartitionType::App(AppPartitionSubType::Ota0),
738            pt.get_partition(4).unwrap().partition_type()
739        );
740        assert_eq!(
741            PartitionType::App(AppPartitionSubType::Ota1),
742            pt.get_partition(5).unwrap().partition_type()
743        );
744
745        assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
746        assert_eq!(0xd000, pt.get_partition(1).unwrap().offset());
747        assert_eq!(0xf000, pt.get_partition(2).unwrap().offset());
748        assert_eq!(0x10000, pt.get_partition(3).unwrap().offset());
749        assert_eq!(0x110000, pt.get_partition(4).unwrap().offset());
750        assert_eq!(0x210000, pt.get_partition(5).unwrap().offset());
751
752        assert_eq!(0x4000, pt.get_partition(0).unwrap().len());
753        assert_eq!(0x2000, pt.get_partition(1).unwrap().len());
754        assert_eq!(0x1000, pt.get_partition(2).unwrap().len());
755        assert_eq!(0x100000, pt.get_partition(3).unwrap().len());
756        assert_eq!(0x100000, pt.get_partition(4).unwrap().len());
757        assert_eq!(0x100000, pt.get_partition(5).unwrap().len());
758
759        assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
760        assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str());
761        assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str());
762        assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str());
763        assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str());
764        assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str());
765
766        assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
767        assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
768        assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
769        assert_eq!(false, pt.get_partition(3).unwrap().is_read_only());
770        assert_eq!(false, pt.get_partition(4).unwrap().is_read_only());
771        assert_eq!(false, pt.get_partition(5).unwrap().is_read_only());
772
773        assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
774        assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
775        assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
776        assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted());
777        assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted());
778        assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted());
779    }
780
781    #[test]
782    fn empty_byte_array() {
783        let pt = PartitionTable::new(&[]).unwrap();
784
785        assert_eq!(0, pt.len());
786        assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds)));
787    }
788
789    #[test]
790    fn validation_fails_wo_hash() {
791        assert!(matches!(
792            PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]),
793            Err(Error::Invalid)
794        ));
795    }
796
797    #[test]
798    fn validation_fails_wo_hash_max_entries() {
799        let mut data = [0u8; PARTITION_TABLE_MAX_LEN];
800        for i in 0..96 {
801            data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]);
802        }
803
804        assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid)));
805    }
806
807    #[test]
808    fn validation_succeeds_with_enough_entries() {
809        assert_eq!(
810            3,
811            PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4])
812                .unwrap()
813                .len()
814        );
815    }
816}