aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-boot/src/firmware_updater/asynch.rs139
-rw-r--r--embassy-boot/src/firmware_updater/blocking.rs144
2 files changed, 266 insertions, 17 deletions
diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index a7c360a35..26f65f295 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
13pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { 13pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU, 14 dfu: DFU,
15 state: FirmwareState<'d, STATE>, 15 state: FirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
16} 17}
17 18
18#[cfg(target_os = "none")] 19#[cfg(target_os = "none")]
@@ -56,6 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
56 Self { 57 Self {
57 dfu: config.dfu, 58 dfu: config.dfu,
58 state: FirmwareState::new(config.state, aligned), 59 state: FirmwareState::new(config.state, aligned),
60 last_erased_dfu_sector_index: None,
59 } 61 }
60 } 62 }
61 63
@@ -72,7 +74,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
72 /// proceed with updating the firmware as it must be signed with a 74 /// proceed with updating the firmware as it must be signed with a
73 /// corresponding private key (otherwise it could be malicious firmware). 75 /// corresponding private key (otherwise it could be malicious firmware).
74 /// 76 ///
75 /// Mark to trigger firmware swap on next boot if verify suceeds. 77 /// Mark to trigger firmware swap on next boot if verify succeeds.
76 /// 78 ///
77 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have 79 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
78 /// been generated from a SHA-512 digest of the firmware bytes. 80 /// been generated from a SHA-512 digest of the firmware bytes.
@@ -172,21 +174,68 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
172 self.state.mark_booted().await 174 self.state.mark_booted().await
173 } 175 }
174 176
175 /// Write data to a flash page. 177 /// Writes firmware data to the device.
176 /// 178 ///
177 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. 179 /// This function writes the given data to the firmware area starting at the specified offset.
180 /// It handles sector erasures and data writes while verifying the device is in a proper state
181 /// for firmware updates. The function ensures that only unerased sectors are erased before
182 /// writing and efficiently handles the writing process across sector boundaries and in
183 /// various configurations (data size, sector size, etc.).
178 /// 184 ///
179 /// # Safety 185 /// # Arguments
186 ///
187 /// * `offset` - The starting offset within the firmware area where data writing should begin.
188 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
189 /// multiple of NorFlash WRITE_SIZE.
190 ///
191 /// # Returns
192 ///
193 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
194 ///
195 /// # Errors
180 /// 196 ///
181 /// Failing to meet alignment and size requirements may result in a panic. 197 /// This function will return an error if:
198 ///
199 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
200 /// - There is a failure erasing a sector before writing.
201 /// - There is a failure writing data to the device.
182 pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { 202 pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
183 assert!(data.len() >= DFU::ERASE_SIZE); 203 // Make sure we are running a booted firmware to avoid reverting to a bad state.
184
185 self.state.verify_booted().await?; 204 self.state.verify_booted().await?;
186 205
187 self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; 206 // Initialize variables to keep track of the remaining data and the current offset.
207 let mut remaining_data = data;
208 let mut offset = offset;
209
210 // Continue writing as long as there is data left to write.
211 while !remaining_data.is_empty() {
212 // Compute the current sector and its boundaries.
213 let current_sector = offset / DFU::ERASE_SIZE;
214 let sector_start = current_sector * DFU::ERASE_SIZE;
215 let sector_end = sector_start + DFU::ERASE_SIZE;
216 // Determine if the current sector needs to be erased before writing.
217 let need_erase = self
218 .last_erased_dfu_sector_index
219 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
220
221 // If the sector needs to be erased, erase it and update the last erased sector index.
222 if need_erase {
223 self.dfu.erase(sector_start as u32, sector_end as u32).await?;
224 self.last_erased_dfu_sector_index = Some(current_sector);
225 }
226
227 // Calculate the size of the data chunk that can be written in the current iteration.
228 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
229 // Split the data to get the current chunk to be written and the remaining data.
230 let (data_chunk, rest) = remaining_data.split_at(write_size);
188 231
189 self.dfu.write(offset as u32, data).await?; 232 // Write the current data chunk.
233 self.dfu.write(offset as u32, data_chunk).await?;
234
235 // Update the offset and remaining data for the next iteration.
236 remaining_data = rest;
237 offset += write_size;
238 }
190 239
191 Ok(()) 240 Ok(())
192 } 241 }
@@ -338,4 +387,76 @@ mod tests {
338 387
339 assert_eq!(Sha1::digest(update).as_slice(), hash); 388 assert_eq!(Sha1::digest(update).as_slice(), hash);
340 } 389 }
390
391 #[test]
392 fn can_verify_sha1_sector_bigger_than_chunk() {
393 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
394 let state = Partition::new(&flash, 0, 4096);
395 let dfu = Partition::new(&flash, 65536, 65536);
396 let mut aligned = [0; 8];
397
398 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
399 let mut to_write = [0; 4096];
400 to_write[..7].copy_from_slice(update.as_slice());
401
402 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
403 let mut offset = 0;
404 for chunk in to_write.chunks(1024) {
405 block_on(updater.write_firmware(offset, chunk)).unwrap();
406 offset += chunk.len();
407 }
408 let mut chunk_buf = [0; 2];
409 let mut hash = [0; 20];
410 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
411
412 assert_eq!(Sha1::digest(update).as_slice(), hash);
413 }
414
415 #[test]
416 fn can_verify_sha1_sector_smaller_than_chunk() {
417 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
418 let state = Partition::new(&flash, 0, 4096);
419 let dfu = Partition::new(&flash, 65536, 65536);
420 let mut aligned = [0; 8];
421
422 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
423 let mut to_write = [0; 4096];
424 to_write[..7].copy_from_slice(update.as_slice());
425
426 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
427 let mut offset = 0;
428 for chunk in to_write.chunks(2048) {
429 block_on(updater.write_firmware(offset, chunk)).unwrap();
430 offset += chunk.len();
431 }
432 let mut chunk_buf = [0; 2];
433 let mut hash = [0; 20];
434 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
435
436 assert_eq!(Sha1::digest(update).as_slice(), hash);
437 }
438
439 #[test]
440 fn can_verify_sha1_cross_sector_boundary() {
441 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
442 let state = Partition::new(&flash, 0, 4096);
443 let dfu = Partition::new(&flash, 65536, 65536);
444 let mut aligned = [0; 8];
445
446 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
447 let mut to_write = [0; 4096];
448 to_write[..7].copy_from_slice(update.as_slice());
449
450 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
451 let mut offset = 0;
452 for chunk in to_write.chunks(896) {
453 block_on(updater.write_firmware(offset, chunk)).unwrap();
454 offset += chunk.len();
455 }
456 let mut chunk_buf = [0; 2];
457 let mut hash = [0; 20];
458 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
459
460 assert_eq!(Sha1::digest(update).as_slice(), hash);
461 }
341} 462}
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 4044871f0..35772a856 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
13pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { 13pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU, 14 dfu: DFU,
15 state: BlockingFirmwareState<'d, STATE>, 15 state: BlockingFirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
16} 17}
17 18
18#[cfg(target_os = "none")] 19#[cfg(target_os = "none")]
@@ -91,6 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
91 Self { 92 Self {
92 dfu: config.dfu, 93 dfu: config.dfu,
93 state: BlockingFirmwareState::new(config.state, aligned), 94 state: BlockingFirmwareState::new(config.state, aligned),
95 last_erased_dfu_sector_index: None,
94 } 96 }
95 } 97 }
96 98
@@ -107,7 +109,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
107 /// proceed with updating the firmware as it must be signed with a 109 /// proceed with updating the firmware as it must be signed with a
108 /// corresponding private key (otherwise it could be malicious firmware). 110 /// corresponding private key (otherwise it could be malicious firmware).
109 /// 111 ///
110 /// Mark to trigger firmware swap on next boot if verify suceeds. 112 /// Mark to trigger firmware swap on next boot if verify succeeds.
111 /// 113 ///
112 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have 114 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
113 /// been generated from a SHA-512 digest of the firmware bytes. 115 /// been generated from a SHA-512 digest of the firmware bytes.
@@ -207,20 +209,68 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
207 self.state.mark_booted() 209 self.state.mark_booted()
208 } 210 }
209 211
210 /// Write data to a flash page. 212 /// Writes firmware data to the device.
211 /// 213 ///
212 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. 214 /// This function writes the given data to the firmware area starting at the specified offset.
215 /// It handles sector erasures and data writes while verifying the device is in a proper state
216 /// for firmware updates. The function ensures that only unerased sectors are erased before
217 /// writing and efficiently handles the writing process across sector boundaries and in
218 /// various configurations (data size, sector size, etc.).
213 /// 219 ///
214 /// # Safety 220 /// # Arguments
221 ///
222 /// * `offset` - The starting offset within the firmware area where data writing should begin.
223 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
224 /// multiple of NorFlash WRITE_SIZE.
225 ///
226 /// # Returns
227 ///
228 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
215 /// 229 ///
216 /// Failing to meet alignment and size requirements may result in a panic. 230 /// # Errors
231 ///
232 /// This function will return an error if:
233 ///
234 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
235 /// - There is a failure erasing a sector before writing.
236 /// - There is a failure writing data to the device.
217 pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { 237 pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
218 assert!(data.len() >= DFU::ERASE_SIZE); 238 // Make sure we are running a booted firmware to avoid reverting to a bad state.
219 self.state.verify_booted()?; 239 self.state.verify_booted()?;
220 240
221 self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; 241 // Initialize variables to keep track of the remaining data and the current offset.
242 let mut remaining_data = data;
243 let mut offset = offset;
244
245 // Continue writing as long as there is data left to write.
246 while !remaining_data.is_empty() {
247 // Compute the current sector and its boundaries.
248 let current_sector = offset / DFU::ERASE_SIZE;
249 let sector_start = current_sector * DFU::ERASE_SIZE;
250 let sector_end = sector_start + DFU::ERASE_SIZE;
251 // Determine if the current sector needs to be erased before writing.
252 let need_erase = self
253 .last_erased_dfu_sector_index
254 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
255
256 // If the sector needs to be erased, erase it and update the last erased sector index.
257 if need_erase {
258 self.dfu.erase(sector_start as u32, sector_end as u32)?;
259 self.last_erased_dfu_sector_index = Some(current_sector);
260 }
261
262 // Calculate the size of the data chunk that can be written in the current iteration.
263 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
264 // Split the data to get the current chunk to be written and the remaining data.
265 let (data_chunk, rest) = remaining_data.split_at(write_size);
222 266
223 self.dfu.write(offset as u32, data)?; 267 // Write the current data chunk.
268 self.dfu.write(offset as u32, data_chunk)?;
269
270 // Update the offset and remaining data for the next iteration.
271 remaining_data = rest;
272 offset += write_size;
273 }
224 274
225 Ok(()) 275 Ok(())
226 } 276 }
@@ -368,4 +418,82 @@ mod tests {
368 418
369 assert_eq!(Sha1::digest(update).as_slice(), hash); 419 assert_eq!(Sha1::digest(update).as_slice(), hash);
370 } 420 }
421
422 #[test]
423 fn can_verify_sha1_sector_bigger_than_chunk() {
424 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
425 let state = BlockingPartition::new(&flash, 0, 4096);
426 let dfu = BlockingPartition::new(&flash, 65536, 65536);
427 let mut aligned = [0; 8];
428
429 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
430 let mut to_write = [0; 4096];
431 to_write[..7].copy_from_slice(update.as_slice());
432
433 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
434 let mut offset = 0;
435 for chunk in to_write.chunks(1024) {
436 updater.write_firmware(offset, chunk).unwrap();
437 offset += chunk.len();
438 }
439 let mut chunk_buf = [0; 2];
440 let mut hash = [0; 20];
441 updater
442 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
443 .unwrap();
444
445 assert_eq!(Sha1::digest(update).as_slice(), hash);
446 }
447
448 #[test]
449 fn can_verify_sha1_sector_smaller_than_chunk() {
450 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
451 let state = BlockingPartition::new(&flash, 0, 4096);
452 let dfu = BlockingPartition::new(&flash, 65536, 65536);
453 let mut aligned = [0; 8];
454
455 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
456 let mut to_write = [0; 4096];
457 to_write[..7].copy_from_slice(update.as_slice());
458
459 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
460 let mut offset = 0;
461 for chunk in to_write.chunks(2048) {
462 updater.write_firmware(offset, chunk).unwrap();
463 offset += chunk.len();
464 }
465 let mut chunk_buf = [0; 2];
466 let mut hash = [0; 20];
467 updater
468 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
469 .unwrap();
470
471 assert_eq!(Sha1::digest(update).as_slice(), hash);
472 }
473
474 #[test]
475 fn can_verify_sha1_cross_sector_boundary() {
476 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
477 let state = BlockingPartition::new(&flash, 0, 4096);
478 let dfu = BlockingPartition::new(&flash, 65536, 65536);
479 let mut aligned = [0; 8];
480
481 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
482 let mut to_write = [0; 4096];
483 to_write[..7].copy_from_slice(update.as_slice());
484
485 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
486 let mut offset = 0;
487 for chunk in to_write.chunks(896) {
488 updater.write_firmware(offset, chunk).unwrap();
489 offset += chunk.len();
490 }
491 let mut chunk_buf = [0; 2];
492 let mut hash = [0; 20];
493 updater
494 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
495 .unwrap();
496
497 assert_eq!(Sha1::digest(update).as_slice(), hash);
498 }
371} 499}