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