1 // Copyright (C) 2024  Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use super::partition::{MetadataBytes, MetadataParseError, SlotBlock};
16 use super::{
17     BootTarget, BootToken, Bootability, Error, Manager, OneShot, RecoveryTarget, Slot,
18     SlotIterator, Suffix, UnbootableReason,
19 };
20 
21 use core::convert::TryInto;
22 use core::iter::zip;
23 use core::mem::size_of;
24 use core::ops::{BitAnd, BitOr, Not, Shl, Shr};
25 use crc32fast::Hasher;
26 use zerocopy::byteorder::little_endian::U32 as LittleEndianU32;
27 use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref};
28 
29 extern crate static_assertions;
30 
31 const MAX_SLOTS: u8 = 4;
32 
33 // TODO(b/332338968): remove the manual field definitions and use bindgen definitions.
34 
35 // Helper function to extract values from bitfields.
36 // Preconditions:
37 // 1) All bits in a bitfield are consecutive.
38 // 1a) No fields interleave their bits.
39 // 2) `offset` defines the position of the least significant bit in the field.
40 // 3) If a bit is set in `mask`, all bits of lower significance are set.
41 // 4) If a bit is NOT set in `mask`, all bits of greater significanec are NOT set.
get_field<N, R>(base: N, offset: N, mask: N) -> R where N: Shr<Output = N> + BitAnd<Output = N>, R: Default + TryFrom<N>,42 fn get_field<N, R>(base: N, offset: N, mask: N) -> R
43 where
44     N: Shr<Output = N> + BitAnd<Output = N>,
45     R: Default + TryFrom<N>,
46 {
47     ((base >> offset) & mask).try_into().unwrap_or_default()
48 }
49 
50 // Helper function to set values in bit fields.
51 // All the preconditions for `get_field` apply.
52 // Returns the modified field. It is the caller's responsibility
53 // to assign the result appropriately.
set_field<N, R>(base: N, val: R, offset: N, mask: N) -> N where N: Copy + Shl<Output = N> + BitAnd<Output = N> + BitOr<Output = N> + Not<Output = N>, R: Into<N>,54 fn set_field<N, R>(base: N, val: R, offset: N, mask: N) -> N
55 where
56     N: Copy + Shl<Output = N> + BitAnd<Output = N> + BitOr<Output = N> + Not<Output = N>,
57     R: Into<N>,
58 {
59     (base & !(mask << offset)) | ((val.into() & mask) << offset)
60 }
61 
62 const DEFAULT_PRIORITY: u8 = 7;
63 const DEFAULT_RETRIES: u8 = 7;
64 
65 /// Android reference implementation for slot-specific metadata.
66 /// See `BootloaderControl` for more background information.
67 ///
68 /// Does NOT contain unbootable reason information.
69 #[repr(C, packed)]
70 #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
71 struct SlotMetaData(u16);
72 
73 impl SlotMetaData {
74     const PRIORITY_MASK: u16 = 0b1111;
75     const PRIORITY_OFFSET: u16 = 0;
76 
77     const TRIES_MASK: u16 = 0b111;
78     const TRIES_OFFSET: u16 = 4;
79 
80     const SUCCESSFUL_MASK: u16 = 0b1;
81     const SUCCESSFUL_OFFSET: u16 = 7;
82 
83     const VERITY_CORRUPTED_MASK: u16 = 0b1;
84     const VERITY_CORRUPTED_OFFSET: u16 = 8;
85 
priority(&self) -> u886     fn priority(&self) -> u8 {
87         get_field(self.0, Self::PRIORITY_OFFSET, Self::PRIORITY_MASK)
88     }
set_priority(&mut self, priority: u8)89     fn set_priority(&mut self, priority: u8) {
90         self.0 = set_field(self.0, priority, Self::PRIORITY_OFFSET, Self::PRIORITY_MASK)
91     }
92 
tries(&self) -> u893     fn tries(&self) -> u8 {
94         get_field(self.0, Self::TRIES_OFFSET, Self::TRIES_MASK)
95     }
set_tries(&mut self, tries: u8)96     fn set_tries(&mut self, tries: u8) {
97         self.0 = set_field(self.0, tries, Self::TRIES_OFFSET, Self::TRIES_MASK)
98     }
99 
successful(&self) -> bool100     fn successful(&self) -> bool {
101         get_field::<_, u8>(self.0, Self::SUCCESSFUL_OFFSET, Self::SUCCESSFUL_MASK) != 0
102     }
set_successful(&mut self, successful: bool)103     fn set_successful(&mut self, successful: bool) {
104         self.0 = set_field(self.0, successful, Self::SUCCESSFUL_OFFSET, Self::SUCCESSFUL_MASK);
105     }
106 
verity_corrupted(&self) -> bool107     fn verity_corrupted(&self) -> bool {
108         get_field::<_, u8>(self.0, Self::VERITY_CORRUPTED_OFFSET, Self::VERITY_CORRUPTED_MASK) != 0
109     }
set_verity_corrupted(&mut self, verity_corrupted: bool)110     fn set_verity_corrupted(&mut self, verity_corrupted: bool) {
111         self.0 = set_field(
112             self.0,
113             verity_corrupted,
114             Self::VERITY_CORRUPTED_OFFSET,
115             Self::VERITY_CORRUPTED_MASK,
116         );
117     }
118 }
119 static_assertions::const_assert_eq!(
120     core::mem::size_of::<SlotMetaData>(),
121     core::mem::size_of::<u16>()
122 );
123 
124 impl Default for SlotMetaData {
default() -> Self125     fn default() -> Self {
126         let mut val = Self(0);
127         val.set_priority(DEFAULT_PRIORITY);
128         val.set_tries(DEFAULT_RETRIES);
129 
130         val
131     }
132 }
133 
134 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
135 #[repr(C, packed)]
136 struct ControlBits(u16);
137 
138 impl ControlBits {
139     const NB_SLOT_MASK: u16 = 0b111;
140     const NB_SLOT_OFFSET: u16 = 0;
141 
142     const RECOVERY_TRIES_MASK: u16 = 0b111;
143     const RECOVERY_TRIES_OFFSET: u16 = 3;
144 
145     const MERGE_STATUS_MASK: u16 = 0b111;
146     const MERGE_STATUS_OFFSET: u16 = 6;
147 
nb_slots(&self) -> u8148     fn nb_slots(&self) -> u8 {
149         core::cmp::min(get_field(self.0, Self::NB_SLOT_OFFSET, Self::NB_SLOT_MASK), MAX_SLOTS)
150     }
set_nb_slots(&mut self, nb_slots: u8)151     fn set_nb_slots(&mut self, nb_slots: u8) {
152         self.0 = set_field(
153             self.0,
154             core::cmp::min(nb_slots, MAX_SLOTS),
155             Self::NB_SLOT_OFFSET,
156             Self::NB_SLOT_MASK,
157         );
158     }
159 
recovery_tries(&self) -> u8160     fn recovery_tries(&self) -> u8 {
161         get_field(self.0, Self::RECOVERY_TRIES_OFFSET, Self::RECOVERY_TRIES_MASK)
162     }
set_recovery_tries(&mut self, recovery_tries: u8)163     fn set_recovery_tries(&mut self, recovery_tries: u8) {
164         self.0 = set_field(
165             self.0,
166             recovery_tries,
167             Self::RECOVERY_TRIES_OFFSET,
168             Self::RECOVERY_TRIES_MASK,
169         );
170     }
171 
merge_status(&self) -> u8172     fn merge_status(&self) -> u8 {
173         get_field(self.0, Self::MERGE_STATUS_OFFSET, Self::MERGE_STATUS_MASK)
174     }
set_merge_status(&mut self, merge_status: u8)175     fn set_merge_status(&mut self, merge_status: u8) {
176         self.0 =
177             set_field(self.0, merge_status, Self::MERGE_STATUS_OFFSET, Self::MERGE_STATUS_MASK);
178     }
179 }
180 
181 const BOOT_CTRL_MAGIC: u32 = 0x42414342;
182 const BOOT_CTRL_VERSION: u8 = 1;
183 
184 /// The reference implementation for Android A/B bootloader message structures.
185 /// It is designed to be put in the `slot_suffix` field of the `bootloader_message`
186 /// structure described bootloader_message.h.
187 ///
188 /// See //hardware/interfaces/boot/1.1/default/boot_control/libboot_control.cpp
189 /// and //hardware/interfaces/boot/1.1/default/boot_control/include/private/boot_control_definition.h
190 /// for structure definition and semantics.
191 ///
192 /// Does NOT support oneshots
193 #[repr(C, packed)]
194 #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
195 struct BootloaderControl {
196     slot_suffix: [u8; 4],
197     magic: u32,
198     version: u8,
199     control_bits: ControlBits,
200     reserved0: [u8; 1],
201     slot_metadata: [SlotMetaData; MAX_SLOTS as usize],
202     reserved1: [u8; 8],
203     crc32: LittleEndianU32,
204 }
205 static_assertions::const_assert_eq!(core::mem::size_of::<BootloaderControl>(), 32);
206 
207 impl BootloaderControl {
calculate_crc32(&self) -> u32208     fn calculate_crc32(&self) -> u32 {
209         let mut hasher = Hasher::new();
210         hasher.update(&self.as_bytes()[..(size_of::<Self>() - size_of::<LittleEndianU32>())]);
211         hasher.finalize()
212     }
213 }
214 
215 impl Default for BootloaderControl {
default() -> Self216     fn default() -> Self {
217         let mut data = Self {
218             slot_suffix: Default::default(),
219             magic: BOOT_CTRL_MAGIC,
220             version: BOOT_CTRL_VERSION,
221             control_bits: Default::default(),
222             reserved0: Default::default(),
223             slot_metadata: Default::default(),
224             reserved1: Default::default(),
225             crc32: LittleEndianU32::ZERO,
226         };
227         // The slot suffix field stores the current active slot,
228         // which starts as the first one.
229         // Notice that it stores the entire suffix,
230         // including the leading underscore.
231         '_'.encode_utf8(&mut data.slot_suffix[0..]);
232         'a'.encode_utf8(&mut data.slot_suffix[1..]);
233         data.control_bits.set_nb_slots(4);
234         data.crc32.set(data.calculate_crc32());
235         data
236     }
237 }
238 
239 impl MetadataBytes for BootloaderControl {
validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, Self>, MetadataParseError>240     fn validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, Self>, MetadataParseError> {
241         let boot_control_data =
242             Ref::<B, Self>::new_from_prefix(buffer).ok_or(MetadataParseError::BufferTooSmall)?.0;
243 
244         if boot_control_data.magic != BOOT_CTRL_MAGIC {
245             return Err(MetadataParseError::BadMagic);
246         }
247         if boot_control_data.version > BOOT_CTRL_VERSION {
248             return Err(MetadataParseError::BadVersion);
249         }
250         if boot_control_data.crc32.get() != boot_control_data.calculate_crc32() {
251             return Err(MetadataParseError::BadChecksum);
252         }
253 
254         Ok(boot_control_data)
255     }
256 
prepare_for_sync(&mut self)257     fn prepare_for_sync(&mut self) {
258         self.crc32 = self.calculate_crc32().into();
259     }
260 }
261 
262 impl super::private::SlotGet for SlotBlock<'_, BootloaderControl> {
get_slot_by_number(&self, number: usize) -> Result<Slot, Error>263     fn get_slot_by_number(&self, number: usize) -> Result<Slot, Error> {
264         let lower_ascii_suffixes = ('a'..='z').map(Suffix);
265         let control = self.get_data();
266         let (suffix, &slot_data) = zip(lower_ascii_suffixes, control.slot_metadata.iter())
267             // Note: there may be fewer slots than the maximum possible
268             .take(control.control_bits.nb_slots().into())
269             .nth(number)
270             .ok_or_else(|| Suffix::try_from(number).map_or(Error::Other, Error::NoSuchSlot))?;
271 
272         let bootability = match (slot_data.successful(), slot_data.tries()) {
273             (true, _) => Bootability::Successful,
274             (false, t) if t > 0 => Bootability::Retriable(t.into()),
275             (_, _) => Bootability::Unbootable(UnbootableReason::Unknown),
276         };
277 
278         Ok(Slot { suffix, priority: slot_data.priority().into(), bootability })
279     }
280 }
281 
282 impl Manager for SlotBlock<'_, BootloaderControl> {
slots_iter(&self) -> SlotIterator283     fn slots_iter(&self) -> SlotIterator {
284         SlotIterator::new(self)
285     }
286 
get_boot_target(&self) -> BootTarget287     fn get_boot_target(&self) -> BootTarget {
288         self.slots_iter()
289             .filter(Slot::is_bootable)
290             .max_by_key(|slot| (slot.priority, slot.suffix.rank()))
291             .map_or(
292                 // TODO(b/326253270): how is the recovery slot actually determined?
293                 BootTarget::Recovery(RecoveryTarget::Slotted(self.get_slot_last_set_active())),
294                 BootTarget::NormalBoot,
295             )
296     }
297 
set_slot_unbootable( &mut self, slot_suffix: Suffix, reason: UnbootableReason, ) -> Result<(), Error>298     fn set_slot_unbootable(
299         &mut self,
300         slot_suffix: Suffix,
301         reason: UnbootableReason,
302     ) -> Result<(), Error> {
303         let (idx, slot) = self
304             .slots_iter()
305             .enumerate()
306             .find(|(_, slot)| slot.suffix == slot_suffix)
307             .ok_or(Error::NoSuchSlot(slot_suffix))?;
308         if slot.bootability == Bootability::Unbootable(reason) {
309             return Ok(());
310         }
311 
312         let slot_data = &mut self.get_mut_data().slot_metadata[idx];
313         slot_data.set_tries(0);
314         slot_data.set_successful(false);
315 
316         Ok(())
317     }
318 
mark_boot_attempt(&mut self) -> Result<BootToken, Error>319     fn mark_boot_attempt(&mut self) -> Result<BootToken, Error> {
320         let target_slot = match self.get_boot_target() {
321             BootTarget::NormalBoot(slot) => slot,
322             BootTarget::Recovery(RecoveryTarget::Dedicated) => Err(Error::OperationProhibited)?,
323             BootTarget::Recovery(RecoveryTarget::Slotted(slot)) => {
324                 self.slots_iter()
325                     .find(|s| s.suffix == slot.suffix)
326                     .ok_or(Error::NoSuchSlot(slot.suffix))?;
327                 return self.take_boot_token().ok_or(Error::OperationProhibited);
328             }
329         };
330 
331         let (idx, slot) = self
332             .slots_iter()
333             .enumerate()
334             .find(|(_, slot)| slot.suffix == target_slot.suffix)
335             .ok_or(Error::NoSuchSlot(target_slot.suffix))?;
336         match slot.bootability {
337             Bootability::Unbootable(_) => Err(Error::OperationProhibited),
338             Bootability::Retriable(_) => {
339                 let metadata = &mut self.get_mut_data().slot_metadata[idx];
340                 metadata.set_tries(metadata.tries() - 1);
341                 let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
342                 Ok(token)
343             }
344             Bootability::Successful => {
345                 let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
346                 Ok(token)
347             }
348         }
349     }
350 
set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error>351     fn set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error> {
352         let idx = self
353             .slots_iter()
354             .position(|s| s.suffix == slot_suffix)
355             .ok_or(Error::NoSuchSlot(slot_suffix))?;
356 
357         let data = self.get_mut_data();
358         for (i, slot) in data.slot_metadata.iter_mut().enumerate() {
359             if i == idx {
360                 *slot = Default::default();
361             } else {
362                 slot.set_priority(DEFAULT_PRIORITY - 1);
363             }
364         }
365 
366         // Note: we know this is safe because the slot suffix is an ASCII char,
367         // which is only 1 byte long in utf8.
368         // The 0th element of self.data.slot_suffix is an underscore character.
369         slot_suffix.0.encode_utf8(&mut self.get_mut_data().slot_suffix[1..]);
370 
371         Ok(())
372     }
373 
set_oneshot_status(&mut self, _: OneShot) -> Result<(), Error>374     fn set_oneshot_status(&mut self, _: OneShot) -> Result<(), Error> {
375         Err(Error::OperationProhibited)
376     }
377 
clear_oneshot_status(&mut self)378     fn clear_oneshot_status(&mut self) {}
379 
write_back<B: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut B)380     fn write_back<B: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut B) {
381         self.sync_to_disk(block_dev)
382     }
383 }
384 
385 #[cfg(test)]
386 mod test {
387     use super::*;
388     use crate::slots::{android::BootloaderControl, partition::MetadataBytes};
389 
390     #[test]
test_slot_block_defaults()391     fn test_slot_block_defaults() {
392         let sb: SlotBlock<BootloaderControl> = Default::default();
393         let expected: Vec<Slot> = ('a'..='d')
394             .map(|c| Slot {
395                 suffix: c.into(),
396                 priority: DEFAULT_PRIORITY.into(),
397                 bootability: Bootability::Retriable(sb.get_max_retries()),
398             })
399             .collect();
400         let actual: Vec<Slot> = sb.slots_iter().collect();
401         assert_eq!(actual, expected);
402         assert_eq!(sb.get_oneshot_status(), None);
403         assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(expected[0]));
404         // Include the explicit null bytes for safety.
405         assert_eq!(sb.get_data().slot_suffix.as_slice(), "_a\0\0".as_bytes());
406     }
407 
408     #[test]
test_slot_block_fewer_slots()409     fn test_slot_block_fewer_slots() {
410         let mut sb: SlotBlock<BootloaderControl> = Default::default();
411         sb.get_mut_data().control_bits.set_nb_slots(2);
412 
413         let expected: Vec<Slot> = ('a'..='b')
414             .map(|c| Slot {
415                 suffix: c.into(),
416                 priority: DEFAULT_PRIORITY.into(),
417                 bootability: Bootability::Retriable(sb.get_max_retries()),
418             })
419             .collect();
420         let actual: Vec<Slot> = sb.slots_iter().collect();
421         assert_eq!(actual, expected);
422     }
423 
424     #[test]
test_slot_block_slot_count_saturates()425     fn test_slot_block_slot_count_saturates() {
426         let mut ctrl: BootloaderControl = Default::default();
427         ctrl.control_bits.set_nb_slots(255);
428         assert_eq!(ctrl.control_bits.nb_slots(), MAX_SLOTS);
429 
430         let mut sb: SlotBlock<BootloaderControl> = Default::default();
431         sb.get_mut_data().control_bits.set_nb_slots(255);
432         assert_eq!(sb.slots_iter().count(), MAX_SLOTS.into());
433     }
434 
435     #[test]
test_slot_block_parse()436     fn test_slot_block_parse() {
437         let boot_ctrl: BootloaderControl = Default::default();
438         assert_eq!(
439             BootloaderControl::validate(boot_ctrl.as_bytes()),
440             Ok(Ref::new(boot_ctrl.as_bytes()).unwrap())
441         );
442     }
443 
444     #[test]
test_slot_block_parse_buffer_too_small()445     fn test_slot_block_parse_buffer_too_small() {
446         let buffer: [u8; 0] = Default::default();
447         assert_eq!(
448             BootloaderControl::validate(buffer.as_slice()),
449             Err(MetadataParseError::BufferTooSmall)
450         );
451     }
452 
453     #[test]
test_slot_block_parse_bad_magic()454     fn test_slot_block_parse_bad_magic() {
455         let mut boot_ctrl: BootloaderControl = Default::default();
456         boot_ctrl.magic += 1;
457         assert_eq!(
458             BootloaderControl::validate(boot_ctrl.as_bytes()),
459             Err(MetadataParseError::BadMagic)
460         );
461     }
462 
463     #[test]
test_slot_block_parse_bad_version()464     fn test_slot_block_parse_bad_version() {
465         let mut boot_ctrl: BootloaderControl = Default::default();
466         boot_ctrl.version = 15;
467         assert_eq!(
468             BootloaderControl::validate(boot_ctrl.as_bytes()),
469             Err(MetadataParseError::BadVersion)
470         );
471     }
472 
473     #[test]
test_slot_block_parse_bad_crc()474     fn test_slot_block_parse_bad_crc() {
475         let mut boot_ctrl: BootloaderControl = Default::default();
476         let bad_crc = boot_ctrl.crc32.get() ^ LittleEndianU32::MAX_VALUE.get();
477         boot_ctrl.crc32 = bad_crc.into();
478         assert_eq!(
479             BootloaderControl::validate(boot_ctrl.as_bytes()),
480             Err(MetadataParseError::BadChecksum)
481         );
482     }
483 
484     #[test]
test_get_boot_target_recovery()485     fn test_get_boot_target_recovery() {
486         let mut sb: SlotBlock<BootloaderControl> = Default::default();
487         sb.get_mut_data().slot_metadata.iter_mut().for_each(|bits| bits.set_tries(0));
488         let a_slot = sb.slots_iter().next().unwrap();
489 
490         assert_eq!(sb.get_boot_target(), BootTarget::Recovery(RecoveryTarget::Slotted(a_slot)));
491     }
492 
493     #[test]
test_get_boot_target_recovery_nondefault_recovery_slot()494     fn test_get_boot_target_recovery_nondefault_recovery_slot() {
495         let mut sb: SlotBlock<BootloaderControl> = Default::default();
496         let b_suffix: Suffix = 'b'.into();
497         assert!(sb.set_active_slot(b_suffix).is_ok());
498         sb.get_mut_data().slot_metadata.iter_mut().for_each(|bits| bits.set_tries(0));
499         let b_slot = sb.slots_iter().find(|s| s.suffix == b_suffix).unwrap();
500 
501         assert_eq!(sb.get_boot_target(), BootTarget::Recovery(RecoveryTarget::Slotted(b_slot)));
502     }
503 
504     #[test]
test_get_slot_last_set_active()505     fn test_get_slot_last_set_active() {
506         let mut sb: SlotBlock<BootloaderControl> = Default::default();
507         let v: Vec<Slot> = sb.slots_iter().collect();
508         assert_eq!(sb.set_active_slot(v[1].suffix), Ok(()));
509         assert_eq!(sb.get_slot_last_set_active(), v[1]);
510         for slot in v.iter() {
511             assert_eq!(sb.set_slot_unbootable(slot.suffix, UnbootableReason::NoMoreTries), Ok(()));
512         }
513 
514         assert_eq!(sb.get_slot_last_set_active(), sb.slots_iter().nth(1).unwrap());
515         assert_eq!(sb.get_data().slot_suffix.as_slice(), "_b\0\0".as_bytes());
516     }
517 
518     #[test]
test_slot_mark_boot_attempt()519     fn test_slot_mark_boot_attempt() {
520         let mut sb: SlotBlock<BootloaderControl> = Default::default();
521         let slot = Slot { suffix: 'a'.into(), ..Default::default() };
522         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
523         assert_eq!(
524             sb.slots_iter().next().unwrap(),
525             Slot {
526                 suffix: slot.suffix,
527                 priority: DEFAULT_PRIORITY.into(),
528                 bootability: Bootability::Retriable((DEFAULT_RETRIES - 1).into())
529             }
530         );
531 
532         // Make sure we can call exactly once
533         assert_eq!(sb.mark_boot_attempt(), Err(Error::OperationProhibited));
534     }
535 
536     #[test]
test_slot_mark_boot_attempt_no_more_tries()537     fn test_slot_mark_boot_attempt_no_more_tries() {
538         let mut sb: SlotBlock<BootloaderControl> = Default::default();
539         sb.get_mut_data().slot_metadata[0].set_tries(1);
540         let slot = Slot { suffix: 'a'.into(), ..Default::default() };
541         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
542         assert_eq!(
543             sb.slots_iter().next().unwrap(),
544             Slot {
545                 suffix: slot.suffix,
546                 priority: DEFAULT_PRIORITY.into(),
547                 // Default implementation does not track unbootable reasons
548                 bootability: Bootability::Unbootable(UnbootableReason::Unknown)
549             }
550         );
551         assert_eq!(sb.get_data().slot_metadata[0].tries(), 0);
552     }
553 
554     #[test]
test_slot_mark_boot_attempt_successful()555     fn test_slot_mark_boot_attempt_successful() {
556         let mut sb: SlotBlock<BootloaderControl> = Default::default();
557         let initial_tries;
558         {
559             let metadata = &mut sb.get_mut_data().slot_metadata[0];
560             initial_tries = metadata.tries();
561             metadata.set_successful(true);
562         }
563         let target = BootTarget::NormalBoot(Slot {
564             suffix: 'a'.into(),
565             priority: DEFAULT_PRIORITY.into(),
566             bootability: Bootability::Successful,
567         });
568         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
569         assert_eq!(BootTarget::NormalBoot(sb.slots_iter().next().unwrap()), target);
570         assert_eq!(sb.get_data().slot_metadata[0].tries(), initial_tries);
571     }
572 
573     #[test]
test_mark_slot_tried_slotted_recovery()574     fn test_mark_slot_tried_slotted_recovery() {
575         let mut sb: SlotBlock<BootloaderControl> = Default::default();
576         sb.set_slot_unbootable('a'.into(), UnbootableReason::UserRequested);
577         sb.set_slot_unbootable('b'.into(), UnbootableReason::UserRequested);
578         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
579     }
580 
581     #[test]
test_set_oneshot_status_unsupported()582     fn test_set_oneshot_status_unsupported() {
583         let mut sb: SlotBlock<BootloaderControl> = Default::default();
584         let oneshots = [
585             OneShot::Bootloader,
586             OneShot::Continue(RecoveryTarget::Dedicated),
587             OneShot::Continue(RecoveryTarget::Slotted(sb.get_slot_last_set_active())),
588         ];
589 
590         for oneshot in oneshots {
591             assert_eq!(sb.set_oneshot_status(oneshot), Err(Error::OperationProhibited));
592         }
593     }
594 }
595