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_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    /// Number of partitions contained in the partition table.
277    pub fn len(&self) -> usize {
278        self.entries
279    }
280
281    /// Checks if there are no recognized partitions.
282    pub fn is_empty(&self) -> bool {
283        self.entries == 0
284    }
285
286    /// Get a partition entry.
287    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    /// Get the first partition matching the given partition type.
295    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/// A partition type including the sub-type.
307#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
308#[cfg_attr(feature = "defmt", derive(defmt::Format))]
309pub enum PartitionType {
310    /// Application.
311    App(AppPartitionSubType),
312    /// Data.
313    Data(DataPartitionSubType),
314    /// Bootloader.
315    Bootloader(BootloaderPartitionSubType),
316    /// Partition table.
317    PartitionTable(PartitionTablePartitionSubType),
318}
319
320/// A partition type
321#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
322#[cfg_attr(feature = "defmt", derive(defmt::Format))]
323#[repr(u8)]
324pub enum RawPartitionType {
325    /// Application.
326    App = 0,
327    /// Data.
328    Data,
329    /// Bootloader.
330    Bootloader,
331    /// Partition table.
332    PartitionTable,
333}
334
335/// Sub-types of an application partition.
336#[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 image
341    Factory = 0,
342    /// OTA slot 0
343    Ota0    = 0x10,
344    /// OTA slot 1
345    Ota1,
346    /// OTA slot 2
347    Ota2,
348    /// OTA slot 3
349    Ota3,
350    /// OTA slot 4
351    Ota4,
352    /// OTA slot 5
353    Ota5,
354    /// OTA slot 6
355    Ota6,
356    /// OTA slot 7
357    Ota7,
358    /// OTA slot 8
359    Ota8,
360    /// OTA slot 9
361    Ota9,
362    /// OTA slot 10
363    Ota10,
364    /// OTA slot 11
365    Ota11,
366    /// OTA slot 12
367    Ota12,
368    /// OTA slot 13
369    Ota13,
370    /// OTA slot 14
371    Ota14,
372    /// OTA slot 15
373    Ota15,
374    /// Test image
375    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/// Sub-types of the data partition type.
387#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
388#[cfg_attr(feature = "defmt", derive(defmt::Format))]
389#[repr(u8)]
390pub enum DataPartitionSubType {
391    /// Data partition which stores information about the currently selected OTA
392    /// app slot. This partition should be 0x2000 bytes in size. Refer to
393    /// the OTA documentation for more details.
394    Ota      = 0,
395    /// Phy is for storing PHY initialization data. This allows PHY to be
396    /// configured per-device, instead of in firmware.
397    Phy,
398    /// Used for Non-Volatile Storage (NVS).
399    Nvs,
400    /// Used for storing core dumps while using a custom partition table
401    Coredump,
402    /// NvsKeys is used for the NVS key partition. (NVS).
403    NvsKeys,
404    /// Used for emulating eFuse bits using Virtual eFuses.
405    EfuseEm,
406    /// Implicitly used for data partitions with unspecified (empty) subtype,
407    /// but it is possible to explicitly mark them as undefined as well.
408    Undefined,
409    /// FAT Filesystem Support.
410    Fat      = 0x81,
411    /// SPIFFS Filesystem.
412    Spiffs   = 0x82,
413    ///  LittleFS filesystem.
414    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/// Sub-type of the bootloader partition type.
426#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
427#[cfg_attr(feature = "defmt", derive(defmt::Format))]
428#[repr(u8)]
429pub enum BootloaderPartitionSubType {
430    /// It is the so-called 2nd stage bootloader.
431    Primary = 0,
432    /// It is a temporary bootloader partition used by the bootloader OTA update
433    /// functionality for downloading a new image.
434    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/// Sub-type of the partition table type.
446#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
447#[cfg_attr(feature = "defmt", derive(defmt::Format))]
448#[repr(u8)]
449pub enum PartitionTablePartitionSubType {
450    /// It is the primary partition table.
451    Primary = 0,
452    /// It is a temporary partition table partition used by the partition table
453    /// OTA update functionality for downloading a new image.
454    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
465/// Read the partition table.
466///
467/// Pass an implementation of [embedded_storage::Storage] which can read from
468/// the whole flash and provide storage to read the partition table into.
469pub 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/// A flash region is a "view" into the partition.
481///
482/// It allows to read and write to the partition without the need to account for
483/// the partition offset.
484#[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}