From 2e8b7d259057a0cf345396b250cacfc04a0e64a0 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Wed, 7 Feb 2024 16:40:24 +0100
Subject: [PATCH 1/8] feat(boot): introduce non-erase flash write method

---
 embassy-boot/src/firmware_updater/blocking.rs | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index f1368540d..514070639 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -194,6 +194,41 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
         Ok(())
     }
 
+    /// Write data directly to a flash page without erasing it first.
+    ///
+    /// This function writes the provided data to the specified offset in the flash memory,
+    /// without performing an erase operation beforehand. It is crucial that the area being
+    /// written to is either already erased.
+    /// This method is intended to be used in conjunction with the `prepare_update` method.
+    ///
+    /// The buffer must follow the alignment requirements of the target flash and be a multiple of
+    /// the page size. This is essential to ensure data integrity and prevent corruption.
+    ///
+    /// # Safety
+    ///
+    /// This function requires careful management of the memory being written to. Writing to a
+    /// non-erased page or not adhering to alignment and size requirements may result in a panic.
+    ///
+    /// Ensure that the data being written is compatible with the current contents of the flash
+    /// memory, as no erase operation will be performed to reset the page content to a default state.
+    ///
+    /// # Parameters
+    ///
+    /// - `offset`: The offset within the DFU partition where the data will be written. Must be
+    /// aligned according to the flash's requirements and within the writable memory range.
+    /// - `data`: A reference to the slice of bytes to be written. The length of the data must not
+    /// exceed the partition size and must follow the flash's alignment requirements.
+    ///
+    /// # Returns
+    ///
+    /// A result indicating the success or failure of the write operation. On success, returns `Ok(())`.
+    /// On failure, returns an `Err` with a `FirmwareUpdaterError` detailing the cause of the failure.
+    pub fn write_firmware_without_erase(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
+        self.dfu.write(offset as u32, data)?;
+
+        Ok(())
+    }
+
     /// Prepare for an incoming DFU update by erasing the entire DFU area and
     /// returning its `Partition`.
     ///

From c95bf6895adfcd33b5238c02620e83c6713205ce Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Wed, 7 Feb 2024 16:41:58 +0100
Subject: [PATCH 2/8] feat(usb-dfu): change usb dfu chunks write mechanism

---
 embassy-usb-dfu/src/dfu.rs | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/embassy-usb-dfu/src/dfu.rs b/embassy-usb-dfu/src/dfu.rs
index e99aa70c3..5f2c98684 100644
--- a/embassy-usb-dfu/src/dfu.rs
+++ b/embassy-usb-dfu/src/dfu.rs
@@ -60,6 +60,21 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
             }
             Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
                 if req.value == 0 {
+                    match self.updater.prepare_update() {
+                        Ok(_) => {
+                            self.status = Status::Ok;
+                        }
+                        Err(e) => {
+                            self.state = State::Error;
+                            match e {
+                                embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
+                                    NorFlashErrorKind::NotAligned => self.status = Status::ErrErase,
+                                    _ => self.status = Status::ErrUnknown,
+                                },
+                                _ => self.status = Status::ErrUnknown,
+                            }
+                        }
+                    }
                     self.state = State::Download;
                     self.offset = 0;
                 }
@@ -93,7 +108,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
                         self.state = State::Error;
                         return Some(OutResponse::Rejected);
                     }
-                    match self.updater.write_firmware(self.offset, buf.as_ref()) {
+                    match self.updater.write_firmware_without_erase(self.offset, buf.as_ref()) {
                         Ok(_) => {
                             self.status = Status::Ok;
                             self.state = State::DlSync;

From 8f7d80f9f71e55ffe97b72dbb361409aee003ea4 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Sun, 11 Feb 2024 19:37:48 +0100
Subject: [PATCH 3/8] Revert "feat(boot): introduce non-erase flash write
 method "

This reverts commit 2e8b7d259057a0cf345396b250cacfc04a0e64a0.
---
 embassy-boot/src/firmware_updater/blocking.rs | 35 -------------------
 1 file changed, 35 deletions(-)

diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 3e83366af..4044871f0 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -225,41 +225,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
         Ok(())
     }
 
-    /// Write data directly to a flash page without erasing it first.
-    ///
-    /// This function writes the provided data to the specified offset in the flash memory,
-    /// without performing an erase operation beforehand. It is crucial that the area being
-    /// written to is either already erased.
-    /// This method is intended to be used in conjunction with the `prepare_update` method.
-    ///
-    /// The buffer must follow the alignment requirements of the target flash and be a multiple of
-    /// the page size. This is essential to ensure data integrity and prevent corruption.
-    ///
-    /// # Safety
-    ///
-    /// This function requires careful management of the memory being written to. Writing to a
-    /// non-erased page or not adhering to alignment and size requirements may result in a panic.
-    ///
-    /// Ensure that the data being written is compatible with the current contents of the flash
-    /// memory, as no erase operation will be performed to reset the page content to a default state.
-    ///
-    /// # Parameters
-    ///
-    /// - `offset`: The offset within the DFU partition where the data will be written. Must be
-    /// aligned according to the flash's requirements and within the writable memory range.
-    /// - `data`: A reference to the slice of bytes to be written. The length of the data must not
-    /// exceed the partition size and must follow the flash's alignment requirements.
-    ///
-    /// # Returns
-    ///
-    /// A result indicating the success or failure of the write operation. On success, returns `Ok(())`.
-    /// On failure, returns an `Err` with a `FirmwareUpdaterError` detailing the cause of the failure.
-    pub fn write_firmware_without_erase(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
-        self.dfu.write(offset as u32, data)?;
-
-        Ok(())
-    }
-
     /// Prepare for an incoming DFU update by erasing the entire DFU area and
     /// returning its `Partition`.
     ///

From 72ab04c45335ceb725076a38a252749e781a8cf6 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Sun, 11 Feb 2024 19:38:01 +0100
Subject: [PATCH 4/8] Revert "feat(usb-dfu): change usb dfu chunks write
 mechanism "

This reverts commit c95bf6895adfcd33b5238c02620e83c6713205ce.
---
 embassy-usb-dfu/src/dfu.rs | 17 +----------------
 1 file changed, 1 insertion(+), 16 deletions(-)

diff --git a/embassy-usb-dfu/src/dfu.rs b/embassy-usb-dfu/src/dfu.rs
index 5f2c98684..e99aa70c3 100644
--- a/embassy-usb-dfu/src/dfu.rs
+++ b/embassy-usb-dfu/src/dfu.rs
@@ -60,21 +60,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
             }
             Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
                 if req.value == 0 {
-                    match self.updater.prepare_update() {
-                        Ok(_) => {
-                            self.status = Status::Ok;
-                        }
-                        Err(e) => {
-                            self.state = State::Error;
-                            match e {
-                                embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
-                                    NorFlashErrorKind::NotAligned => self.status = Status::ErrErase,
-                                    _ => self.status = Status::ErrUnknown,
-                                },
-                                _ => self.status = Status::ErrUnknown,
-                            }
-                        }
-                    }
                     self.state = State::Download;
                     self.offset = 0;
                 }
@@ -108,7 +93,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
                         self.state = State::Error;
                         return Some(OutResponse::Rejected);
                     }
-                    match self.updater.write_firmware_without_erase(self.offset, buf.as_ref()) {
+                    match self.updater.write_firmware(self.offset, buf.as_ref()) {
                         Ok(_) => {
                             self.status = Status::Ok;
                             self.state = State::DlSync;

From eb3bd39b068ae34892520ec38f111704ea357355 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Sun, 11 Feb 2024 20:02:28 +0100
Subject: [PATCH 5/8] feat(boot): enhance firmware write functionality

---
 embassy-boot/src/firmware_updater/asynch.rs   | 71 ++++++++++++++++---
 embassy-boot/src/firmware_updater/blocking.rs | 70 +++++++++++++++---
 2 files changed, 124 insertions(+), 17 deletions(-)

diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index 668f16f16..99a3aa246 100644
--- a/embassy-boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -172,21 +172,69 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
         self.state.mark_booted().await
     }
 
-    /// Write data to a flash page.
+    /// Writes firmware data to the device.
     ///
-    /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
+    /// This function writes the given data to the firmware area starting at the specified offset.
+    /// It handles sector erasures and data writes while verifying the device is in a proper state
+    /// for firmware updates. The function ensures that only unerased sectors are erased before
+    /// writing and efficiently handles the writing process across sector boundaries and in
+    /// various configurations (data size, page size, etc.).
     ///
-    /// # Safety
+    /// # Arguments
     ///
-    /// Failing to meet alignment and size requirements may result in a panic.
+    /// * `offset` - The starting offset within the firmware area where data writing should begin.
+    /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
+    /// multiple of NorFlash WRITE_SIZE.
+    ///
+    /// # Returns
+    ///
+    /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if:
+    ///
+    /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
+    /// - There is a failure erasing a sector before writing.
+    /// - There is a failure writing data to the device.
     pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
-        assert!(data.len() >= DFU::ERASE_SIZE);
-
+        // Make sure we are running a booted firmware to avoid reverting to a bad state.
         self.state.verify_booted().await?;
 
-        self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
+        // Initialize variables to keep track of the remaining data and the current offset.
+        let mut remaining_data = data;
+        let mut offset = offset;
 
-        self.dfu.write(offset as u32, data).await?;
+        // Continue writing as long as there is data left to write.
+        while !remaining_data.is_empty() {
+            // Compute the current sector and its boundaries.
+            let current_sector = offset / DFU::ERASE_SIZE;
+            let sector_start = current_sector * DFU::ERASE_SIZE;
+            let sector_end = sector_start + DFU::ERASE_SIZE;
+            // Determine if the current sector needs to be erased before writing.
+            let need_erase = self
+                .state
+                .last_erased_dfu_page_index
+                .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
+
+            // If the sector needs to be erased, erase it and update the last erased sector index.
+            if need_erase {
+                self.dfu.erase(sector_start as u32, sector_end as u32).await?;
+                self.state.last_erased_dfu_page_index = Some(current_sector);
+            }
+
+            // Calculate the size of the data chunk that can be written in the current iteration.
+            let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
+            // Split the data to get the current chunk to be written and the remaining data.
+            let (data_chunk, rest) = remaining_data.split_at(write_size);
+
+            // Write the current data chunk.
+            self.dfu.write(offset as u32, data_chunk).await?;
+
+            // Update the offset and remaining data for the next iteration.
+            remaining_data = rest;
+            offset += write_size;
+        }
 
         Ok(())
     }
@@ -210,6 +258,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
 pub struct FirmwareState<'d, STATE> {
     state: STATE,
     aligned: &'d mut [u8],
+    last_erased_dfu_page_index: Option<usize>,
 }
 
 impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
@@ -231,7 +280,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
     /// and follow the alignment rules for the flash being read from and written to.
     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
         assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE));
-        Self { state, aligned }
+        Self {
+            state,
+            aligned,
+            last_erased_dfu_page_index: None,
+        }
     }
 
     // Make sure we are running a booted firmware to avoid reverting to a bad state.
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 4044871f0..45ae966f3 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -207,20 +207,69 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
         self.state.mark_booted()
     }
 
-    /// Write data to a flash page.
+    /// Writes firmware data to the device.
     ///
-    /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
+    /// This function writes the given data to the firmware area starting at the specified offset.
+    /// It handles sector erasures and data writes while verifying the device is in a proper state
+    /// for firmware updates. The function ensures that only unerased sectors are erased before
+    /// writing and efficiently handles the writing process across sector boundaries and in
+    /// various configurations (data size, page size, etc.).
     ///
-    /// # Safety
+    /// # Arguments
     ///
-    /// Failing to meet alignment and size requirements may result in a panic.
+    /// * `offset` - The starting offset within the firmware area where data writing should begin.
+    /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
+    /// multiple of NorFlash WRITE_SIZE.
+    ///
+    /// # Returns
+    ///
+    /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if:
+    ///
+    /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
+    /// - There is a failure erasing a sector before writing.
+    /// - There is a failure writing data to the device.
     pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
-        assert!(data.len() >= DFU::ERASE_SIZE);
+        // Make sure we are running a booted firmware to avoid reverting to a bad state.
         self.state.verify_booted()?;
 
-        self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
+        // Initialize variables to keep track of the remaining data and the current offset.
+        let mut remaining_data = data;
+        let mut offset = offset;
 
-        self.dfu.write(offset as u32, data)?;
+        // Continue writing as long as there is data left to write.
+        while !remaining_data.is_empty() {
+            // Compute the current sector and its boundaries.
+            let current_sector = offset / DFU::ERASE_SIZE;
+            let sector_start = current_sector * DFU::ERASE_SIZE;
+            let sector_end = sector_start + DFU::ERASE_SIZE;
+            // Determine if the current sector needs to be erased before writing.
+            let need_erase = self
+                .state
+                .last_erased_dfu_page_index
+                .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
+
+            // If the sector needs to be erased, erase it and update the last erased sector index.
+            if need_erase {
+                self.dfu.erase(sector_start as u32, sector_end as u32)?;
+                self.state.last_erased_dfu_page_index = Some(current_sector);
+            }
+
+            // Calculate the size of the data chunk that can be written in the current iteration.
+            let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
+            // Split the data to get the current chunk to be written and the remaining data.
+            let (data_chunk, rest) = remaining_data.split_at(write_size);
+
+            // Write the current data chunk.
+            self.dfu.write(offset as u32, data_chunk)?;
+
+            // Update the offset and remaining data for the next iteration.
+            remaining_data = rest;
+            offset += write_size;
+        }
 
         Ok(())
     }
@@ -244,6 +293,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
 pub struct BlockingFirmwareState<'d, STATE> {
     state: STATE,
     aligned: &'d mut [u8],
+    last_erased_dfu_page_index: Option<usize>,
 }
 
 impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
@@ -265,7 +315,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
     /// and written to.
     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
         assert_eq!(aligned.len(), STATE::WRITE_SIZE);
-        Self { state, aligned }
+        Self {
+            state,
+            aligned,
+            last_erased_dfu_page_index: None,
+        }
     }
 
     // Make sure we are running a booted firmware to avoid reverting to a bad state.

From 333b2afe6d5319317b2679e634c3cf242bab0e73 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Sun, 11 Feb 2024 20:17:15 +0100
Subject: [PATCH 6/8] test(boot): add various write firmware test
 configurations

---
 embassy-boot/src/firmware_updater/asynch.rs   | 72 +++++++++++++++++
 embassy-boot/src/firmware_updater/blocking.rs | 78 +++++++++++++++++++
 2 files changed, 150 insertions(+)

diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index 99a3aa246..d31eff005 100644
--- a/embassy-boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -382,4 +382,76 @@ mod tests {
 
         assert_eq!(Sha1::digest(update).as_slice(), hash);
     }
+
+    #[test]
+    fn can_verify_sha1_page_bigger_than_chunk() {
+        let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
+        let state = Partition::new(&flash, 0, 4096);
+        let dfu = Partition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(1024) {
+            block_on(updater.write_firmware(offset, chunk)).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
+
+    #[test]
+    fn can_verify_sha1_page_smaller_than_chunk() {
+        let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
+        let state = Partition::new(&flash, 0, 4096);
+        let dfu = Partition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(2048) {
+            block_on(updater.write_firmware(offset, chunk)).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
+
+    #[test]
+    fn can_verify_sha1_cross_page_boundary() {
+        let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
+        let state = Partition::new(&flash, 0, 4096);
+        let dfu = Partition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(896) {
+            block_on(updater.write_firmware(offset, chunk)).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
 }
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 45ae966f3..5b8076f81 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -422,4 +422,82 @@ mod tests {
 
         assert_eq!(Sha1::digest(update).as_slice(), hash);
     }
+
+    #[test]
+    fn can_verify_sha1_page_bigger_than_chunk() {
+        let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
+        let state = BlockingPartition::new(&flash, 0, 4096);
+        let dfu = BlockingPartition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(1024) {
+            updater.write_firmware(offset, chunk).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        updater
+            .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
+            .unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
+
+    #[test]
+    fn can_verify_sha1_page_smaller_than_chunk() {
+        let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
+        let state = BlockingPartition::new(&flash, 0, 4096);
+        let dfu = BlockingPartition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(2048) {
+            updater.write_firmware(offset, chunk).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        updater
+            .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
+            .unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
+
+    #[test]
+    fn can_verify_sha1_cross_page_boundary() {
+        let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
+        let state = BlockingPartition::new(&flash, 0, 4096);
+        let dfu = BlockingPartition::new(&flash, 65536, 65536);
+        let mut aligned = [0; 8];
+
+        let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+        let mut to_write = [0; 4096];
+        to_write[..7].copy_from_slice(update.as_slice());
+
+        let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+        let mut offset = 0;
+        for chunk in to_write.chunks(896) {
+            updater.write_firmware(offset, chunk).unwrap();
+            offset += chunk.len();
+        }
+        let mut chunk_buf = [0; 2];
+        let mut hash = [0; 20];
+        updater
+            .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
+            .unwrap();
+
+        assert_eq!(Sha1::digest(update).as_slice(), hash);
+    }
 }

From 56e6b6bee6bd6087b35857e8aa1a011f6e83f703 Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Mon, 12 Feb 2024 23:24:21 +0100
Subject: [PATCH 7/8] refactor(boot): move page erase index out of state

---
 embassy-boot/src/firmware_updater/asynch.rs   | 14 +++++---------
 embassy-boot/src/firmware_updater/blocking.rs | 14 +++++---------
 2 files changed, 10 insertions(+), 18 deletions(-)

diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index d31eff005..b76668136 100644
--- a/embassy-boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -13,6 +13,7 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA
 pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
     dfu: DFU,
     state: FirmwareState<'d, STATE>,
+    last_erased_dfu_page_index: Option<usize>,
 }
 
 #[cfg(target_os = "none")]
@@ -56,6 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
         Self {
             dfu: config.dfu,
             state: FirmwareState::new(config.state, aligned),
+            last_erased_dfu_page_index: None,
         }
     }
 
@@ -72,7 +74,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
     /// proceed with updating the firmware as it must be signed with a
     /// corresponding private key (otherwise it could be malicious firmware).
     ///
-    /// Mark to trigger firmware swap on next boot if verify suceeds.
+    /// Mark to trigger firmware swap on next boot if verify succeeds.
     ///
     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
     /// been generated from a SHA-512 digest of the firmware bytes.
@@ -213,14 +215,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
             let sector_end = sector_start + DFU::ERASE_SIZE;
             // Determine if the current sector needs to be erased before writing.
             let need_erase = self
-                .state
                 .last_erased_dfu_page_index
                 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
 
             // If the sector needs to be erased, erase it and update the last erased sector index.
             if need_erase {
                 self.dfu.erase(sector_start as u32, sector_end as u32).await?;
-                self.state.last_erased_dfu_page_index = Some(current_sector);
+                self.last_erased_dfu_page_index = Some(current_sector);
             }
 
             // Calculate the size of the data chunk that can be written in the current iteration.
@@ -258,7 +259,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
 pub struct FirmwareState<'d, STATE> {
     state: STATE,
     aligned: &'d mut [u8],
-    last_erased_dfu_page_index: Option<usize>,
 }
 
 impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
@@ -280,11 +280,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
     /// and follow the alignment rules for the flash being read from and written to.
     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
         assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE));
-        Self {
-            state,
-            aligned,
-            last_erased_dfu_page_index: None,
-        }
+        Self { state, aligned }
     }
 
     // Make sure we are running a booted firmware to avoid reverting to a bad state.
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 5b8076f81..eb96a9523 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -13,6 +13,7 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA
 pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
     dfu: DFU,
     state: BlockingFirmwareState<'d, STATE>,
+    last_erased_dfu_page_index: Option<usize>,
 }
 
 #[cfg(target_os = "none")]
@@ -91,6 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
         Self {
             dfu: config.dfu,
             state: BlockingFirmwareState::new(config.state, aligned),
+            last_erased_dfu_page_index: None,
         }
     }
 
@@ -107,7 +109,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
     /// proceed with updating the firmware as it must be signed with a
     /// corresponding private key (otherwise it could be malicious firmware).
     ///
-    /// Mark to trigger firmware swap on next boot if verify suceeds.
+    /// Mark to trigger firmware swap on next boot if verify succeeds.
     ///
     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
     /// been generated from a SHA-512 digest of the firmware bytes.
@@ -248,14 +250,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
             let sector_end = sector_start + DFU::ERASE_SIZE;
             // Determine if the current sector needs to be erased before writing.
             let need_erase = self
-                .state
                 .last_erased_dfu_page_index
                 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
 
             // If the sector needs to be erased, erase it and update the last erased sector index.
             if need_erase {
                 self.dfu.erase(sector_start as u32, sector_end as u32)?;
-                self.state.last_erased_dfu_page_index = Some(current_sector);
+                self.last_erased_dfu_page_index = Some(current_sector);
             }
 
             // Calculate the size of the data chunk that can be written in the current iteration.
@@ -293,7 +294,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
 pub struct BlockingFirmwareState<'d, STATE> {
     state: STATE,
     aligned: &'d mut [u8],
-    last_erased_dfu_page_index: Option<usize>,
 }
 
 impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
@@ -315,11 +315,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
     /// and written to.
     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
         assert_eq!(aligned.len(), STATE::WRITE_SIZE);
-        Self {
-            state,
-            aligned,
-            last_erased_dfu_page_index: None,
-        }
+        Self { state, aligned }
     }
 
     // Make sure we are running a booted firmware to avoid reverting to a bad state.

From 7dd974aa0dd88ad319971b81083f63a190d278bf Mon Sep 17 00:00:00 2001
From: Badr Bouslikhin <bouslikhin.badr@gmail.com>
Date: Mon, 12 Feb 2024 23:28:04 +0100
Subject: [PATCH 8/8] refactor(boot): use sector instead of page for
 consistency

---
 embassy-boot/src/firmware_updater/asynch.rs   | 16 ++++++++--------
 embassy-boot/src/firmware_updater/blocking.rs | 16 ++++++++--------
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index b76668136..3d211be65 100644
--- a/embassy-boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -13,7 +13,7 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA
 pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
     dfu: DFU,
     state: FirmwareState<'d, STATE>,
-    last_erased_dfu_page_index: Option<usize>,
+    last_erased_dfu_sector_index: Option<usize>,
 }
 
 #[cfg(target_os = "none")]
@@ -57,7 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
         Self {
             dfu: config.dfu,
             state: FirmwareState::new(config.state, aligned),
-            last_erased_dfu_page_index: None,
+            last_erased_dfu_sector_index: None,
         }
     }
 
@@ -180,7 +180,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
     /// It handles sector erasures and data writes while verifying the device is in a proper state
     /// for firmware updates. The function ensures that only unerased sectors are erased before
     /// writing and efficiently handles the writing process across sector boundaries and in
-    /// various configurations (data size, page size, etc.).
+    /// various configurations (data size, sector size, etc.).
     ///
     /// # Arguments
     ///
@@ -215,13 +215,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
             let sector_end = sector_start + DFU::ERASE_SIZE;
             // Determine if the current sector needs to be erased before writing.
             let need_erase = self
-                .last_erased_dfu_page_index
+                .last_erased_dfu_sector_index
                 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
 
             // If the sector needs to be erased, erase it and update the last erased sector index.
             if need_erase {
                 self.dfu.erase(sector_start as u32, sector_end as u32).await?;
-                self.last_erased_dfu_page_index = Some(current_sector);
+                self.last_erased_dfu_sector_index = Some(current_sector);
             }
 
             // Calculate the size of the data chunk that can be written in the current iteration.
@@ -380,7 +380,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_page_bigger_than_chunk() {
+    fn can_verify_sha1_sector_bigger_than_chunk() {
         let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
         let state = Partition::new(&flash, 0, 4096);
         let dfu = Partition::new(&flash, 65536, 65536);
@@ -404,7 +404,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_page_smaller_than_chunk() {
+    fn can_verify_sha1_sector_smaller_than_chunk() {
         let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
         let state = Partition::new(&flash, 0, 4096);
         let dfu = Partition::new(&flash, 65536, 65536);
@@ -428,7 +428,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_cross_page_boundary() {
+    fn can_verify_sha1_cross_sector_boundary() {
         let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
         let state = Partition::new(&flash, 0, 4096);
         let dfu = Partition::new(&flash, 65536, 65536);
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index eb96a9523..35772a856 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -13,7 +13,7 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA
 pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
     dfu: DFU,
     state: BlockingFirmwareState<'d, STATE>,
-    last_erased_dfu_page_index: Option<usize>,
+    last_erased_dfu_sector_index: Option<usize>,
 }
 
 #[cfg(target_os = "none")]
@@ -92,7 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
         Self {
             dfu: config.dfu,
             state: BlockingFirmwareState::new(config.state, aligned),
-            last_erased_dfu_page_index: None,
+            last_erased_dfu_sector_index: None,
         }
     }
 
@@ -215,7 +215,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
     /// It handles sector erasures and data writes while verifying the device is in a proper state
     /// for firmware updates. The function ensures that only unerased sectors are erased before
     /// writing and efficiently handles the writing process across sector boundaries and in
-    /// various configurations (data size, page size, etc.).
+    /// various configurations (data size, sector size, etc.).
     ///
     /// # Arguments
     ///
@@ -250,13 +250,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
             let sector_end = sector_start + DFU::ERASE_SIZE;
             // Determine if the current sector needs to be erased before writing.
             let need_erase = self
-                .last_erased_dfu_page_index
+                .last_erased_dfu_sector_index
                 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
 
             // If the sector needs to be erased, erase it and update the last erased sector index.
             if need_erase {
                 self.dfu.erase(sector_start as u32, sector_end as u32)?;
-                self.last_erased_dfu_page_index = Some(current_sector);
+                self.last_erased_dfu_sector_index = Some(current_sector);
             }
 
             // Calculate the size of the data chunk that can be written in the current iteration.
@@ -420,7 +420,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_page_bigger_than_chunk() {
+    fn can_verify_sha1_sector_bigger_than_chunk() {
         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
         let state = BlockingPartition::new(&flash, 0, 4096);
         let dfu = BlockingPartition::new(&flash, 65536, 65536);
@@ -446,7 +446,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_page_smaller_than_chunk() {
+    fn can_verify_sha1_sector_smaller_than_chunk() {
         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
         let state = BlockingPartition::new(&flash, 0, 4096);
         let dfu = BlockingPartition::new(&flash, 65536, 65536);
@@ -472,7 +472,7 @@ mod tests {
     }
 
     #[test]
-    fn can_verify_sha1_cross_page_boundary() {
+    fn can_verify_sha1_cross_sector_boundary() {
         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
         let state = BlockingPartition::new(&flash, 0, 4096);
         let dfu = BlockingPartition::new(&flash, 65536, 65536);