Skip to main content

esp_bootloader_esp_idf/
ota.rs

1//! # Over The Air Updates (OTA)
2//!
3//! ## Overview
4//! The OTA update mechanism allows a device to update itself based on data
5//! received while the normal firmware is running (for example, over Wi-Fi,
6//! Bluetooth or Ethernet).
7//!
8//! OTA requires configuring the Partition Tables of the device with at least
9//! two OTA app slot partitions (i.e., ota_0 and ota_1) and an OTA Data
10//! Partition.
11//!
12//! The OTA operation functions write a new app firmware image to whichever OTA
13//! app slot that is currently not selected for booting. Once the image is
14//! verified, the OTA Data partition is updated to specify that this image
15//! should be used for the next boot.
16//!
17//! Note: The prebuilt bootloaders provided by `espflash` _might not_ include
18//! OTA support. In that case you need to build the bootloader yourself.
19//!
20//! The general procedure to change the active slot
21//! - read the partition table [crate::partitions::read_partition_table]
22//! - find the Data/Ota partition
23//! - initialize [Ota]
24//! - read the current slot, change the current slot
25//!
26//! For more details see <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html>
27use crate::partitions::{
28    AppPartitionSubType,
29    DataPartitionSubType,
30    Error,
31    FlashRegion,
32    PartitionType,
33};
34
35const SLOT0_DATA_OFFSET: u32 = 0x0000;
36const SLOT1_DATA_OFFSET: u32 = 0x1000;
37
38const UNINITIALIZED_SEQUENCE: u32 = 0xffffffff;
39
40/// Representation of the current OTA-data slot.
41#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
42#[cfg_attr(feature = "defmt", derive(defmt::Format))]
43enum OtaDataSlot {
44    /// If there is a `firmware` app-partition it's used. Otherwise OTA-0
45    None,
46    /// OTA-0
47    Slot0,
48    /// OTA-1
49    Slot1,
50}
51
52impl OtaDataSlot {
53    /// The next logical OTA-data slot
54    fn next(&self) -> OtaDataSlot {
55        match self {
56            OtaDataSlot::None => OtaDataSlot::Slot0,
57            OtaDataSlot::Slot0 => OtaDataSlot::Slot1,
58            OtaDataSlot::Slot1 => OtaDataSlot::Slot0,
59        }
60    }
61
62    fn offset(&self) -> u32 {
63        match self {
64            OtaDataSlot::None => SLOT0_DATA_OFFSET,
65            OtaDataSlot::Slot0 => SLOT0_DATA_OFFSET,
66            OtaDataSlot::Slot1 => SLOT1_DATA_OFFSET,
67        }
68    }
69}
70
71/// OTA image states for checking operability of the app.
72#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash, strum::FromRepr)]
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74#[repr(u32)]
75pub enum OtaImageState {
76    /// Monitor the first boot. The bootloader will change this to
77    /// `PendingVerify` if auto-rollback is enabled.
78    ///
79    /// You want to set this state after activating a newly installed update.
80    New           = 0x0,
81
82    /// Bootloader changes [OtaImageState::New] to
83    /// [OtaImageState::PendingVerify] to indicate the app should confirm the
84    /// image as working.
85    PendingVerify = 0x1,
86
87    /// Set by the firmware once it's found to be working. The bootloader will
88    /// consider this Slot as working and continue to use it.
89    Valid         = 0x2,
90
91    /// Set by the firmware once it's found to be non-working.
92    ///
93    /// The bootloader will consider this Slot as non-working and not try to
94    /// boot it further.
95    Invalid       = 0x3,
96
97    /// The bootloader will change the state to [OtaImageState::Aborted] if the
98    /// application didn't change [OtaImageState::PendingVerify]
99    /// to either [OtaImageState::Valid] or [OtaImageState::Invalid].
100    Aborted       = 0x4,
101
102    /// Undefined. The bootloader won't make any assumptions about the working
103    /// state of this slot.
104    #[default]
105    Undefined     = 0xFFFFFFFF,
106}
107
108impl TryFrom<u32> for OtaImageState {
109    type Error = Error;
110
111    fn try_from(value: u32) -> Result<Self, Self::Error> {
112        OtaImageState::from_repr(value).ok_or(Error::Invalid)
113    }
114}
115
116/// OTA selection entry structure (two copies in the OTA data partition).
117/// Size of 32 bytes is friendly to flash encryption.
118#[derive(Debug, Clone, Copy, Default)]
119#[cfg_attr(feature = "defmt", derive(defmt::Format))]
120#[repr(C)]
121struct OtaSelectEntry {
122    /// OTA sequence number.
123    pub ota_seq: u32,
124    /// Sequence label (unused in the bootloader).
125    pub seq_label: [u8; 20],
126    /// OTA image state.
127    pub ota_state: OtaImageState,
128    /// CRC32 of the `ota_seq` field only.
129    pub crc: u32,
130}
131
132impl OtaSelectEntry {
133    fn read<'a, 'd>(region: &mut FlashRegion<'a, 'd>, offset: u32) -> Result<Self, Error> {
134        fn is_valid(buffer: &[u8]) -> bool {
135            let ota_seq = u32::from_le_bytes(unwrap!(buffer[0..4].try_into()));
136            let ota_state = u32::from_le_bytes(unwrap!(buffer[24..28].try_into()));
137            let crc = u32::from_le_bytes(unwrap!(buffer[28..32].try_into()));
138
139            if ota_seq == u32::MAX && ota_state == OtaImageState::Undefined as u32 {
140                return true;
141            }
142
143            if OtaImageState::try_from(ota_state).is_err() {
144                return false;
145            }
146
147            let calculated_crc = crate::crypto::Crc32::new().crc(&ota_seq.to_le_bytes());
148            if crc != calculated_crc {
149                return false;
150            }
151
152            true
153        }
154
155        let mut buffer = [0u8; 32];
156        region.read(offset, &mut buffer)?;
157
158        if !is_valid(&buffer) {
159            return Err(Error::Invalid);
160        }
161
162        let entry: *mut OtaSelectEntry = &mut buffer as *mut _ as *mut OtaSelectEntry;
163        let entry = unsafe { *entry };
164        Ok(entry)
165    }
166
167    fn write<'a, 'd>(
168        &mut self,
169        region: &mut FlashRegion<'a, 'd>,
170        offset: u32,
171    ) -> Result<(), Error> {
172        let bytes: &mut [u8; 32] = unwrap!(
173            unsafe { core::slice::from_raw_parts_mut(self as *mut _ as *mut u8, 0x20) }.try_into()
174        );
175        region.write(offset, bytes)?;
176
177        Ok(())
178    }
179}
180
181/// This is used to manipulate the OTA-data partition.
182///
183/// If you are looking for a more high-level way to do this, see [crate::ota_updater::OtaUpdater]
184#[derive(Debug)]
185#[cfg_attr(feature = "defmt", derive(defmt::Format))]
186pub struct Ota<'a, 'd> {
187    flash: FlashRegion<'a, 'd>,
188    ota_partition_count: usize,
189}
190
191impl<'a, 'd> Ota<'a, 'd> {
192    /// Create a [Ota] instance from the given [FlashRegion] and the count of OTA app partitions
193    /// (not including "firmware" and "test" partitions)
194    ///
195    /// # Errors
196    /// A [Error::InvalidPartition] if the given flash region
197    /// doesn't represent a Data/Ota partition or the size is unexpected.
198    ///
199    /// [Error::InvalidArgument] if the `ota_partition_count` exceeds the maximum or if it's 0.
200    pub fn new(
201        flash: FlashRegion<'a, 'd>,
202        ota_partition_count: usize,
203    ) -> Result<Ota<'a, 'd>, Error> {
204        if ota_partition_count == 0 || ota_partition_count > 16 {
205            return Err(Error::InvalidArgument);
206        }
207
208        if flash.capacity() != 0x2000
209            || flash.partition_type != PartitionType::Data(DataPartitionSubType::Ota)
210        {
211            return Err(Error::InvalidPartition {
212                expected_size: 0x2000,
213                expected_type: PartitionType::Data(DataPartitionSubType::Ota),
214            });
215        }
216
217        Ok(Ota {
218            flash,
219            ota_partition_count,
220        })
221    }
222
223    /// Returns the currently selected app partition.
224    ///
225    /// This might not be the booted partition if the bootloader failed to boot
226    /// the partition and felt back to the last known working app partition.
227    ///
228    /// See [crate::partitions::PartitionTable::booted_partition] to get the booted
229    /// partition.
230    pub fn current_app_partition(&mut self) -> Result<AppPartitionSubType, Error> {
231        let (seq0, seq1) = self.get_slot_seq()?;
232
233        let slot = if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
234            AppPartitionSubType::Factory
235        } else if seq0 == UNINITIALIZED_SEQUENCE {
236            AppPartitionSubType::from_ota_app_number(
237                ((seq1 - 1) % self.ota_partition_count as u32) as u8,
238            )?
239        } else if seq1 == UNINITIALIZED_SEQUENCE || seq0 > seq1 {
240            AppPartitionSubType::from_ota_app_number(
241                ((seq0 - 1) % self.ota_partition_count as u32) as u8,
242            )?
243        } else {
244            let counter = u32::max(seq0, seq1) - 1;
245            AppPartitionSubType::from_ota_app_number(
246                (counter % self.ota_partition_count as u32) as u8,
247            )?
248        };
249
250        Ok(slot)
251    }
252
253    fn get_slot_seq(&mut self) -> Result<(u32, u32), Error> {
254        let buffer1 = OtaSelectEntry::read(&mut self.flash, SLOT0_DATA_OFFSET)?;
255        let buffer2 = OtaSelectEntry::read(&mut self.flash, SLOT1_DATA_OFFSET)?;
256        let seq0 = buffer1.ota_seq;
257        let seq1 = buffer2.ota_seq;
258        Ok((seq0, seq1))
259    }
260
261    /// Sets the currently active OTA-slot.
262    ///
263    /// Passing [AppPartitionSubType::Factory] will reset the OTA-data.
264    ///
265    /// # Errors
266    ///
267    /// [Error::InvalidArgument] if [AppPartitionSubType::Test] is given or if the OTA app partition
268    /// number exceeds the value given to the constructor.
269    pub fn set_current_app_partition(&mut self, app: AppPartitionSubType) -> Result<(), Error> {
270        if app == AppPartitionSubType::Factory {
271            self.flash.write(SLOT0_DATA_OFFSET, &[0xffu8; 0x20])?;
272            self.flash.write(SLOT1_DATA_OFFSET, &[0xffu8; 0x20])?;
273            return Ok(());
274        }
275
276        if app == AppPartitionSubType::Test {
277            // cannot switch to the test partition - it's a partition
278            // which special built bootloaders will boot depending on the state of a pin
279            return Err(Error::InvalidArgument);
280        }
281
282        let ota_app_index = app.ota_app_number();
283        if ota_app_index >= self.ota_partition_count as u8 {
284            return Err(Error::InvalidArgument);
285        }
286
287        let current = self.current_app_partition()?;
288
289        // no need to update any sequence if the partition isn't changed
290        if current != app {
291            // the bootloader will look at the two slots in ota-data and get the highest sequence
292            // number
293            //
294            // the booted ota-app-partition is the sequence-nr modulo the number of
295            // ota-app-partitions
296
297            // calculate the needed increment of the sequence-number to select the requested OTA-app
298            // partition
299            let inc = if current == AppPartitionSubType::Factory {
300                (((app.ota_app_number()) as i32 + 1) + (self.ota_partition_count as i32)) as u32
301                    % self.ota_partition_count as u32
302            } else {
303                ((((app.ota_app_number()) as i32) - ((current.ota_app_number()) as i32))
304                    + (self.ota_partition_count as i32)) as u32
305                    % self.ota_partition_count as u32
306            };
307
308            // the slot we need to write the new sequence number to
309            let slot = self.current_slot()?.next();
310
311            let (seq0, seq1) = self.get_slot_seq()?;
312            let new_seq = {
313                if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
314                    // no ota-app partition is selected
315                    inc
316                } else if seq0 == UNINITIALIZED_SEQUENCE {
317                    // seq1 is the sequence number to increment
318                    seq1 + inc
319                } else if seq1 == UNINITIALIZED_SEQUENCE {
320                    // seq0 is the sequence number to increment
321                    seq0 + inc
322                } else {
323                    u32::max(seq0, seq1) + inc
324                }
325            };
326
327            let crc = crate::crypto::Crc32::new();
328            let checksum = crc.crc(&new_seq.to_le_bytes());
329
330            let mut buffer = OtaSelectEntry::read(&mut self.flash, slot.offset())?;
331            buffer.ota_seq = new_seq;
332            buffer.crc = checksum;
333            buffer.write(&mut self.flash, slot.offset())?;
334        }
335
336        Ok(())
337    }
338
339    // determine the current ota-data slot by checking the sequence numbers
340    fn current_slot(&mut self) -> Result<OtaDataSlot, Error> {
341        let (seq0, seq1) = self.get_slot_seq()?;
342
343        let slot = if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
344            OtaDataSlot::None
345        } else if seq0 == UNINITIALIZED_SEQUENCE {
346            OtaDataSlot::Slot1
347        } else if seq1 == UNINITIALIZED_SEQUENCE || seq0 > seq1 {
348            OtaDataSlot::Slot0
349        } else {
350            OtaDataSlot::Slot1
351        };
352        Ok(slot)
353    }
354
355    /// Set the [OtaImageState] of the currently selected slot.
356    ///
357    /// # Errors
358    /// A [Error::InvalidState] if no partition is currently selected.
359    pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> {
360        if let (UNINITIALIZED_SEQUENCE, UNINITIALIZED_SEQUENCE) = self.get_slot_seq()? {
361            Err(Error::InvalidState)
362        } else {
363            let offset = self.current_slot()?.offset();
364            let mut buffer = OtaSelectEntry::read(&mut self.flash, offset)?;
365            buffer.ota_state = state;
366            buffer.write(&mut self.flash, offset)?;
367            Ok(())
368        }
369    }
370
371    /// Get the [OtaImageState] of the currently selected slot.
372    ///
373    /// # Errors
374    /// A [Error::InvalidState] if no partition is currently selected.
375    pub fn current_ota_state(&mut self) -> Result<OtaImageState, Error> {
376        if let (UNINITIALIZED_SEQUENCE, UNINITIALIZED_SEQUENCE) = self.get_slot_seq()? {
377            Err(Error::InvalidState)
378        } else {
379            let offset = self.current_slot()?.offset();
380            let buffer = OtaSelectEntry::read(&mut self.flash, offset)?;
381            Ok(buffer.ota_state)
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use crate::partitions::{FlashStorage, PartitionEntry};
390
391    const PARTITION_RAW: [u8; 32] = [
392        0xaa, 0x50, // MAGIC
393        1,    // TYPE = DATA
394        0,    // SUBTYPE = OTA
395        0, 0, 0, 0, // OFFSET
396        0, 0x20, 0, 0, // LEN (0x2000)
397        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // LABEL
398        0, 0, 0, 0, // FLAGS
399    ];
400
401    const SLOT_INITIAL: &[u8] = &[
402        255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
403        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
404    ];
405
406    const SLOT_COUNT_1_UNDEFINED: &[u8] = &[
407        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
408        255, 255, 255, 255, 255, 255, 255, 255, 255, 154, 152, 67, 71,
409    ];
410
411    const SLOT_COUNT_1_VALID: &[u8] = &[
412        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
413        255, 255, 255, 255, 255, 2, 0, 0, 0, 154, 152, 67, 71,
414    ];
415
416    const SLOT_COUNT_2_NEW: &[u8] = &[
417        2, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
418        255, 255, 255, 255, 0, 0, 0, 0, 116, 55, 246, 85,
419    ];
420
421    const SLOT_COUNT_3_PENDING: &[u8] = &[
422        3, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
423        255, 255, 255, 255, 1, 0, 0, 0, 17, 80, 74, 237,
424    ];
425
426    fn ota_region<'a>(
427        flash: &'a mut FlashStorage<'static>,
428        binary: [u8; 32],
429    ) -> FlashRegion<'a, 'static> {
430        PartitionEntry { binary }.as_flash_region(flash)
431    }
432
433    fn init_ota_flash(flash: &mut FlashStorage<'static>) {
434        flash.erase(0, 0x2000).unwrap();
435    }
436
437    fn read_slot(flash: &mut FlashStorage<'static>, offset: u32) -> [u8; 0x20] {
438        let mut buf = [0u8; 0x20];
439        flash.read(offset, &mut buf).unwrap();
440        buf
441    }
442
443    #[test]
444    fn test_initial_state_and_next_slot() {
445        let mut flash = FlashStorage::new();
446        init_ota_flash(&mut flash);
447
448        let mock_region = ota_region(&mut flash, PARTITION_RAW);
449
450        let mut sut = Ota::new(mock_region, 2).unwrap();
451        assert_eq!(
452            sut.current_app_partition().unwrap(),
453            AppPartitionSubType::Factory
454        );
455        assert_eq!(
456            sut.current_ota_state(),
457            Err(crate::partitions::Error::InvalidState)
458        );
459        assert_eq!(
460            sut.set_current_ota_state(OtaImageState::New),
461            Err(crate::partitions::Error::InvalidState)
462        );
463        assert_eq!(
464            sut.current_ota_state(),
465            Err(crate::partitions::Error::InvalidState)
466        );
467
468        sut.set_current_app_partition(AppPartitionSubType::Ota0)
469            .unwrap();
470        assert_eq!(
471            sut.current_app_partition().unwrap(),
472            AppPartitionSubType::Ota0
473        );
474        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
475
476        assert_eq!(&read_slot(&mut flash, 0x0000)[..], SLOT_COUNT_1_UNDEFINED);
477        assert_eq!(&read_slot(&mut flash, 0x1000)[..], SLOT_INITIAL);
478    }
479
480    #[test]
481    fn test_slot0_valid_next_slot() {
482        let mut flash = FlashStorage::new();
483        init_ota_flash(&mut flash);
484        flash.write(0x0000, SLOT_COUNT_1_VALID).unwrap();
485        flash.write(0x1000, SLOT_INITIAL).unwrap();
486
487        let mock_region = ota_region(&mut flash, PARTITION_RAW);
488
489        let mut sut = Ota::new(mock_region, 2).unwrap();
490        assert_eq!(
491            sut.current_app_partition().unwrap(),
492            AppPartitionSubType::Ota0
493        );
494        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
495
496        sut.set_current_app_partition(AppPartitionSubType::Ota1)
497            .unwrap();
498        sut.set_current_ota_state(OtaImageState::New).unwrap();
499        assert_eq!(
500            sut.current_app_partition().unwrap(),
501            AppPartitionSubType::Ota1
502        );
503        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
504
505        assert_eq!(&read_slot(&mut flash, 0x0000)[..], SLOT_COUNT_1_VALID);
506        assert_eq!(&read_slot(&mut flash, 0x1000)[..], SLOT_COUNT_2_NEW);
507    }
508
509    #[test]
510    fn test_slot1_new_next_slot() {
511        let mut flash = FlashStorage::new();
512        init_ota_flash(&mut flash);
513        flash.write(0x0000, SLOT_COUNT_1_VALID).unwrap();
514        flash.write(0x1000, SLOT_COUNT_2_NEW).unwrap();
515
516        let mock_region = ota_region(&mut flash, PARTITION_RAW);
517
518        let mut sut = Ota::new(mock_region, 2).unwrap();
519        assert_eq!(
520            sut.current_app_partition().unwrap(),
521            AppPartitionSubType::Ota1
522        );
523        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
524
525        sut.set_current_app_partition(AppPartitionSubType::Ota0)
526            .unwrap();
527        sut.set_current_ota_state(OtaImageState::PendingVerify)
528            .unwrap();
529        assert_eq!(
530            sut.current_app_partition().unwrap(),
531            AppPartitionSubType::Ota0
532        );
533        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
534
535        assert_eq!(&read_slot(&mut flash, 0x0000)[..], SLOT_COUNT_3_PENDING);
536        assert_eq!(&read_slot(&mut flash, 0x1000)[..], SLOT_COUNT_2_NEW);
537    }
538
539    #[test]
540    fn test_multi_updates() {
541        let mut flash = FlashStorage::new();
542        init_ota_flash(&mut flash);
543
544        let mock_region = ota_region(&mut flash, PARTITION_RAW);
545
546        let mut sut = Ota::new(mock_region, 2).unwrap();
547        assert_eq!(
548            sut.current_app_partition().unwrap(),
549            AppPartitionSubType::Factory
550        );
551        assert_eq!(
552            sut.current_ota_state(),
553            Err(crate::partitions::Error::InvalidState)
554        );
555
556        sut.set_current_app_partition(AppPartitionSubType::Ota0)
557            .unwrap();
558        sut.set_current_ota_state(OtaImageState::PendingVerify)
559            .unwrap();
560        assert_eq!(
561            sut.current_app_partition().unwrap(),
562            AppPartitionSubType::Ota0
563        );
564        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
565
566        sut.set_current_app_partition(AppPartitionSubType::Ota1)
567            .unwrap();
568        sut.set_current_ota_state(OtaImageState::New).unwrap();
569        assert_eq!(
570            sut.current_app_partition().unwrap(),
571            AppPartitionSubType::Ota1
572        );
573        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
574
575        sut.set_current_app_partition(AppPartitionSubType::Ota0)
576            .unwrap();
577        sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
578        assert_eq!(
579            sut.current_app_partition().unwrap(),
580            AppPartitionSubType::Ota0
581        );
582        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
583
584        // setting same app partition again
585        sut.set_current_app_partition(AppPartitionSubType::Ota0)
586            .unwrap();
587        sut.set_current_ota_state(OtaImageState::Valid).unwrap();
588        assert_eq!(
589            sut.current_app_partition().unwrap(),
590            AppPartitionSubType::Ota0
591        );
592        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
593    }
594
595    #[test]
596    fn test_multi_updates_4_apps() {
597        let mut flash = FlashStorage::new();
598        init_ota_flash(&mut flash);
599
600        let mock_region = ota_region(&mut flash, PARTITION_RAW);
601
602        let mut sut = Ota::new(mock_region, 4).unwrap();
603        assert_eq!(
604            sut.current_app_partition().unwrap(),
605            AppPartitionSubType::Factory
606        );
607        assert_eq!(
608            sut.current_ota_state(),
609            Err(crate::partitions::Error::InvalidState)
610        );
611
612        sut.set_current_app_partition(AppPartitionSubType::Ota0)
613            .unwrap();
614        sut.set_current_ota_state(OtaImageState::PendingVerify)
615            .unwrap();
616        assert_eq!(
617            sut.current_app_partition().unwrap(),
618            AppPartitionSubType::Ota0
619        );
620        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
621
622        sut.set_current_app_partition(AppPartitionSubType::Ota1)
623            .unwrap();
624        sut.set_current_ota_state(OtaImageState::New).unwrap();
625        assert_eq!(
626            sut.current_app_partition().unwrap(),
627            AppPartitionSubType::Ota1
628        );
629        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
630
631        sut.set_current_app_partition(AppPartitionSubType::Ota2)
632            .unwrap();
633        sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
634        assert_eq!(
635            sut.current_app_partition().unwrap(),
636            AppPartitionSubType::Ota2
637        );
638        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
639
640        sut.set_current_app_partition(AppPartitionSubType::Ota3)
641            .unwrap();
642        sut.set_current_ota_state(OtaImageState::Valid).unwrap();
643        assert_eq!(
644            sut.current_app_partition().unwrap(),
645            AppPartitionSubType::Ota3
646        );
647        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
648
649        // going back to a previous app image
650        sut.set_current_app_partition(AppPartitionSubType::Ota2)
651            .unwrap();
652        sut.set_current_ota_state(OtaImageState::Invalid).unwrap();
653        assert_eq!(
654            sut.current_app_partition().unwrap(),
655            AppPartitionSubType::Ota2
656        );
657        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Invalid));
658
659        assert_eq!(
660            sut.set_current_app_partition(AppPartitionSubType::Ota5),
661            Err(crate::partitions::Error::InvalidArgument)
662        );
663
664        assert_eq!(
665            sut.set_current_app_partition(AppPartitionSubType::Test),
666            Err(crate::partitions::Error::InvalidArgument)
667        );
668    }
669
670    #[test]
671    fn test_multi_updates_skip_parts() {
672        let mut flash = FlashStorage::new();
673        init_ota_flash(&mut flash);
674
675        let mock_region = ota_region(&mut flash, PARTITION_RAW);
676
677        let mut sut = Ota::new(mock_region, 16).unwrap();
678        assert_eq!(
679            sut.current_app_partition().unwrap(),
680            AppPartitionSubType::Factory
681        );
682        assert_eq!(
683            sut.current_ota_state(),
684            Err(crate::partitions::Error::InvalidState)
685        );
686
687        sut.set_current_app_partition(AppPartitionSubType::Ota10)
688            .unwrap();
689        assert_eq!(
690            sut.current_app_partition().unwrap(),
691            AppPartitionSubType::Ota10
692        );
693        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
694
695        sut.set_current_app_partition(AppPartitionSubType::Ota14)
696            .unwrap();
697        assert_eq!(
698            sut.current_app_partition().unwrap(),
699            AppPartitionSubType::Ota14
700        );
701        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
702
703        sut.set_current_app_partition(AppPartitionSubType::Ota5)
704            .unwrap();
705        assert_eq!(
706            sut.current_app_partition().unwrap(),
707            AppPartitionSubType::Ota5
708        );
709        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
710    }
711
712    #[test]
713    fn test_ota_slot_next() {
714        assert_eq!(OtaDataSlot::None.next(), OtaDataSlot::Slot0);
715        assert_eq!(OtaDataSlot::Slot0.next(), OtaDataSlot::Slot1);
716        assert_eq!(OtaDataSlot::Slot1.next(), OtaDataSlot::Slot0);
717    }
718
719    #[test]
720    fn test_read_erased_slot() {
721        let mut flash = FlashStorage::new();
722        init_ota_flash(&mut flash);
723
724        let mut region = ota_region(&mut flash, PARTITION_RAW);
725        let entry = OtaSelectEntry::read(&mut region, SLOT0_DATA_OFFSET).unwrap();
726        assert_eq!(entry.ota_seq, UNINITIALIZED_SEQUENCE);
727        assert_eq!(entry.seq_label, [0xff; 20]);
728        assert_eq!(entry.ota_state, OtaImageState::Undefined);
729        assert_eq!(entry.crc, UNINITIALIZED_SEQUENCE);
730    }
731
732    #[test]
733    fn test_read_valid_slot() {
734        let mut flash = FlashStorage::new();
735        init_ota_flash(&mut flash);
736        flash.write(0x0000, SLOT_COUNT_1_VALID).unwrap();
737
738        let mut region = ota_region(&mut flash, PARTITION_RAW);
739        let entry = OtaSelectEntry::read(&mut region, SLOT0_DATA_OFFSET).unwrap();
740        assert_eq!(entry.ota_seq, 1);
741        assert_eq!(entry.ota_state, OtaImageState::Valid);
742    }
743
744    #[test]
745    fn test_read_rejects_bad_crc() {
746        let mut flash = FlashStorage::new();
747        init_ota_flash(&mut flash);
748
749        let mut slot = [0u8; 32];
750        slot.copy_from_slice(SLOT_COUNT_1_UNDEFINED);
751        slot[31] ^= 0xff;
752        flash.write(0x0000, &slot).unwrap();
753
754        let mut region = ota_region(&mut flash, PARTITION_RAW);
755        assert!(matches!(
756            OtaSelectEntry::read(&mut region, SLOT0_DATA_OFFSET),
757            Err(crate::partitions::Error::Invalid)
758        ));
759    }
760
761    #[test]
762    fn test_read_rejects_unknown_ota_state() {
763        let mut flash = FlashStorage::new();
764        init_ota_flash(&mut flash);
765
766        let ota_seq = 1u32;
767        let crc = crate::crypto::Crc32::new().crc(&ota_seq.to_le_bytes());
768        let mut slot = [0xffu8; 32];
769        slot[0..4].copy_from_slice(&ota_seq.to_le_bytes());
770        slot[24..28].copy_from_slice(&0x1234_5678u32.to_le_bytes());
771        slot[28..32].copy_from_slice(&crc.to_le_bytes());
772        flash.write(0x0000, &slot).unwrap();
773
774        let mut region = ota_region(&mut flash, PARTITION_RAW);
775        assert!(matches!(
776            OtaSelectEntry::read(&mut region, SLOT0_DATA_OFFSET),
777            Err(crate::partitions::Error::Invalid)
778        ));
779    }
780
781    #[test]
782    fn test_read_rejects_erased_seq_with_non_erased_state() {
783        let mut flash = FlashStorage::new();
784        init_ota_flash(&mut flash);
785
786        let mut slot = [0xffu8; 32];
787        slot[24..28].copy_from_slice(&(OtaImageState::Valid as u32).to_le_bytes());
788        flash.write(0x0000, &slot).unwrap();
789
790        let mut region = ota_region(&mut flash, PARTITION_RAW);
791        assert!(matches!(
792            OtaSelectEntry::read(&mut region, SLOT0_DATA_OFFSET),
793            Err(crate::partitions::Error::Invalid)
794        ));
795    }
796
797    #[test]
798    fn test_one_corrupt_slot_fails_current_app_partition() {
799        let mut flash = FlashStorage::new();
800        init_ota_flash(&mut flash);
801
802        let mut corrupt = [0u8; 32];
803        corrupt.copy_from_slice(SLOT_COUNT_1_UNDEFINED);
804        corrupt[31] ^= 0xff;
805        flash.write(0x0000, &corrupt).unwrap();
806        flash.write(0x1000, SLOT_COUNT_2_NEW).unwrap();
807
808        let region = ota_region(&mut flash, PARTITION_RAW);
809        let mut sut = Ota::new(region, 2).unwrap();
810        assert_eq!(
811            sut.current_app_partition(),
812            Err(crate::partitions::Error::Invalid)
813        );
814    }
815
816    #[test]
817    fn test_reset_to_factory_after_corrupt_ota_data() {
818        let mut flash = FlashStorage::new();
819        init_ota_flash(&mut flash);
820
821        let mut corrupt = [0u8; 32];
822        corrupt.copy_from_slice(SLOT_COUNT_1_UNDEFINED);
823        corrupt[31] ^= 0xff;
824        flash.write(0x0000, &corrupt).unwrap();
825        flash.write(0x1000, SLOT_COUNT_2_NEW).unwrap();
826
827        let region = ota_region(&mut flash, PARTITION_RAW);
828        let mut sut = Ota::new(region, 2).unwrap();
829        assert_eq!(
830            sut.current_app_partition(),
831            Err(crate::partitions::Error::Invalid)
832        );
833
834        sut.set_current_app_partition(AppPartitionSubType::Factory)
835            .unwrap();
836        assert_eq!(
837            sut.current_app_partition().unwrap(),
838            AppPartitionSubType::Factory
839        );
840        assert_eq!(&read_slot(&mut flash, 0x0000)[..], SLOT_INITIAL);
841        assert_eq!(&read_slot(&mut flash, 0x1000)[..], SLOT_INITIAL);
842    }
843}