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