Skip to main content

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;
18#[cfg(feature = "validation")]
19const MD5_MAGIC: u16 = 0xebeb;
20
21const OTA_SUBTYPE_OFFSET: u8 = 0x10;
22
23use crate::flash::FlashAccess;
24pub use crate::flash::FlashStorage;
25
26/// Represents a single partition entry.
27#[derive(Clone, Copy)]
28pub struct PartitionEntry {
29    pub(crate) binary: [u8; RAW_ENTRY_LEN],
30}
31
32impl PartitionEntry {
33    fn new(binary: &[u8; RAW_ENTRY_LEN]) -> Self {
34        Self { binary: *binary }
35    }
36
37    /// The magic value of the entry.
38    pub fn magic(&self) -> u16 {
39        u16::from_le_bytes(unwrap!(self.binary[..2].try_into()))
40    }
41
42    /// The partition type in raw representation.
43    pub fn raw_type(&self) -> u8 {
44        self.binary[2]
45    }
46
47    /// The partition sub-type in raw representation.
48    pub fn raw_subtype(&self) -> u8 {
49        self.binary[3]
50    }
51
52    /// Offset of the partition on flash.
53    pub fn offset(&self) -> u32 {
54        u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into()))
55    }
56
57    /// Length of the partition in bytes.
58    pub fn len(&self) -> u32 {
59        u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into()))
60    }
61
62    /// Checks for a zero-length partition.
63    pub fn is_empty(&self) -> bool {
64        self.len() == 0
65    }
66
67    /// The label of the partition.
68    pub fn label(&self) -> &[u8] {
69        &self.binary[12..][..16]
70    }
71
72    /// The label of the partition as `&str`.
73    pub fn label_as_str(&self) -> &str {
74        let array = self.label();
75        let len = array
76            .iter()
77            .position(|b| *b == 0 || *b == 0xff)
78            .unwrap_or(array.len());
79        unsafe {
80            core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len))
81        }
82    }
83
84    /// Raw flags of this partition. You probably want to use
85    /// [Self::is_read_only] and [Self::is_encrypted] instead.
86    pub fn flags(&self) -> u32 {
87        u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into()))
88    }
89
90    /// If the partition is read only.
91    pub fn is_read_only(&self) -> bool {
92        self.flags() & 0b01 != 0
93    }
94
95    /// If the partition is encrypted.
96    ///
97    /// This is the flag from the partition table.
98    /// If flash encryption is enabled certain partition types are encrypted
99    /// regardless of this.
100    pub fn is_encrypted(&self) -> bool {
101        self.flags() & 0b10 != 0
102    }
103
104    /// Like [PartitionEntry::is_encrypted] but also takes into account:
105    /// - is flash encryption enabled, otherwise this will always return false
106    /// - certain partition types are always encrypted, no matter what the partition table says
107    pub(crate) fn is_effectively_encrypted(&self) -> bool {
108        #[cfg(feature = "std")]
109        let enabled = false;
110
111        #[cfg(not(feature = "std"))]
112        let enabled = esp_storage::flash_encryption();
113
114        enabled
115            && (self.is_encrypted()
116                || matches!(self.partition_type(), PartitionType::App(_))
117                || matches!(self.partition_type(), PartitionType::PartitionTable(_))
118                || matches!(
119                    self.partition_type(),
120                    PartitionType::Data(DataPartitionSubType::NvsKeys)
121                )
122                || matches!(
123                    self.partition_type(),
124                    PartitionType::Data(DataPartitionSubType::Ota)
125                ))
126    }
127
128    /// The partition type (type and sub-type).
129    pub fn partition_type(&self) -> PartitionType {
130        match self.raw_type() {
131            0 => PartitionType::App(unwrap!(self.raw_subtype().try_into())),
132            1 => PartitionType::Data(unwrap!(self.raw_subtype().try_into())),
133            2 => PartitionType::Bootloader(unwrap!(self.raw_subtype().try_into())),
134            3 => PartitionType::PartitionTable(unwrap!(self.raw_subtype().try_into())),
135            _ => unreachable!(),
136        }
137    }
138
139    /// Provides a "view" into the partition allowing to read/write the
140    /// partition contents using the given [`FlashStorage`].
141    pub fn as_flash_region<'a, 'd>(self, flash: &'a mut FlashStorage<'d>) -> FlashRegion<'a, 'd> {
142        FlashRegion {
143            offset: self.offset(),
144            len: self.len(),
145            partition_type: self.partition_type(),
146            read_only: self.is_read_only(),
147            encrypted: self.is_effectively_encrypted(),
148            flash,
149        }
150    }
151
152    /// Calculate the SHA-256 digest of this partition.
153    ///
154    /// - App / bootloader with appended hash: return that digest after verifying it
155    /// - App / bootloader without appended hash: hash the image (not the whole partition)
156    /// - Other types: hash the entire partition
157    ///
158    /// For app images this is the **validation hash** (shown by
159    /// `esptool.py image-info`), not the ELF file SHA-256 stored in
160    /// [`crate::EspAppDesc`].
161    pub fn sha256(&self, flash: &mut FlashStorage<'_>) -> Result<[u8; 32], Error> {
162        if self.is_empty() {
163            return Err(Error::InvalidArgument);
164        }
165
166        let address = self.offset();
167        let encrypted = self.is_effectively_encrypted();
168        let mut size = self.len();
169
170        if matches!(
171            self.partition_type(),
172            PartitionType::App(_) | PartitionType::Bootloader(_)
173        ) {
174            let data = get_image_metadata(flash, address, size, encrypted)?;
175            if data.hash_appended {
176                let calc = sha256_flash_contents(
177                    flash,
178                    address,
179                    data.image_len - PARTITION_HASH_LEN as u32,
180                    encrypted,
181                )?;
182                if calc != data.image_digest {
183                    return Err(Error::InvalidImage);
184                }
185                return Ok(data.image_digest);
186            }
187            size = data.image_len;
188        }
189
190        sha256_flash_contents(flash, address, size, encrypted)
191    }
192}
193
194impl core::fmt::Debug for PartitionEntry {
195    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196        f.debug_struct("PartitionEntry")
197            .field("magic", &self.magic())
198            .field("raw_type", &self.raw_type())
199            .field("raw_subtype", &self.raw_subtype())
200            .field("offset", &self.offset())
201            .field("len", &self.len())
202            .field("label", &self.label_as_str())
203            .field("flags", &self.flags())
204            .field("is_read_only", &self.is_read_only())
205            .field("is_encrypted", &self.is_encrypted())
206            .finish()
207    }
208}
209
210#[cfg(feature = "defmt")]
211impl defmt::Format for PartitionEntry {
212    fn format(&self, fmt: defmt::Formatter) {
213        defmt::write!(
214            fmt,
215            "PartitionEntry (\
216            magic = {}, \
217            raw_type = {}, \
218            raw_subtype = {}, \
219            offset = {}, \
220            len = {}, \
221            label = {}, \
222            flags = {}, \
223            is_read_only = {}, \
224            is_encrypted = {}\
225            )",
226            self.magic(),
227            self.raw_type(),
228            self.raw_subtype(),
229            self.offset(),
230            self.len(),
231            self.label_as_str(),
232            self.flags(),
233            self.is_read_only(),
234            self.is_encrypted()
235        )
236    }
237}
238
239/// Errors which can be returned.
240#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::Display)]
241#[cfg_attr(feature = "defmt", derive(defmt::Format))]
242#[non_exhaustive]
243pub enum Error {
244    /// The partition table is invalid or doesn't contain a needed partition.
245    Invalid,
246    /// An operation tries to access data that is out of bounds.
247    OutOfBounds,
248    /// An error which originates from the embedded-storage implementation.
249    StorageError,
250    /// The partition is write protected.
251    WriteProtected,
252    /// The partition is invalid.
253    InvalidPartition {
254        expected_size: usize,
255        expected_type: PartitionType,
256    },
257    /// Invalid state
258    InvalidState,
259    /// The given argument is invalid.
260    InvalidArgument,
261    /// The operation is not supported for this partition (e.g. `as_nor_flash` on an encrypted
262    /// partition).
263    NotSupported,
264    /// The partition does not contain a valid application or bootloader image.
265    InvalidImage,
266}
267
268impl core::error::Error for Error {}
269
270/// A partition table.
271#[derive(Debug)]
272#[cfg_attr(feature = "defmt", derive(defmt::Format))]
273pub struct PartitionTable<'a> {
274    binary: &'a [[u8; RAW_ENTRY_LEN]],
275    entries: usize,
276}
277
278impl<'a> PartitionTable<'a> {
279    fn new(binary: &'a [u8]) -> Result<Self, Error> {
280        if binary.len() > PARTITION_TABLE_MAX_LEN {
281            return Err(Error::Invalid);
282        }
283
284        let (binary, rem) = binary.as_chunks::<RAW_ENTRY_LEN>();
285        if !rem.is_empty() {
286            return Err(Error::Invalid);
287        }
288
289        if binary.is_empty() {
290            return Ok(Self {
291                binary: &[],
292                entries: 0,
293            });
294        }
295
296        let mut raw_table = Self {
297            binary,
298            entries: binary.len(),
299        };
300
301        #[cfg(feature = "validation")]
302        {
303            let index = raw_table
304                .binary
305                .iter()
306                .position(|entry| u16::from_le_bytes([entry[0], entry[1]]) == MD5_MAGIC)
307                .ok_or(Error::Invalid)?;
308            let hash = &raw_table.binary[index][16..][..16];
309
310            let mut hasher = crate::crypto::Md5::new();
311
312            for entry in &raw_table.binary[..index] {
313                hasher.update(entry);
314            }
315            let calculated_hash = hasher.finalize();
316
317            if calculated_hash != hash {
318                return Err(Error::Invalid);
319            }
320        }
321
322        let entries = {
323            let mut i = 0;
324            loop {
325                if let Ok(entry) = raw_table.get_partition(i) {
326                    if entry.magic() != ENTRY_MAGIC {
327                        break;
328                    }
329
330                    i += 1;
331
332                    if i == raw_table.entries {
333                        break;
334                    }
335                } else {
336                    return Err(Error::Invalid);
337                }
338            }
339            i
340        };
341
342        raw_table.entries = entries;
343
344        Ok(raw_table)
345    }
346
347    /// Number of partitions contained in the partition table.
348    pub fn len(&self) -> usize {
349        self.entries
350    }
351
352    /// Checks if there are no recognized partitions.
353    pub fn is_empty(&self) -> bool {
354        self.entries == 0
355    }
356
357    /// Get a partition entry.
358    pub fn get_partition(&self, index: usize) -> Result<PartitionEntry, Error> {
359        if index >= self.entries {
360            return Err(Error::OutOfBounds);
361        }
362        Ok(PartitionEntry::new(&self.binary[index]))
363    }
364
365    /// Get the first partition matching the given partition type.
366    pub fn find_partition(&self, pt: PartitionType) -> Result<Option<PartitionEntry>, Error> {
367        for i in 0..self.entries {
368            let entry = self.get_partition(i)?;
369            if entry.partition_type() == pt {
370                return Ok(Some(entry));
371            }
372        }
373        Ok(None)
374    }
375
376    /// Returns an iterator over the partitions.
377    pub fn iter(&self) -> impl Iterator<Item = PartitionEntry> {
378        (0..self.entries).filter_map(|i| self.get_partition(i).ok())
379    }
380
381    #[cfg(feature = "std")]
382    /// Get the currently booted partition.
383    pub fn booted_partition(&self) -> Result<Option<PartitionEntry>, Error> {
384        Err(Error::Invalid)
385    }
386
387    #[cfg(not(feature = "std"))]
388    /// Get the currently booted partition.
389    pub fn booted_partition(&self) -> Result<Option<PartitionEntry>, Error> {
390        // Read entry 0 from MMU to know which partition is mapped
391        //
392        // See <https://github.com/espressif/esp-idf/blob/758939caecb16e5542b3adfba0bc85025517db45/components/hal/mmu_hal.c#L124>
393        cfg_select! {
394            feature = "esp32" => {
395                let paddr = unsafe { ((0x3FF10000 as *const u32).read_volatile() & 0xff) << 16 };
396            }
397            feature = "esp32s2" => {
398                let paddr = unsafe {
399                    (((0x61801000 + 128 * 4) as *const u32).read_volatile() & 0xff) << 16
400                };
401            }
402            feature = "esp32s3" => {
403                // Revisit this once we support XiP from PSRAM for ESP32-S3
404                let paddr = unsafe { ((0x600C5000 as *const u32).read_volatile() & 0xff) << 16 };
405            }
406            any(feature = "esp32c2", feature = "esp32c3") => {
407                let paddr = unsafe { ((0x600c5000 as *const u32).read_volatile() & 0xff) << 16 };
408            }
409            feature = "esp32p4" => {
410                // DR_REG_FLASH_SPI0_BASE : 0x5008C000 = DR_REG_HPPERIPH0_BASE + 0x8C000
411                // TODO: verify MSPI register for partition physical address read
412                let paddr = unsafe {
413                    ((0x5008C000 + 0x380) as *mut u32).write_volatile(0); // SPI_MEM_C_MMU_ITEM_INDEX_REG
414                    (((0x5008C000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16 // SPI_MEM_C_MMU_ITEM_CONTENT_REG
415                };
416            }
417            feature = "esp32s31" => {
418                // Read MMU entry 0, which maps the beginning of the flash
419                // virtual-address range.
420                let paddr = unsafe {
421                    ((0x20500000 + 0x380) as *mut u32).write_volatile(0); // SPI_MEM_C_MMU_ITEM_INDEX_REG
422                    (((0x20500000 + 0x37c) as *const u32).read_volatile() & 0x7ff) << 16 // SPI_MEM_C_MMU_ITEM_CONTENT_REG
423                };
424            }
425            any(
426                feature = "esp32c5",
427                feature = "esp32c6",
428                feature = "esp32c61",
429                feature = "esp32h2"
430            ) => {
431                let paddr = unsafe {
432                    ((0x60002000 + 0x380) as *mut u32).write_volatile(0);
433                    (((0x60002000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16
434                };
435            }
436            _ => {}
437        }
438
439        for id in 0..self.len() {
440            let entry = self.get_partition(id)?;
441            if entry.offset() == paddr {
442                return Ok(Some(entry));
443            }
444        }
445
446        Ok(None)
447    }
448}
449
450/// A partition type including the sub-type.
451#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
452#[cfg_attr(feature = "defmt", derive(defmt::Format))]
453pub enum PartitionType {
454    /// Application.
455    App(AppPartitionSubType),
456    /// Data.
457    Data(DataPartitionSubType),
458    /// Bootloader.
459    Bootloader(BootloaderPartitionSubType),
460    /// Partition table.
461    PartitionTable(PartitionTablePartitionSubType),
462}
463
464/// A partition type
465#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
466#[cfg_attr(feature = "defmt", derive(defmt::Format))]
467#[repr(u8)]
468pub enum RawPartitionType {
469    /// Application.
470    App = 0,
471    /// Data.
472    Data,
473    /// Bootloader.
474    Bootloader,
475    /// Partition table.
476    PartitionTable,
477}
478
479/// Sub-types of an application partition.
480#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
481#[cfg_attr(feature = "defmt", derive(defmt::Format))]
482#[repr(u8)]
483pub enum AppPartitionSubType {
484    /// Factory image
485    Factory = 0,
486    /// OTA slot 0
487    Ota0    = OTA_SUBTYPE_OFFSET,
488    /// OTA slot 1
489    Ota1,
490    /// OTA slot 2
491    Ota2,
492    /// OTA slot 3
493    Ota3,
494    /// OTA slot 4
495    Ota4,
496    /// OTA slot 5
497    Ota5,
498    /// OTA slot 6
499    Ota6,
500    /// OTA slot 7
501    Ota7,
502    /// OTA slot 8
503    Ota8,
504    /// OTA slot 9
505    Ota9,
506    /// OTA slot 10
507    Ota10,
508    /// OTA slot 11
509    Ota11,
510    /// OTA slot 12
511    Ota12,
512    /// OTA slot 13
513    Ota13,
514    /// OTA slot 14
515    Ota14,
516    /// OTA slot 15
517    Ota15,
518    /// Test image
519    Test,
520}
521
522impl AppPartitionSubType {
523    pub(crate) fn ota_app_number(&self) -> u8 {
524        *self as u8 - OTA_SUBTYPE_OFFSET
525    }
526
527    pub(crate) fn from_ota_app_number(number: u8) -> Result<Self, Error> {
528        if number > 16 {
529            return Err(Error::InvalidArgument);
530        }
531        Self::try_from(number + OTA_SUBTYPE_OFFSET)
532    }
533}
534
535impl TryFrom<u8> for AppPartitionSubType {
536    type Error = Error;
537
538    fn try_from(value: u8) -> Result<Self, Self::Error> {
539        AppPartitionSubType::from_repr(value).ok_or(Error::Invalid)
540    }
541}
542
543/// Sub-types of the data partition type.
544#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
545#[cfg_attr(feature = "defmt", derive(defmt::Format))]
546#[repr(u8)]
547pub enum DataPartitionSubType {
548    /// Data partition which stores information about the currently selected OTA
549    /// app slot. This partition should be 0x2000 bytes in size. Refer to
550    /// the OTA documentation for more details.
551    Ota      = 0,
552    /// Phy is for storing PHY initialization data. This allows PHY to be
553    /// configured per-device, instead of in firmware.
554    Phy,
555    /// Used for Non-Volatile Storage (NVS).
556    Nvs,
557    /// Used for storing core dumps while using a custom partition table
558    Coredump,
559    /// NvsKeys is used for the NVS key partition. (NVS).
560    NvsKeys,
561    /// Used for emulating eFuse bits using Virtual eFuses.
562    EfuseEm,
563    /// Implicitly used for data partitions with unspecified (empty) subtype,
564    /// but it is possible to explicitly mark them as undefined as well.
565    Undefined,
566    /// FAT Filesystem Support.
567    Fat      = 0x81,
568    /// SPIFFS Filesystem.
569    Spiffs   = 0x82,
570    ///  LittleFS filesystem.
571    LittleFs = 0x83,
572}
573
574impl TryFrom<u8> for DataPartitionSubType {
575    type Error = Error;
576
577    fn try_from(value: u8) -> Result<Self, Self::Error> {
578        DataPartitionSubType::from_repr(value).ok_or(Error::Invalid)
579    }
580}
581
582/// Sub-type of the bootloader partition type.
583#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
584#[cfg_attr(feature = "defmt", derive(defmt::Format))]
585#[repr(u8)]
586pub enum BootloaderPartitionSubType {
587    /// It is the so-called 2nd stage bootloader.
588    Primary = 0,
589    /// It is a temporary bootloader partition used by the bootloader OTA update
590    /// functionality for downloading a new image.
591    Ota     = 1,
592}
593
594impl TryFrom<u8> for BootloaderPartitionSubType {
595    type Error = Error;
596
597    fn try_from(value: u8) -> Result<Self, Self::Error> {
598        BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid)
599    }
600}
601
602/// Sub-type of the partition table type.
603#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
604#[cfg_attr(feature = "defmt", derive(defmt::Format))]
605#[repr(u8)]
606pub enum PartitionTablePartitionSubType {
607    /// It is the primary partition table.
608    Primary = 0,
609    /// It is a temporary partition table partition used by the partition table
610    /// OTA update functionality for downloading a new image.
611    Ota     = 1,
612}
613
614impl TryFrom<u8> for PartitionTablePartitionSubType {
615    type Error = Error;
616
617    fn try_from(value: u8) -> Result<Self, Self::Error> {
618        PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid)
619    }
620}
621
622/// Read the partition table.
623///
624/// Pass [`FlashStorage`] and a buffer to read the partition table into.
625pub fn read_partition_table<'a, 'd>(
626    flash: &mut FlashStorage<'d>,
627    storage: &'a mut [u8],
628) -> Result<PartitionTable<'a>, Error> {
629    read_partition_table_impl(flash, storage)
630}
631
632fn read_partition_table_impl<'a, F: FlashAccess>(
633    flash: &mut F,
634    storage: &'a mut [u8],
635) -> Result<PartitionTable<'a>, Error> {
636    #[cfg(feature = "std")]
637    let enabled = false;
638
639    #[cfg(not(feature = "std"))]
640    let enabled = esp_storage::flash_encryption();
641
642    if enabled {
643        flash.flash_read_encrypted(PARTITION_TABLE_OFFSET, storage)?;
644    } else {
645        flash.flash_read(PARTITION_TABLE_OFFSET, storage)?;
646    }
647
648    PartitionTable::new(storage)
649}
650
651const PARTITION_HASH_LEN: usize = 32;
652const IMAGE_HEADER_MAGIC: u8 = 0xE9;
653const IMAGE_HEADER_LEN: u32 = 24;
654const IMAGE_MAX_SEGMENTS: u8 = 16;
655const IMAGE_MAX_FLASH_ADDR_SIZE: u32 = 16 * 1024 * 1024;
656
657/// Subset of ESP-IDF `esp_image_metadata_t` for partition SHA-256 convenience.
658struct ImageMetadata {
659    image_len: u32,
660    image_digest: [u8; PARTITION_HASH_LEN],
661    hash_appended: bool,
662}
663
664/// Parse an app/bootloader image on flash and return its length and optional
665/// appended SHA-256 digest.
666///
667/// Walks the image header and segment table, accounts for the checksum
668/// padding, and — if the image has a simple hash appended — reads that digest.
669/// Does not verify the checksum or load any segments.
670fn get_image_metadata<F: FlashAccess>(
671    flash: &mut F,
672    address: u32,
673    part_size: u32,
674    encrypted: bool,
675) -> Result<ImageMetadata, Error> {
676    if part_size == 0 || part_size > IMAGE_MAX_FLASH_ADDR_SIZE {
677        return Err(Error::InvalidArgument);
678    }
679
680    // process_image_header()
681    let mut hdr = [0u8; IMAGE_HEADER_LEN as usize];
682    flash_read(flash, address, &mut hdr, encrypted)?;
683    // `esp_image_get_metadata` skips header verify, but refuse obvious garbage.
684    if hdr[0] != IMAGE_HEADER_MAGIC || hdr[1] > IMAGE_MAX_SEGMENTS {
685        return Err(Error::InvalidImage);
686    }
687
688    let mut image_len = IMAGE_HEADER_LEN;
689
690    // process_segments()
691    for _ in 0..hdr[1] {
692        let mut seg = [0u8; 8];
693        flash_read(flash, address + image_len, &mut seg, encrypted)?;
694        // seg[0..4] - load address
695        let data_len = u32::from_le_bytes(unwrap!(seg[4..8].try_into()));
696        if data_len % 4 != 0 || data_len >= IMAGE_MAX_FLASH_ADDR_SIZE {
697            return Err(Error::InvalidImage);
698        }
699        image_len = image_len
700            .checked_add(8 + data_len)
701            .ok_or(Error::InvalidImage)?;
702    }
703
704    // process_checksum()
705    // add a byte for the checksum, pad to next full 16 byte block
706    image_len = (image_len + 1 + 15) & !15;
707
708    // process_appended_hash_and_sig()
709    let hash_appended = hdr[23] != 0;
710    let mut image_digest = [0u8; PARTITION_HASH_LEN];
711    if hash_appended {
712        flash_read(flash, address + image_len, &mut image_digest, encrypted)?;
713        image_len += PARTITION_HASH_LEN as u32;
714    }
715
716    if image_len > part_size {
717        return Err(Error::InvalidImage);
718    }
719
720    Ok(ImageMetadata {
721        image_len,
722        image_digest,
723        hash_appended,
724    })
725}
726
727fn flash_read<F: FlashAccess>(
728    flash: &mut F,
729    address: u32,
730    bytes: &mut [u8],
731    encrypted: bool,
732) -> Result<(), Error> {
733    if encrypted {
734        flash.flash_read_encrypted(address, bytes)
735    } else {
736        flash.flash_read(address, bytes)
737    }
738}
739
740/// Hash `len` bytes of flash starting at `flash_offset`.
741///
742/// Reads the region in fixed-size chunks so large partitions do not need to be
743/// loaded into memory at once.
744fn sha256_flash_contents<F: FlashAccess>(
745    flash: &mut F,
746    mut flash_offset: u32,
747    mut len: u32,
748    encrypted: bool,
749) -> Result<[u8; PARTITION_HASH_LEN], Error> {
750    use sha2::{Digest, Sha256};
751
752    let mut hasher = Sha256::new();
753    let mut chunk = [0u8; 4096];
754
755    while len > 0 {
756        let n = len.min(chunk.len() as u32) as usize;
757        flash_read(flash, flash_offset, &mut chunk[..n], encrypted)?;
758        hasher.update(&chunk[..n]);
759        flash_offset += n as u32;
760        len -= n as u32;
761    }
762
763    Ok(hasher.finalize().into())
764}
765
766/// A flash region is a "view" into the partition.
767///
768/// It allows to read and write to the partition without the need to account for
769/// the partition offset.
770#[derive(Debug)]
771#[cfg_attr(feature = "defmt", derive(defmt::Format))]
772pub struct FlashRegion<'a, 'd> {
773    pub(crate) offset: u32,
774    pub(crate) len: u32,
775    pub(crate) partition_type: PartitionType,
776    pub(crate) read_only: bool,
777    /// Whether the partition is effectively encrypted (see
778    /// `PartitionEntry::is_effectively_encrypted`).
779    pub(crate) encrypted: bool,
780    pub(crate) flash: &'a mut FlashStorage<'d>,
781}
782
783impl<'a, 'd> FlashRegion<'a, 'd> {
784    /// Returns the size of the partition in bytes.
785    pub fn partition_size(&self) -> usize {
786        self.len as _
787    }
788
789    fn range(&self) -> core::ops::Range<u32> {
790        self.offset..self.offset + self.len
791    }
792
793    fn in_range(&self, start: u32, len: usize) -> bool {
794        self.range().contains(&start) && (start + len as u32 <= self.range().end)
795    }
796
797    /// Read bytes from the partition.
798    pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> {
799        let address = offset + self.offset;
800
801        if !self.in_range(address, bytes.len()) {
802            return Err(Error::OutOfBounds);
803        }
804
805        if self.encrypted {
806            self.flash.flash_read_encrypted(address, bytes)
807        } else {
808            self.flash.flash_read(address, bytes)
809        }
810    }
811
812    /// Write bytes to the partition.
813    pub fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> {
814        let address = offset + self.offset;
815
816        if self.read_only {
817            return Err(Error::WriteProtected);
818        }
819
820        if !self.in_range(address, bytes.len()) {
821            return Err(Error::OutOfBounds);
822        }
823
824        if self.encrypted {
825            self.flash.flash_write_encrypted(address, bytes)
826        } else {
827            self.flash.flash_write(address, bytes)
828        }
829    }
830
831    /// Returns the size of the partition in bytes.
832    pub fn capacity(&self) -> usize {
833        self.partition_size()
834    }
835
836    /// Erase flash in the partition from `from` up to but not including `to`.
837    ///
838    /// Addresses are relative to the partition start.
839    pub fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> {
840        let address_from = from + self.offset;
841        let address_to = to + self.offset;
842
843        if self.read_only {
844            return Err(Error::WriteProtected);
845        }
846
847        if from > to {
848            return Err(Error::OutOfBounds);
849        }
850
851        if !self.in_range(address_from, (address_to - address_from) as usize) {
852            return Err(Error::OutOfBounds);
853        }
854
855        self.flash.flash_erase(address_from, address_to)
856    }
857}
858
859#[cfg(feature = "embedded-storage")]
860/// [`NorFlash`] and [`MultiwriteNorFlash`] view of a non-encrypted [`FlashRegion`].
861pub struct NorFlashRegion<'r, 'a, 'd> {
862    region: &'r mut FlashRegion<'a, 'd>,
863}
864
865#[cfg(feature = "embedded-storage")]
866/// [`NorFlash`] view of an encrypted [`FlashRegion`].
867///
868/// Write size is one flash sector ([`esp_storage::FlashStorage::SECTOR_SIZE`]): the ROM encrypts
869/// whole sectors.
870pub struct EncryptedNorFlashRegion<'r, 'a, 'd> {
871    region: &'r mut FlashRegion<'a, 'd>,
872}
873
874#[cfg(feature = "embedded-storage")]
875mod embedded_storage_traits {
876    use ::embedded_storage::{
877        ReadStorage,
878        Region,
879        Storage,
880        nor_flash::{
881            ErrorType,
882            MultiwriteNorFlash,
883            NorFlash,
884            NorFlashError,
885            NorFlashErrorKind,
886            ReadNorFlash,
887        },
888    };
889
890    use super::*;
891
892    const NOR_READ_SIZE: usize = <FlashStorage<'static> as FlashAccess>::READ_SIZE;
893    const NOR_WRITE_SIZE: usize = <FlashStorage<'static> as FlashAccess>::WRITE_SIZE;
894    const NOR_ERASE_SIZE: usize = <FlashStorage<'static> as FlashAccess>::ERASE_SIZE;
895    const ENCRYPTED_WRITE_SIZE: usize =
896        <FlashStorage<'static> as FlashAccess>::SECTOR_SIZE as usize;
897
898    impl<'a, 'd> FlashRegion<'a, 'd> {
899        /// Returns a [`NorFlashRegion`] for [`NorFlash`] access.
900        ///
901        /// # Errors
902        ///
903        /// Returns [`Error::NotSupported`] if this partition is treated as encrypted (e.g. app
904        /// partitions when flash encryption is enabled).
905        pub fn as_nor_flash<'r>(&'r mut self) -> Result<NorFlashRegion<'r, 'a, 'd>, Error> {
906            if self.encrypted {
907                return Err(Error::NotSupported);
908            }
909
910            Ok(NorFlashRegion { region: self })
911        }
912
913        /// Returns a [`EncryptedNorFlashRegion`] for [`NorFlash`] access.
914        ///
915        /// # Errors
916        ///
917        /// Returns [`Error::NotSupported`] if this partition is not treated as encrypted.
918        pub fn as_nor_flash_encrypted<'r>(
919            &'r mut self,
920        ) -> Result<EncryptedNorFlashRegion<'r, 'a, 'd>, Error> {
921            if !self.encrypted {
922                return Err(Error::NotSupported);
923            }
924
925            Ok(EncryptedNorFlashRegion { region: self })
926        }
927    }
928
929    impl Region for FlashRegion<'_, '_> {
930        fn contains(&self, address: u32) -> bool {
931            self.range().contains(&address)
932        }
933    }
934
935    impl ReadStorage for FlashRegion<'_, '_> {
936        type Error = Error;
937
938        fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
939            FlashRegion::read(self, offset, bytes)
940        }
941
942        fn capacity(&self) -> usize {
943            FlashRegion::capacity(self)
944        }
945    }
946
947    impl Storage for FlashRegion<'_, '_> {
948        fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
949            FlashRegion::write(self, offset, bytes)
950        }
951    }
952
953    impl NorFlashError for Error {
954        fn kind(&self) -> NorFlashErrorKind {
955            match self {
956                Error::OutOfBounds => NorFlashErrorKind::OutOfBounds,
957                _ => NorFlashErrorKind::Other,
958            }
959        }
960    }
961
962    impl ErrorType for NorFlashRegion<'_, '_, '_> {
963        type Error = Error;
964    }
965
966    impl ReadNorFlash for NorFlashRegion<'_, '_, '_> {
967        const READ_SIZE: usize = NOR_READ_SIZE;
968
969        fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
970            let address = offset + self.region.offset;
971
972            if !self.region.in_range(address, bytes.len()) {
973                return Err(Error::OutOfBounds);
974            }
975
976            self.region.flash.flash_read_nor(address, bytes)
977        }
978
979        fn capacity(&self) -> usize {
980            self.region.capacity()
981        }
982    }
983
984    impl NorFlash for NorFlashRegion<'_, '_, '_> {
985        const WRITE_SIZE: usize = NOR_WRITE_SIZE;
986        const ERASE_SIZE: usize = NOR_ERASE_SIZE;
987
988        fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
989            self.region.erase(from, to)
990        }
991
992        fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
993            let address = offset + self.region.offset;
994
995            if self.region.read_only {
996                return Err(Error::WriteProtected);
997            }
998
999            if !self.region.in_range(address, bytes.len()) {
1000                return Err(Error::OutOfBounds);
1001            }
1002
1003            self.region.flash.flash_write_nor(address, bytes)
1004        }
1005    }
1006
1007    impl MultiwriteNorFlash for NorFlashRegion<'_, '_, '_> {}
1008
1009    impl ErrorType for EncryptedNorFlashRegion<'_, '_, '_> {
1010        type Error = Error;
1011    }
1012
1013    impl ReadNorFlash for EncryptedNorFlashRegion<'_, '_, '_> {
1014        const READ_SIZE: usize = NOR_READ_SIZE;
1015
1016        fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
1017            let address = offset + self.region.offset;
1018
1019            if !self.region.in_range(address, bytes.len()) {
1020                return Err(Error::OutOfBounds);
1021            }
1022
1023            self.region.flash.flash_read_encrypted(address, bytes)
1024        }
1025
1026        fn capacity(&self) -> usize {
1027            self.region.capacity()
1028        }
1029    }
1030
1031    impl NorFlash for EncryptedNorFlashRegion<'_, '_, '_> {
1032        const WRITE_SIZE: usize = ENCRYPTED_WRITE_SIZE;
1033        const ERASE_SIZE: usize = NOR_ERASE_SIZE;
1034
1035        fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
1036            self.region.erase(from, to)
1037        }
1038
1039        fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
1040            let address = offset + self.region.offset;
1041
1042            if self.region.read_only {
1043                return Err(Error::WriteProtected);
1044            }
1045
1046            if !self.region.in_range(address, bytes.len()) {
1047                return Err(Error::OutOfBounds);
1048            }
1049
1050            self.region.flash.flash_write_encrypted(address, bytes)
1051        }
1052    }
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057    use super::*;
1058
1059    static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin");
1060    static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin");
1061
1062    #[test]
1063    fn read_simple() {
1064        let pt = PartitionTable::new(SIMPLE).unwrap();
1065
1066        assert_eq!(3, pt.len());
1067
1068        assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
1069        assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
1070        assert_eq!(0, pt.get_partition(2).unwrap().raw_type());
1071
1072        assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
1073        assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype());
1074        assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype());
1075
1076        assert_eq!(
1077            PartitionType::Data(DataPartitionSubType::Nvs),
1078            pt.get_partition(0).unwrap().partition_type()
1079        );
1080        assert_eq!(
1081            PartitionType::Data(DataPartitionSubType::Phy),
1082            pt.get_partition(1).unwrap().partition_type()
1083        );
1084        assert_eq!(
1085            PartitionType::App(AppPartitionSubType::Factory),
1086            pt.get_partition(2).unwrap().partition_type()
1087        );
1088
1089        assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
1090        assert_eq!(0xf000, pt.get_partition(1).unwrap().offset());
1091        assert_eq!(0x10000, pt.get_partition(2).unwrap().offset());
1092
1093        assert_eq!(0x6000, pt.get_partition(0).unwrap().len());
1094        assert_eq!(0x1000, pt.get_partition(1).unwrap().len());
1095        assert_eq!(0x100000, pt.get_partition(2).unwrap().len());
1096
1097        assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
1098        assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str());
1099        assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str());
1100
1101        assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
1102        assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
1103        assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
1104
1105        assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
1106        assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
1107        assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
1108    }
1109
1110    #[test]
1111    fn read_ota() {
1112        let pt = PartitionTable::new(OTA).unwrap();
1113
1114        assert_eq!(6, pt.len());
1115
1116        assert_eq!(1, pt.get_partition(0).unwrap().raw_type());
1117        assert_eq!(1, pt.get_partition(1).unwrap().raw_type());
1118        assert_eq!(1, pt.get_partition(2).unwrap().raw_type());
1119        assert_eq!(0, pt.get_partition(3).unwrap().raw_type());
1120        assert_eq!(0, pt.get_partition(4).unwrap().raw_type());
1121        assert_eq!(0, pt.get_partition(5).unwrap().raw_type());
1122
1123        assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype());
1124        assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype());
1125        assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype());
1126        assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype());
1127        assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype());
1128        assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype());
1129
1130        assert_eq!(
1131            PartitionType::Data(DataPartitionSubType::Nvs),
1132            pt.get_partition(0).unwrap().partition_type()
1133        );
1134        assert_eq!(
1135            PartitionType::Data(DataPartitionSubType::Ota),
1136            pt.get_partition(1).unwrap().partition_type()
1137        );
1138        assert_eq!(
1139            PartitionType::Data(DataPartitionSubType::Phy),
1140            pt.get_partition(2).unwrap().partition_type()
1141        );
1142        assert_eq!(
1143            PartitionType::App(AppPartitionSubType::Factory),
1144            pt.get_partition(3).unwrap().partition_type()
1145        );
1146        assert_eq!(
1147            PartitionType::App(AppPartitionSubType::Ota0),
1148            pt.get_partition(4).unwrap().partition_type()
1149        );
1150        assert_eq!(
1151            PartitionType::App(AppPartitionSubType::Ota1),
1152            pt.get_partition(5).unwrap().partition_type()
1153        );
1154
1155        assert_eq!(0x9000, pt.get_partition(0).unwrap().offset());
1156        assert_eq!(0xd000, pt.get_partition(1).unwrap().offset());
1157        assert_eq!(0xf000, pt.get_partition(2).unwrap().offset());
1158        assert_eq!(0x10000, pt.get_partition(3).unwrap().offset());
1159        assert_eq!(0x110000, pt.get_partition(4).unwrap().offset());
1160        assert_eq!(0x210000, pt.get_partition(5).unwrap().offset());
1161
1162        assert_eq!(0x4000, pt.get_partition(0).unwrap().len());
1163        assert_eq!(0x2000, pt.get_partition(1).unwrap().len());
1164        assert_eq!(0x1000, pt.get_partition(2).unwrap().len());
1165        assert_eq!(0x100000, pt.get_partition(3).unwrap().len());
1166        assert_eq!(0x100000, pt.get_partition(4).unwrap().len());
1167        assert_eq!(0x100000, pt.get_partition(5).unwrap().len());
1168
1169        assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str());
1170        assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str());
1171        assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str());
1172        assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str());
1173        assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str());
1174        assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str());
1175
1176        assert_eq!(false, pt.get_partition(0).unwrap().is_read_only());
1177        assert_eq!(false, pt.get_partition(1).unwrap().is_read_only());
1178        assert_eq!(false, pt.get_partition(2).unwrap().is_read_only());
1179        assert_eq!(false, pt.get_partition(3).unwrap().is_read_only());
1180        assert_eq!(false, pt.get_partition(4).unwrap().is_read_only());
1181        assert_eq!(false, pt.get_partition(5).unwrap().is_read_only());
1182
1183        assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted());
1184        assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted());
1185        assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted());
1186        assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted());
1187        assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted());
1188        assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted());
1189    }
1190
1191    #[test]
1192    fn empty_byte_array() {
1193        let pt = PartitionTable::new(&[]).unwrap();
1194
1195        assert_eq!(0, pt.len());
1196        assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds)));
1197    }
1198
1199    #[test]
1200    fn validation_fails_wo_hash() {
1201        assert!(matches!(
1202            PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]),
1203            Err(Error::Invalid)
1204        ));
1205    }
1206
1207    #[test]
1208    fn validation_fails_wo_hash_max_entries() {
1209        let mut data = [0u8; PARTITION_TABLE_MAX_LEN];
1210        for i in 0..96 {
1211            data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]);
1212        }
1213
1214        assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid)));
1215    }
1216
1217    #[test]
1218    fn validation_succeeds_with_enough_entries() {
1219        assert_eq!(
1220            3,
1221            PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4])
1222                .unwrap()
1223                .len()
1224        );
1225    }
1226}
1227
1228#[cfg(test)]
1229mod storage_tests {
1230    use super::*;
1231
1232    fn test_flash() -> FlashStorage<'static> {
1233        let mut flash = FlashStorage::new();
1234        let mut data = [23u8; 0x10000];
1235        data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN]
1236            .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin"));
1237        flash.write(0, &data).unwrap();
1238        flash
1239    }
1240
1241    #[test]
1242    fn can_read_write_all_of_nvs() {
1243        let mut storage = test_flash();
1244
1245        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1246        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1247
1248        let nvs = pt
1249            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1250            .unwrap()
1251            .unwrap();
1252        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1253        assert_eq!(nvs_partition.offset, 36864);
1254
1255        assert_eq!(nvs_partition.capacity(), 24576);
1256
1257        let mut buffer = [0u8; 24576];
1258        nvs_partition.read(0, &mut buffer).unwrap();
1259        assert!(buffer.iter().all(|v| *v == 23));
1260        buffer.fill(42);
1261        nvs_partition.write(0, &buffer).unwrap();
1262        let mut buffer = [0u8; 24576];
1263        nvs_partition.read(0, &mut buffer).unwrap();
1264        assert!(buffer.iter().all(|v| *v == 42));
1265    }
1266
1267    #[test]
1268    fn cannot_read_write_more_than_partition_size() {
1269        let mut storage = test_flash();
1270
1271        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1272        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1273
1274        let nvs = pt
1275            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1276            .unwrap()
1277            .unwrap();
1278        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1279        assert_eq!(nvs_partition.offset, 36864);
1280
1281        assert_eq!(nvs_partition.capacity(), 24576);
1282
1283        let mut buffer = [0u8; 24577];
1284        assert!(nvs_partition.read(0, &mut buffer) == Err(Error::OutOfBounds));
1285    }
1286
1287    #[test]
1288    fn can_erase_up_to_partition_end() {
1289        let mut storage = test_flash();
1290
1291        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1292        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1293
1294        let nvs = pt
1295            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1296            .unwrap()
1297            .unwrap();
1298        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1299
1300        let capacity = nvs_partition.capacity() as u32;
1301        assert_eq!(capacity, 24576);
1302
1303        nvs_partition.write(0, &[42u8; 24576]).unwrap();
1304
1305        nvs_partition.erase(capacity - 4096, capacity).unwrap();
1306        let mut buffer = [0u8; 4096];
1307        nvs_partition.read(capacity - 4096, &mut buffer).unwrap();
1308        assert!(buffer.iter().all(|v| *v == 0xff));
1309
1310        nvs_partition.erase(0, capacity).unwrap();
1311        let mut buffer = [0u8; 24576];
1312        nvs_partition.read(0, &mut buffer).unwrap();
1313        assert!(buffer.iter().all(|v| *v == 0xff));
1314    }
1315
1316    #[test]
1317    fn cannot_erase_out_of_bounds() {
1318        let mut storage = test_flash();
1319
1320        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1321        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1322
1323        let nvs = pt
1324            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1325            .unwrap()
1326            .unwrap();
1327        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1328
1329        let capacity = nvs_partition.capacity() as u32;
1330
1331        assert!(nvs_partition.erase(0, capacity + 4096) == Err(Error::OutOfBounds));
1332        assert!(nvs_partition.erase(capacity, capacity + 4096) == Err(Error::OutOfBounds));
1333        assert!(nvs_partition.erase(4096, 0) == Err(Error::OutOfBounds));
1334    }
1335}
1336
1337#[cfg(test)]
1338mod sha256_tests {
1339    use super::*;
1340
1341    /// SHA-256 of `0x6000` bytes filled with `0xA5`.
1342    const NVS_DIGEST: [u8; 32] = [
1343        0xb0, 0x5b, 0x4f, 0x2c, 0xc2, 0xa7, 0x54, 0x25, 0x54, 0xfa, 0x32, 0x8b, 0xd0, 0x5d, 0x86,
1344        0x7f, 0x0c, 0x1d, 0xae, 0xed, 0x46, 0x48, 0x8e, 0x31, 0xb0, 0x0c, 0xb0, 0xaa, 0xe5, 0xb5,
1345        0x49, 0x81,
1346    ];
1347
1348    /// SHA-256 of the minimal test image body (32 bytes) with `hash_appended = 1`.
1349    const IMAGE_DIGEST_WITH_HASH_FLAG: [u8; 32] = [
1350        0xb2, 0xb7, 0x64, 0x4a, 0x57, 0x62, 0x46, 0x05, 0xf7, 0xe4, 0xb1, 0xc3, 0xbf, 0x96, 0x5a,
1351        0x20, 0x87, 0x37, 0x3d, 0x7a, 0xc6, 0x2d, 0xf8, 0x6a, 0xcf, 0x2b, 0x1a, 0xcf, 0xe4, 0x8e,
1352        0xe8, 0xa0,
1353    ];
1354
1355    /// SHA-256 of the same minimal image body with `hash_appended = 0`.
1356    const IMAGE_DIGEST_WITHOUT_HASH_FLAG: [u8; 32] = [
1357        0x02, 0x50, 0xbb, 0x56, 0xe1, 0x91, 0xf6, 0x6d, 0xde, 0xf1, 0x5e, 0x2d, 0x7c, 0xb4, 0x48,
1358        0x23, 0x75, 0x36, 0x52, 0x54, 0x7f, 0xc3, 0xd9, 0xd5, 0x83, 0xaa, 0xca, 0x2e, 0xec, 0xfe,
1359        0x99, 0x00,
1360    ];
1361
1362    fn test_flash() -> FlashStorage<'static> {
1363        let mut flash = FlashStorage::new();
1364        let mut data = [0xffu8; 0x10000];
1365        data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN]
1366            .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin"));
1367        flash.write(0, &data).unwrap();
1368        flash
1369    }
1370
1371    /// Header-only ESP image (0 segments); body pads to 32 bytes for the checksum.
1372    fn write_minimal_app_image(
1373        flash: &mut FlashStorage<'static>,
1374        offset: u32,
1375        hash_appended: bool,
1376    ) {
1377        let mut image = [0u8; 64];
1378        image[0] = IMAGE_HEADER_MAGIC;
1379        image[23] = u8::from(hash_appended);
1380        // image[1] = 0 segments; bytes 24..32 are checksum padding
1381        if hash_appended {
1382            image[32..64].copy_from_slice(&IMAGE_DIGEST_WITH_HASH_FLAG);
1383            flash.write(offset, &image).unwrap();
1384        } else {
1385            flash.write(offset, &image[..32]).unwrap();
1386        }
1387    }
1388
1389    #[test]
1390    fn sha256_of_data_partition_matches_known_digest() {
1391        let mut flash = test_flash();
1392
1393        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1394        let pt = read_partition_table(&mut flash, &mut buffer).unwrap();
1395        let nvs = pt
1396            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1397            .unwrap()
1398            .unwrap();
1399
1400        nvs.as_flash_region(&mut flash)
1401            .write(0, &[0xa5u8; 0x6000])
1402            .unwrap();
1403
1404        assert_eq!(nvs.sha256(&mut flash).unwrap(), NVS_DIGEST);
1405    }
1406
1407    #[test]
1408    fn sha256_of_app_with_appended_hash_returns_validation_digest() {
1409        let mut flash = test_flash();
1410
1411        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1412        let pt = read_partition_table(&mut flash, &mut buffer).unwrap();
1413        let factory = pt
1414            .find_partition(PartitionType::App(AppPartitionSubType::Factory))
1415            .unwrap()
1416            .unwrap();
1417
1418        write_minimal_app_image(&mut flash, factory.offset(), true);
1419
1420        assert_eq!(
1421            factory.sha256(&mut flash).unwrap(),
1422            IMAGE_DIGEST_WITH_HASH_FLAG
1423        );
1424    }
1425
1426    #[test]
1427    fn sha256_of_app_without_appended_hash_hashes_image() {
1428        let mut flash = test_flash();
1429
1430        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1431        let pt = read_partition_table(&mut flash, &mut buffer).unwrap();
1432        let factory = pt
1433            .find_partition(PartitionType::App(AppPartitionSubType::Factory))
1434            .unwrap()
1435            .unwrap();
1436
1437        write_minimal_app_image(&mut flash, factory.offset(), false);
1438
1439        assert_eq!(
1440            factory.sha256(&mut flash).unwrap(),
1441            IMAGE_DIGEST_WITHOUT_HASH_FLAG
1442        );
1443    }
1444
1445    #[test]
1446    fn sha256_rejects_corrupt_appended_hash() {
1447        let mut flash = test_flash();
1448
1449        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1450        let pt = read_partition_table(&mut flash, &mut buffer).unwrap();
1451        let factory = pt
1452            .find_partition(PartitionType::App(AppPartitionSubType::Factory))
1453            .unwrap()
1454            .unwrap();
1455
1456        write_minimal_app_image(&mut flash, factory.offset(), true);
1457
1458        // Corrupt the appended digest
1459        flash.write(factory.offset() + 32, &[0u8; 32]).unwrap();
1460
1461        assert_eq!(factory.sha256(&mut flash), Err(Error::InvalidImage));
1462    }
1463}
1464
1465#[cfg(all(test, feature = "embedded-storage"))]
1466mod nor_flash_tests {
1467    use embedded_storage::nor_flash::{MultiwriteNorFlash, NorFlash, ReadNorFlash};
1468
1469    use super::*;
1470
1471    fn test_flash() -> FlashStorage<'static> {
1472        let mut flash = FlashStorage::new();
1473        let mut data = [23u8; 0x10000];
1474        data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN]
1475            .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin"));
1476        flash.write(0, &data).unwrap();
1477        flash
1478    }
1479
1480    #[test]
1481    fn plain_nor_flash_implements_multi_write() {
1482        fn assert_multi_write<N: MultiwriteNorFlash>() {}
1483        assert_multi_write::<NorFlashRegion<'static, 'static, 'static>>();
1484    }
1485
1486    #[test]
1487    fn as_nor_flash_succeeds_on_plain_partition() {
1488        let mut storage = test_flash();
1489
1490        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1491        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1492
1493        let nvs = pt
1494            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1495            .unwrap()
1496            .unwrap();
1497        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1498
1499        assert!(nvs_partition.as_nor_flash().is_ok());
1500        assert!(matches!(
1501            nvs_partition.as_nor_flash_encrypted(),
1502            Err(Error::NotSupported)
1503        ));
1504    }
1505
1506    #[test]
1507    fn nor_flash_write_sizes() {
1508        assert_eq!(
1509            <NorFlashRegion<'static, 'static, 'static> as NorFlash>::WRITE_SIZE,
1510            <FlashStorage<'static> as FlashAccess>::WRITE_SIZE
1511        );
1512        assert_eq!(
1513            <EncryptedNorFlashRegion<'static, 'static, 'static> as NorFlash>::WRITE_SIZE,
1514            <FlashStorage<'static> as FlashAccess>::SECTOR_SIZE as usize
1515        );
1516        assert_eq!(
1517            <NorFlashRegion<'static, 'static, 'static> as ReadNorFlash>::READ_SIZE,
1518            <FlashStorage<'static> as FlashAccess>::READ_SIZE
1519        );
1520        assert_eq!(
1521            <EncryptedNorFlashRegion<'static, 'static, 'static> as ReadNorFlash>::READ_SIZE,
1522            <FlashStorage<'static> as FlashAccess>::READ_SIZE
1523        );
1524    }
1525
1526    #[test]
1527    fn nor_flash_erase_bounds() {
1528        let mut storage = test_flash();
1529
1530        let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN];
1531        let pt = read_partition_table(&mut storage, &mut buffer).unwrap();
1532
1533        let nvs = pt
1534            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
1535            .unwrap()
1536            .unwrap();
1537        let mut nvs_partition = nvs.as_flash_region(&mut storage);
1538        let capacity = nvs_partition.capacity() as u32;
1539        let mut nor_flash = nvs_partition.as_nor_flash().unwrap();
1540
1541        nor_flash.erase(0, capacity).unwrap();
1542        let mut buffer = [0u8; 4096];
1543        nor_flash.read(capacity - 4096, &mut buffer).unwrap();
1544        assert!(buffer.iter().all(|v| *v == 0xff));
1545
1546        assert!(nor_flash.erase(0, capacity + 4096) == Err(Error::OutOfBounds));
1547        assert!(nor_flash.erase(4096, 0) == Err(Error::OutOfBounds));
1548    }
1549}