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
10/// Maximum length of a partition table.
11pub 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/// 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))]
165#[non_exhaustive]
166pub enum Error {
167    /// The partition table is invalid or doesn't contain a needed partition.
168    Invalid,
169    /// An operation tries to access data that is out of bounds.
170    OutOfBounds,
171    /// An error which originates from the embedded-storage implementation.
172    StorageError,
173    /// The partition is write protected.
174    WriteProtected,
175    /// The partition is invalid.
176    InvalidPartition {
177        expected_size: usize,
178        expected_type: PartitionType,
179    },
180    /// Invalid tate
181    InvalidState,
182    /// The given argument is invalid.
183    InvalidArgument,
184}
185
186impl core::error::Error for Error {}
187
188/// A partition table.
189#[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    /// Number of partitions contained in the partition table.
275    pub fn len(&self) -> usize {
276        self.entries
277    }
278
279    /// Checks if there are no recognized partitions.
280    pub fn is_empty(&self) -> bool {
281        self.entries == 0
282    }
283
284    /// Get a partition entry.
285    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    /// Get the first partition matching the given partition type.
293    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    /// Returns an iterator over the partitions.
304    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    /// Get the currently booted partition.
310    pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
311        Err(Error::Invalid)
312    }
313
314    #[cfg(not(feature = "std"))]
315    /// Get the currently booted partition.
316    pub fn booted_partition(&self) -> Result<Option<PartitionEntry<'a>>, Error> {
317        // Read entry 0 from MMU to know which partition is mapped
318        //
319        // See <https://github.com/espressif/esp-idf/blob/758939caecb16e5542b3adfba0bc85025517db45/components/hal/mmu_hal.c#L124>
320        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                // Revisit this once we support XiP from PSRAM for ESP32-S3
331                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/// A partition type including the sub-type.
358#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
359#[cfg_attr(feature = "defmt", derive(defmt::Format))]
360pub enum PartitionType {
361    /// Application.
362    App(AppPartitionSubType),
363    /// Data.
364    Data(DataPartitionSubType),
365    /// Bootloader.
366    Bootloader(BootloaderPartitionSubType),
367    /// Partition table.
368    PartitionTable(PartitionTablePartitionSubType),
369}
370
371/// A partition type
372#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
373#[cfg_attr(feature = "defmt", derive(defmt::Format))]
374#[repr(u8)]
375pub enum RawPartitionType {
376    /// Application.
377    App = 0,
378    /// Data.
379    Data,
380    /// Bootloader.
381    Bootloader,
382    /// Partition table.
383    PartitionTable,
384}
385
386/// Sub-types of an application partition.
387#[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 image
392    Factory = 0,
393    /// OTA slot 0
394    Ota0    = OTA_SUBTYPE_OFFSET,
395    /// OTA slot 1
396    Ota1,
397    /// OTA slot 2
398    Ota2,
399    /// OTA slot 3
400    Ota3,
401    /// OTA slot 4
402    Ota4,
403    /// OTA slot 5
404    Ota5,
405    /// OTA slot 6
406    Ota6,
407    /// OTA slot 7
408    Ota7,
409    /// OTA slot 8
410    Ota8,
411    /// OTA slot 9
412    Ota9,
413    /// OTA slot 10
414    Ota10,
415    /// OTA slot 11
416    Ota11,
417    /// OTA slot 12
418    Ota12,
419    /// OTA slot 13
420    Ota13,
421    /// OTA slot 14
422    Ota14,
423    /// OTA slot 15
424    Ota15,
425    /// Test image
426    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/// Sub-types of the data partition type.
451#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
452#[cfg_attr(feature = "defmt", derive(defmt::Format))]
453#[repr(u8)]
454pub enum DataPartitionSubType {
455    /// Data partition which stores information about the currently selected OTA
456    /// app slot. This partition should be 0x2000 bytes in size. Refer to
457    /// the OTA documentation for more details.
458    Ota      = 0,
459    /// Phy is for storing PHY initialization data. This allows PHY to be
460    /// configured per-device, instead of in firmware.
461    Phy,
462    /// Used for Non-Volatile Storage (NVS).
463    Nvs,
464    /// Used for storing core dumps while using a custom partition table
465    Coredump,
466    /// NvsKeys is used for the NVS key partition. (NVS).
467    NvsKeys,
468    /// Used for emulating eFuse bits using Virtual eFuses.
469    EfuseEm,
470    /// Implicitly used for data partitions with unspecified (empty) subtype,
471    /// but it is possible to explicitly mark them as undefined as well.
472    Undefined,
473    /// FAT Filesystem Support.
474    Fat      = 0x81,
475    /// SPIFFS Filesystem.
476    Spiffs   = 0x82,
477    ///  LittleFS filesystem.
478    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/// Sub-type of the bootloader partition type.
490#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
491#[cfg_attr(feature = "defmt", derive(defmt::Format))]
492#[repr(u8)]
493pub enum BootloaderPartitionSubType {
494    /// It is the so-called 2nd stage bootloader.
495    Primary = 0,
496    /// It is a temporary bootloader partition used by the bootloader OTA update
497    /// functionality for downloading a new image.
498    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/// Sub-type of the partition table type.
510#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
511#[cfg_attr(feature = "defmt", derive(defmt::Format))]
512#[repr(u8)]
513pub enum PartitionTablePartitionSubType {
514    /// It is the primary partition table.
515    Primary = 0,
516    /// It is a temporary partition table partition used by the partition table
517    /// OTA update functionality for downloading a new image.
518    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
529/// Read the partition table.
530///
531/// Pass an implementation of [embedded_storage::Storage] which can read from
532/// the whole flash and provide storage to read the partition table into.
533pub 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/// A flash region is a "view" into the partition.
545///
546/// It allows to read and write to the partition without the need to account for
547/// the partition offset.
548#[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    /// Returns the size of the partition in bytes.
557    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}