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 UNINITALIZED_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: &'a mut 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(
169        flash: &'a mut FlashRegion<'a, F>,
170        ota_partition_count: usize,
171    ) -> Result<Ota<'a, F>, Error> {
172        if ota_partition_count == 0 || ota_partition_count > 16 {
173            return Err(Error::InvalidArgument);
174        }
175
176        if flash.capacity() != 0x2000
177            || flash.raw.partition_type() != PartitionType::Data(DataPartitionSubType::Ota)
178        {
179            return Err(Error::InvalidPartition {
180                expected_size: 0x2000,
181                expected_type: PartitionType::Data(DataPartitionSubType::Ota),
182            });
183        }
184
185        Ok(Ota {
186            flash,
187            ota_partition_count,
188        })
189    }
190
191    /// Returns the currently selected app partition.
192    ///
193    /// This might not be the booted partition if the bootloader failed to boot
194    /// the partition and felt back to the last known working app partition.
195    ///
196    /// See [crate::partitions::PartitionTable::booted_partition] to get the booted
197    /// partition.
198    pub fn current_app_partition(&mut self) -> Result<AppPartitionSubType, Error> {
199        let (seq0, seq1) = self.get_slot_seq()?;
200
201        let slot = if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE {
202            AppPartitionSubType::Factory
203        } else if seq0 == UNINITALIZED_SEQUENCE {
204            AppPartitionSubType::from_ota_app_number(
205                ((seq1 - 1) % self.ota_partition_count as u32) as u8,
206            )?
207        } else if seq1 == UNINITALIZED_SEQUENCE || seq0 > seq1 {
208            AppPartitionSubType::from_ota_app_number(
209                ((seq0 - 1) % self.ota_partition_count as u32) as u8,
210            )?
211        } else {
212            let counter = u32::max(seq0, seq1) - 1;
213            AppPartitionSubType::from_ota_app_number(
214                (counter % self.ota_partition_count as u32) as u8,
215            )?
216        };
217
218        Ok(slot)
219    }
220
221    fn get_slot_seq(&mut self) -> Result<(u32, u32), Error> {
222        let mut buffer1 = OtaSelectEntry::default();
223        let mut buffer2 = OtaSelectEntry::default();
224        self.flash.read(SLOT0_DATA_OFFSET, buffer1.as_bytes_mut())?;
225        self.flash.read(SLOT1_DATA_OFFSET, buffer2.as_bytes_mut())?;
226        let seq0 = buffer1.ota_seq;
227        let seq1 = buffer2.ota_seq;
228        Ok((seq0, seq1))
229    }
230
231    /// Sets the currently active OTA-slot.
232    ///
233    /// Passing [AppPartitionSubType::Factory] will reset the OTA-data.
234    ///
235    /// # Errors
236    ///
237    /// [Error::InvalidArgument] if [AppPartitionSubType::Test] is given or if the OTA app partition
238    /// number exceeds the value given to the constructor.
239    pub fn set_current_app_partition(&mut self, app: AppPartitionSubType) -> Result<(), Error> {
240        if app == AppPartitionSubType::Factory {
241            self.flash.write(SLOT0_DATA_OFFSET, &[0xffu8; 0x20])?;
242            self.flash.write(SLOT1_DATA_OFFSET, &[0xffu8; 0x20])?;
243            return Ok(());
244        }
245
246        if app == AppPartitionSubType::Test {
247            // cannot switch to the test partition - it's a partition
248            // which special built bootloaders will boot depending on the state of a pin
249            return Err(Error::InvalidArgument);
250        }
251
252        let ota_app_index = app.ota_app_number();
253        if ota_app_index >= self.ota_partition_count as u8 {
254            return Err(Error::InvalidArgument);
255        }
256
257        let current = self.current_app_partition()?;
258
259        // no need to update any sequence if the partition isn't changed
260        if current != app {
261            // the bootloader will look at the two slots in ota-data and get the highest sequence
262            // number
263            //
264            // the booted ota-app-partition is the sequence-nr modulo the number of
265            // ota-app-partitions
266
267            // calculate the needed increment of the sequence-number to select the requested OTA-app
268            // partition
269            let inc = if current == AppPartitionSubType::Factory {
270                (((app.ota_app_number()) as i32 + 1) + (self.ota_partition_count as i32)) as u32
271                    % self.ota_partition_count as u32
272            } else {
273                ((((app.ota_app_number()) as i32) - ((current.ota_app_number()) as i32))
274                    + (self.ota_partition_count as i32)) as u32
275                    % self.ota_partition_count as u32
276            };
277
278            // the slot we need to write the new sequence number to
279            let slot = self.current_slot()?.next();
280
281            let (seq0, seq1) = self.get_slot_seq()?;
282            let new_seq = {
283                if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE {
284                    // no ota-app partition is selected
285                    inc
286                } else if seq0 == UNINITALIZED_SEQUENCE {
287                    // seq1 is the sequence number to increment
288                    seq1 + inc
289                } else if seq1 == UNINITALIZED_SEQUENCE {
290                    // seq0 is the sequence number to increment
291                    seq0 + inc
292                } else {
293                    u32::max(seq0, seq1) + inc
294                }
295            };
296
297            let crc = crate::crypto::Crc32::new();
298            let checksum = crc.crc(&new_seq.to_le_bytes());
299
300            let mut buffer = OtaSelectEntry::default();
301            self.flash.read(slot.offset(), buffer.as_bytes_mut())?;
302            buffer.ota_seq = new_seq;
303            buffer.crc = checksum;
304            self.flash.write(slot.offset(), buffer.as_bytes_mut())?;
305        }
306
307        Ok(())
308    }
309
310    // determine the current ota-data slot by checking the sequence numbers
311    fn current_slot(&mut self) -> Result<OtaDataSlot, Error> {
312        let (seq0, seq1) = self.get_slot_seq()?;
313
314        let slot = if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE {
315            OtaDataSlot::None
316        } else if seq0 == UNINITALIZED_SEQUENCE {
317            OtaDataSlot::Slot1
318        } else if seq1 == UNINITALIZED_SEQUENCE || seq0 > seq1 {
319            OtaDataSlot::Slot0
320        } else {
321            OtaDataSlot::Slot1
322        };
323        Ok(slot)
324    }
325
326    /// Set the [OtaImageState] of the currently selected slot.
327    ///
328    /// # Errors
329    /// A [Error::InvalidState] if no partition is currently selected.
330    pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> {
331        if let (UNINITALIZED_SEQUENCE, UNINITALIZED_SEQUENCE) = self.get_slot_seq()? {
332            Err(Error::InvalidState)
333        } else {
334            let offset = self.current_slot()?.offset();
335            let mut buffer = OtaSelectEntry::default();
336            self.flash.read(offset, buffer.as_bytes_mut())?;
337            buffer.ota_state = state;
338            self.flash.write(offset, buffer.as_bytes_mut())?;
339            Ok(())
340        }
341    }
342
343    /// Get the [OtaImageState] of the currently selected slot.
344    ///
345    /// # Errors
346    /// A [Error::InvalidState] if no partition is currently selected.
347    pub fn current_ota_state(&mut self) -> Result<OtaImageState, Error> {
348        if let (UNINITALIZED_SEQUENCE, UNINITALIZED_SEQUENCE) = self.get_slot_seq()? {
349            Err(Error::InvalidState)
350        } else {
351            let offset = self.current_slot()?.offset();
352            let mut buffer = OtaSelectEntry::default();
353            self.flash.read(offset, buffer.as_bytes_mut())?;
354            Ok(buffer.ota_state)
355        }
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use crate::partitions::PartitionEntry;
363
364    struct MockFlash {
365        data: [u8; 0x2000],
366    }
367
368    impl embedded_storage::Storage for MockFlash {
369        fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
370            self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes);
371            Ok(())
372        }
373    }
374
375    impl embedded_storage::ReadStorage for MockFlash {
376        type Error = crate::partitions::Error;
377        fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> {
378            let l = buffer.len();
379            buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]);
380            Ok(())
381        }
382
383        fn capacity(&self) -> usize {
384            unimplemented!()
385        }
386    }
387
388    const PARTITION_RAW: [u8; 32] = [
389        0xaa, 0x50, // MAGIC
390        1,    // TYPE = DATA
391        0,    // SUBTYPE = OTA
392        0, 0, 0, 0, // OFFSET
393        0, 0x20, 0, 0, // LEN (0x2000)
394        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // LABEL
395        0, 0, 0, 0, // FLAGS
396    ];
397
398    const SLOT_INITIAL: &[u8] = &[
399        255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
400        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
401    ];
402
403    const SLOT_COUNT_1_UNDEFINED: &[u8] = &[
404        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
405        255, 255, 255, 255, 255, 255, 255, 255, 255, 154, 152, 67, 71,
406    ];
407
408    const SLOT_COUNT_1_VALID: &[u8] = &[
409        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
410        255, 255, 255, 255, 255, 2, 0, 0, 0, 154, 152, 67, 71,
411    ];
412
413    const SLOT_COUNT_2_NEW: &[u8] = &[
414        2, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
415        255, 255, 255, 255, 0, 0, 0, 0, 116, 55, 246, 85,
416    ];
417
418    const SLOT_COUNT_3_PENDING: &[u8] = &[
419        3, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
420        255, 255, 255, 255, 1, 0, 0, 0, 17, 80, 74, 237,
421    ];
422
423    #[test]
424    fn test_initial_state_and_next_slot() {
425        let mut binary = PARTITION_RAW;
426
427        let mock_entry = PartitionEntry {
428            binary: &mut binary,
429        };
430
431        let mut mock_flash = MockFlash {
432            data: [0xff; 0x2000],
433        };
434
435        let mut mock_region = FlashRegion {
436            raw: &mock_entry,
437            flash: &mut mock_flash,
438        };
439
440        let mut sut = Ota::new(&mut mock_region, 2).unwrap();
441        assert_eq!(
442            sut.current_app_partition().unwrap(),
443            AppPartitionSubType::Factory
444        );
445        assert_eq!(
446            sut.current_ota_state(),
447            Err(crate::partitions::Error::InvalidState)
448        );
449        assert_eq!(
450            sut.set_current_ota_state(OtaImageState::New),
451            Err(crate::partitions::Error::InvalidState)
452        );
453        assert_eq!(
454            sut.current_ota_state(),
455            Err(crate::partitions::Error::InvalidState)
456        );
457
458        sut.set_current_app_partition(AppPartitionSubType::Ota0)
459            .unwrap();
460        assert_eq!(
461            sut.current_app_partition().unwrap(),
462            AppPartitionSubType::Ota0
463        );
464        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
465
466        assert_eq!(SLOT_COUNT_1_UNDEFINED, &mock_flash.data[0x0000..][..0x20],);
467        assert_eq!(SLOT_INITIAL, &mock_flash.data[0x1000..][..0x20],);
468    }
469
470    #[test]
471    fn test_slot0_valid_next_slot() {
472        let mut binary = PARTITION_RAW;
473
474        let mock_entry = PartitionEntry {
475            binary: &mut binary,
476        };
477
478        let mut mock_flash = MockFlash {
479            data: [0xff; 0x2000],
480        };
481
482        mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
483        mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_INITIAL);
484
485        let mut mock_region = FlashRegion {
486            raw: &mock_entry,
487            flash: &mut mock_flash,
488        };
489
490        let mut sut = Ota::new(&mut mock_region, 2).unwrap();
491        assert_eq!(
492            sut.current_app_partition().unwrap(),
493            AppPartitionSubType::Ota0
494        );
495        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
496
497        sut.set_current_app_partition(AppPartitionSubType::Ota1)
498            .unwrap();
499        sut.set_current_ota_state(OtaImageState::New).unwrap();
500        assert_eq!(
501            sut.current_app_partition().unwrap(),
502            AppPartitionSubType::Ota1
503        );
504        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
505
506        assert_eq!(SLOT_COUNT_1_VALID, &mock_flash.data[0x0000..][..0x20],);
507        assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
508    }
509
510    #[test]
511    fn test_slot1_new_next_slot() {
512        let mut binary = PARTITION_RAW;
513
514        let mock_entry = PartitionEntry {
515            binary: &mut binary,
516        };
517
518        let mut mock_flash = MockFlash {
519            data: [0xff; 0x2000],
520        };
521
522        mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
523        mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_COUNT_2_NEW);
524
525        let mut mock_region = FlashRegion {
526            raw: &mock_entry,
527            flash: &mut mock_flash,
528        };
529
530        let mut sut = Ota::new(&mut mock_region, 2).unwrap();
531        assert_eq!(
532            sut.current_app_partition().unwrap(),
533            AppPartitionSubType::Ota1
534        );
535        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
536
537        sut.set_current_app_partition(AppPartitionSubType::Ota0)
538            .unwrap();
539        sut.set_current_ota_state(OtaImageState::PendingVerify)
540            .unwrap();
541        assert_eq!(
542            sut.current_app_partition().unwrap(),
543            AppPartitionSubType::Ota0
544        );
545        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
546
547        assert_eq!(SLOT_COUNT_3_PENDING, &mock_flash.data[0x0000..][..0x20],);
548        assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
549    }
550
551    #[test]
552    fn test_multi_updates() {
553        let mut binary = PARTITION_RAW;
554
555        let mock_entry = PartitionEntry {
556            binary: &mut binary,
557        };
558
559        let mut mock_flash = MockFlash {
560            data: [0xff; 0x2000],
561        };
562
563        let mut mock_region = FlashRegion {
564            raw: &mock_entry,
565            flash: &mut mock_flash,
566        };
567
568        let mut sut = Ota::new(&mut mock_region, 2).unwrap();
569        assert_eq!(
570            sut.current_app_partition().unwrap(),
571            AppPartitionSubType::Factory
572        );
573        assert_eq!(
574            sut.current_ota_state(),
575            Err(crate::partitions::Error::InvalidState)
576        );
577
578        sut.set_current_app_partition(AppPartitionSubType::Ota0)
579            .unwrap();
580        sut.set_current_ota_state(OtaImageState::PendingVerify)
581            .unwrap();
582        assert_eq!(
583            sut.current_app_partition().unwrap(),
584            AppPartitionSubType::Ota0
585        );
586        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
587
588        sut.set_current_app_partition(AppPartitionSubType::Ota1)
589            .unwrap();
590        sut.set_current_ota_state(OtaImageState::New).unwrap();
591        assert_eq!(
592            sut.current_app_partition().unwrap(),
593            AppPartitionSubType::Ota1
594        );
595        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
596
597        sut.set_current_app_partition(AppPartitionSubType::Ota0)
598            .unwrap();
599        sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
600        assert_eq!(
601            sut.current_app_partition().unwrap(),
602            AppPartitionSubType::Ota0
603        );
604        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
605
606        // setting same app partition again
607        sut.set_current_app_partition(AppPartitionSubType::Ota0)
608            .unwrap();
609        sut.set_current_ota_state(OtaImageState::Valid).unwrap();
610        assert_eq!(
611            sut.current_app_partition().unwrap(),
612            AppPartitionSubType::Ota0
613        );
614        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
615    }
616
617    #[test]
618    fn test_multi_updates_4_apps() {
619        let mut binary = PARTITION_RAW;
620
621        let mock_entry = PartitionEntry {
622            binary: &mut binary,
623        };
624
625        let mut mock_flash = MockFlash {
626            data: [0xff; 0x2000],
627        };
628
629        let mut mock_region = FlashRegion {
630            raw: &mock_entry,
631            flash: &mut mock_flash,
632        };
633
634        let mut sut = Ota::new(&mut mock_region, 4).unwrap();
635        assert_eq!(
636            sut.current_app_partition().unwrap(),
637            AppPartitionSubType::Factory
638        );
639        assert_eq!(
640            sut.current_ota_state(),
641            Err(crate::partitions::Error::InvalidState)
642        );
643
644        sut.set_current_app_partition(AppPartitionSubType::Ota0)
645            .unwrap();
646        sut.set_current_ota_state(OtaImageState::PendingVerify)
647            .unwrap();
648        assert_eq!(
649            sut.current_app_partition().unwrap(),
650            AppPartitionSubType::Ota0
651        );
652        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
653
654        sut.set_current_app_partition(AppPartitionSubType::Ota1)
655            .unwrap();
656        sut.set_current_ota_state(OtaImageState::New).unwrap();
657        assert_eq!(
658            sut.current_app_partition().unwrap(),
659            AppPartitionSubType::Ota1
660        );
661        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
662
663        sut.set_current_app_partition(AppPartitionSubType::Ota2)
664            .unwrap();
665        sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
666        assert_eq!(
667            sut.current_app_partition().unwrap(),
668            AppPartitionSubType::Ota2
669        );
670        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
671
672        sut.set_current_app_partition(AppPartitionSubType::Ota3)
673            .unwrap();
674        sut.set_current_ota_state(OtaImageState::Valid).unwrap();
675        assert_eq!(
676            sut.current_app_partition().unwrap(),
677            AppPartitionSubType::Ota3
678        );
679        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
680
681        // going back to a previous app image
682        sut.set_current_app_partition(AppPartitionSubType::Ota2)
683            .unwrap();
684        sut.set_current_ota_state(OtaImageState::Invalid).unwrap();
685        assert_eq!(
686            sut.current_app_partition().unwrap(),
687            AppPartitionSubType::Ota2
688        );
689        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Invalid));
690
691        assert_eq!(
692            sut.set_current_app_partition(AppPartitionSubType::Ota5),
693            Err(crate::partitions::Error::InvalidArgument)
694        );
695
696        assert_eq!(
697            sut.set_current_app_partition(AppPartitionSubType::Test),
698            Err(crate::partitions::Error::InvalidArgument)
699        );
700    }
701
702    #[test]
703    fn test_multi_updates_skip_parts() {
704        let mut binary = PARTITION_RAW;
705
706        let mock_entry = PartitionEntry {
707            binary: &mut binary,
708        };
709
710        let mut mock_flash = MockFlash {
711            data: [0xff; 0x2000],
712        };
713
714        let mut mock_region = FlashRegion {
715            raw: &mock_entry,
716            flash: &mut mock_flash,
717        };
718
719        let mut sut = Ota::new(&mut mock_region, 16).unwrap();
720        assert_eq!(
721            sut.current_app_partition().unwrap(),
722            AppPartitionSubType::Factory
723        );
724        assert_eq!(
725            sut.current_ota_state(),
726            Err(crate::partitions::Error::InvalidState)
727        );
728
729        sut.set_current_app_partition(AppPartitionSubType::Ota10)
730            .unwrap();
731        assert_eq!(
732            sut.current_app_partition().unwrap(),
733            AppPartitionSubType::Ota10
734        );
735        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
736
737        sut.set_current_app_partition(AppPartitionSubType::Ota14)
738            .unwrap();
739        assert_eq!(
740            sut.current_app_partition().unwrap(),
741            AppPartitionSubType::Ota14
742        );
743        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
744
745        sut.set_current_app_partition(AppPartitionSubType::Ota5)
746            .unwrap();
747        assert_eq!(
748            sut.current_app_partition().unwrap(),
749            AppPartitionSubType::Ota5
750        );
751        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
752    }
753
754    #[test]
755    fn test_ota_slot_next() {
756        assert_eq!(OtaDataSlot::None.next(), OtaDataSlot::Slot0);
757        assert_eq!(OtaDataSlot::Slot0.next(), OtaDataSlot::Slot1);
758        assert_eq!(OtaDataSlot::Slot1.next(), OtaDataSlot::Slot0);
759    }
760}