embassy_boot/
lib.rs

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