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