diff options
Diffstat (limited to 'embassy-boot/src/lib.rs')
| -rw-r--r-- | embassy-boot/src/lib.rs | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs new file mode 100644 index 000000000..b4f03e01e --- /dev/null +++ b/embassy-boot/src/lib.rs | |||
| @@ -0,0 +1,323 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![allow(async_fn_in_trait)] | ||
| 3 | #![warn(missing_docs)] | ||
| 4 | #![doc = include_str!("../README.md")] | ||
| 5 | mod fmt; | ||
| 6 | |||
| 7 | mod boot_loader; | ||
| 8 | mod digest_adapters; | ||
| 9 | mod firmware_updater; | ||
| 10 | #[cfg(test)] | ||
| 11 | mod mem_flash; | ||
| 12 | #[cfg(test)] | ||
| 13 | mod test_flash; | ||
| 14 | |||
| 15 | // The expected value of the flash after an erase | ||
| 16 | // TODO: Use the value provided by NorFlash when available | ||
| 17 | pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | ||
| 18 | pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | ||
| 19 | pub use firmware_updater::{ | ||
| 20 | BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, | ||
| 21 | FirmwareUpdaterError, | ||
| 22 | }; | ||
| 23 | |||
| 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | ||
| 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | ||
| 26 | pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; | ||
| 27 | |||
| 28 | /// The state of the bootloader after running prepare. | ||
| 29 | #[derive(PartialEq, Eq, Debug)] | ||
| 30 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 31 | pub enum State { | ||
| 32 | /// Bootloader is ready to boot the active partition. | ||
| 33 | Boot, | ||
| 34 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. | ||
| 35 | Swap, | ||
| 36 | /// Application has received a request to reboot into DFU mode to apply an update. | ||
| 37 | DfuDetach, | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. | ||
| 41 | #[repr(align(32))] | ||
| 42 | pub struct AlignedBuffer<const N: usize>(pub [u8; N]); | ||
| 43 | |||
| 44 | impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> { | ||
| 45 | fn as_ref(&self) -> &[u8] { | ||
| 46 | &self.0 | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> { | ||
| 51 | fn as_mut(&mut self) -> &mut [u8] { | ||
| 52 | &mut self.0 | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | #[cfg(test)] | ||
| 57 | mod tests { | ||
| 58 | #![allow(unused_imports)] | ||
| 59 | |||
| 60 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||
| 61 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | ||
| 62 | use futures::executor::block_on; | ||
| 63 | |||
| 64 | use super::*; | ||
| 65 | use crate::boot_loader::BootLoaderConfig; | ||
| 66 | use crate::firmware_updater::FirmwareUpdaterConfig; | ||
| 67 | use crate::mem_flash::MemFlash; | ||
| 68 | use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; | ||
| 69 | |||
| 70 | /* | ||
| 71 | #[test] | ||
| 72 | fn test_bad_magic() { | ||
| 73 | let mut flash = MemFlash([0xff; 131072]); | ||
| 74 | let mut flash = SingleFlashConfig::new(&mut flash); | ||
| 75 | |||
| 76 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | ||
| 77 | |||
| 78 | assert_eq!( | ||
| 79 | bootloader.prepare_boot(&mut flash), | ||
| 80 | Err(BootError::BadMagic) | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | */ | ||
| 84 | |||
| 85 | #[test] | ||
| 86 | fn test_boot_state() { | ||
| 87 | let flash = BlockingTestFlash::new(BootLoaderConfig { | ||
| 88 | active: MemFlash::<57344, 4096, 4>::default(), | ||
| 89 | dfu: MemFlash::<61440, 4096, 4>::default(), | ||
| 90 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 91 | }); | ||
| 92 | |||
| 93 | flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); | ||
| 94 | |||
| 95 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 96 | active: flash.active(), | ||
| 97 | dfu: flash.dfu(), | ||
| 98 | state: flash.state(), | ||
| 99 | }); | ||
| 100 | |||
| 101 | let mut page = [0; 4096]; | ||
| 102 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 103 | } | ||
| 104 | |||
| 105 | #[test] | ||
| 106 | #[cfg(not(feature = "_verify"))] | ||
| 107 | fn test_swap_state() { | ||
| 108 | const FIRMWARE_SIZE: usize = 57344; | ||
| 109 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 110 | active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(), | ||
| 111 | dfu: MemFlash::<61440, 4096, 4>::default(), | ||
| 112 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 113 | }); | ||
| 114 | |||
| 115 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 116 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 117 | let mut aligned = [0; 4]; | ||
| 118 | |||
| 119 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 120 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 121 | |||
| 122 | let mut updater = FirmwareUpdater::new( | ||
| 123 | FirmwareUpdaterConfig { | ||
| 124 | dfu: flash.dfu(), | ||
| 125 | state: flash.state(), | ||
| 126 | }, | ||
| 127 | &mut aligned, | ||
| 128 | ); | ||
| 129 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 130 | block_on(updater.mark_updated()).unwrap(); | ||
| 131 | |||
| 132 | // Writing after marking updated is not allowed until marked as booted. | ||
| 133 | let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); | ||
| 134 | assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); | ||
| 135 | |||
| 136 | let flash = flash.into_blocking(); | ||
| 137 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 138 | active: flash.active(), | ||
| 139 | dfu: flash.dfu(), | ||
| 140 | state: flash.state(), | ||
| 141 | }); | ||
| 142 | |||
| 143 | let mut page = [0; 1024]; | ||
| 144 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 145 | |||
| 146 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 147 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 148 | assert_eq!(UPDATE, read_buf); | ||
| 149 | // First DFU page is untouched | ||
| 150 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 151 | assert_eq!(ORIGINAL, read_buf); | ||
| 152 | |||
| 153 | // Running again should cause a revert | ||
| 154 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 155 | |||
| 156 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 157 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 158 | assert_eq!(ORIGINAL, read_buf); | ||
| 159 | // Last DFU page is untouched | ||
| 160 | flash.dfu().read(0, &mut read_buf).unwrap(); | ||
| 161 | assert_eq!(UPDATE, read_buf); | ||
| 162 | |||
| 163 | // Mark as booted | ||
| 164 | let flash = flash.into_async(); | ||
| 165 | let mut updater = FirmwareUpdater::new( | ||
| 166 | FirmwareUpdaterConfig { | ||
| 167 | dfu: flash.dfu(), | ||
| 168 | state: flash.state(), | ||
| 169 | }, | ||
| 170 | &mut aligned, | ||
| 171 | ); | ||
| 172 | block_on(updater.mark_booted()).unwrap(); | ||
| 173 | |||
| 174 | let flash = flash.into_blocking(); | ||
| 175 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 176 | active: flash.active(), | ||
| 177 | dfu: flash.dfu(), | ||
| 178 | state: flash.state(), | ||
| 179 | }); | ||
| 180 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 181 | } | ||
| 182 | |||
| 183 | #[test] | ||
| 184 | #[cfg(not(feature = "_verify"))] | ||
| 185 | fn test_swap_state_active_page_biggest() { | ||
| 186 | const FIRMWARE_SIZE: usize = 12288; | ||
| 187 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 188 | active: MemFlash::<12288, 4096, 8>::random(), | ||
| 189 | dfu: MemFlash::<16384, 2048, 8>::random(), | ||
| 190 | state: MemFlash::<2048, 128, 4>::random(), | ||
| 191 | }); | ||
| 192 | |||
| 193 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 194 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 195 | let mut aligned = [0; 4]; | ||
| 196 | |||
| 197 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 198 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 199 | |||
| 200 | let mut updater = FirmwareUpdater::new( | ||
| 201 | FirmwareUpdaterConfig { | ||
| 202 | dfu: flash.dfu(), | ||
| 203 | state: flash.state(), | ||
| 204 | }, | ||
| 205 | &mut aligned, | ||
| 206 | ); | ||
| 207 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 208 | block_on(updater.mark_updated()).unwrap(); | ||
| 209 | |||
| 210 | let flash = flash.into_blocking(); | ||
| 211 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 212 | active: flash.active(), | ||
| 213 | dfu: flash.dfu(), | ||
| 214 | state: flash.state(), | ||
| 215 | }); | ||
| 216 | |||
| 217 | let mut page = [0; 4096]; | ||
| 218 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 219 | |||
| 220 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 221 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 222 | assert_eq!(UPDATE, read_buf); | ||
| 223 | // First DFU page is untouched | ||
| 224 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 225 | assert_eq!(ORIGINAL, read_buf); | ||
| 226 | } | ||
| 227 | |||
| 228 | #[test] | ||
| 229 | #[cfg(not(feature = "_verify"))] | ||
| 230 | fn test_swap_state_dfu_page_biggest() { | ||
| 231 | const FIRMWARE_SIZE: usize = 12288; | ||
| 232 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 233 | active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(), | ||
| 234 | dfu: MemFlash::<16384, 4096, 8>::random(), | ||
| 235 | state: MemFlash::<2048, 128, 4>::random(), | ||
| 236 | }); | ||
| 237 | |||
| 238 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 239 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 240 | let mut aligned = [0; 4]; | ||
| 241 | |||
| 242 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 243 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 244 | |||
| 245 | let mut updater = FirmwareUpdater::new( | ||
| 246 | FirmwareUpdaterConfig { | ||
| 247 | dfu: flash.dfu(), | ||
| 248 | state: flash.state(), | ||
| 249 | }, | ||
| 250 | &mut aligned, | ||
| 251 | ); | ||
| 252 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 253 | block_on(updater.mark_updated()).unwrap(); | ||
| 254 | |||
| 255 | let flash = flash.into_blocking(); | ||
| 256 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 257 | active: flash.active(), | ||
| 258 | dfu: flash.dfu(), | ||
| 259 | state: flash.state(), | ||
| 260 | }); | ||
| 261 | let mut page = [0; 4096]; | ||
| 262 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 263 | |||
| 264 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 265 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 266 | assert_eq!(UPDATE, read_buf); | ||
| 267 | // First DFU page is untouched | ||
| 268 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 269 | assert_eq!(ORIGINAL, read_buf); | ||
| 270 | } | ||
| 271 | |||
| 272 | #[test] | ||
| 273 | #[cfg(feature = "_verify")] | ||
| 274 | fn test_verify() { | ||
| 275 | // The following key setup is based on: | ||
| 276 | // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example | ||
| 277 | |||
| 278 | use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; | ||
| 279 | use rand::rngs::OsRng; | ||
| 280 | |||
| 281 | let mut csprng = OsRng {}; | ||
| 282 | let keypair = SigningKey::generate(&mut csprng); | ||
| 283 | |||
| 284 | let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; | ||
| 285 | let mut digest = Sha512::new(); | ||
| 286 | digest.update(&firmware); | ||
| 287 | let message = digest.finalize(); | ||
| 288 | let signature: Signature = keypair.sign(&message); | ||
| 289 | |||
| 290 | let public_key = keypair.verifying_key(); | ||
| 291 | |||
| 292 | // Setup flash | ||
| 293 | let flash = BlockingTestFlash::new(BootLoaderConfig { | ||
| 294 | active: MemFlash::<0, 0, 0>::default(), | ||
| 295 | dfu: MemFlash::<4096, 4096, 4>::default(), | ||
| 296 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 297 | }); | ||
| 298 | |||
| 299 | let firmware_len = firmware.len(); | ||
| 300 | |||
| 301 | let mut write_buf = [0; 4096]; | ||
| 302 | write_buf[0..firmware_len].copy_from_slice(firmware); | ||
| 303 | flash.dfu().write(0, &write_buf).unwrap(); | ||
| 304 | |||
| 305 | // On with the test | ||
| 306 | let flash = flash.into_async(); | ||
| 307 | let mut aligned = [0; 4]; | ||
| 308 | let mut updater = FirmwareUpdater::new( | ||
| 309 | FirmwareUpdaterConfig { | ||
| 310 | dfu: flash.dfu(), | ||
| 311 | state: flash.state(), | ||
| 312 | }, | ||
| 313 | &mut aligned, | ||
| 314 | ); | ||
| 315 | |||
| 316 | assert!(block_on(updater.verify_and_mark_updated( | ||
| 317 | &public_key.to_bytes(), | ||
| 318 | &signature.to_bytes(), | ||
| 319 | firmware_len as u32, | ||
| 320 | )) | ||
| 321 | .is_ok()); | ||
| 322 | } | ||
| 323 | } | ||
