1 // Copyright 2023, The Android Open Source Project
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 extern crate bitflags;
16 extern crate crc32fast;
17 extern crate zerocopy;
18 
19 use super::partition::{MetadataBytes, MetadataParseError, SlotBlock};
20 use super::{
21     BootTarget, BootToken, Bootability, Error, Manager, OneShot, RecoveryTarget, Slot,
22     SlotIterator, Suffix, UnbootableReason,
23 };
24 use bitflags::bitflags;
25 use core::iter::zip;
26 use core::mem::size_of;
27 use crc32fast::Hasher;
28 use zerocopy::byteorder::big_endian::U32 as BigEndianU32;
29 use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref};
30 
31 const DEFAULT_PRIORITY: u8 = 15;
32 const DEFAULT_RETRIES: u8 = 7;
33 
34 #[repr(C, packed)]
35 #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
36 struct AbrSlotData {
37     priority: u8,
38     tries: u8,
39     successful: u8,
40     unbootable_reason: u8,
41 }
42 
43 impl Default for AbrSlotData {
default() -> Self44     fn default() -> Self {
45         Self {
46             priority: DEFAULT_PRIORITY,
47             tries: DEFAULT_RETRIES,
48             successful: 0,
49             unbootable_reason: 0,
50         }
51     }
52 }
53 
54 #[repr(C, packed)]
55 #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
56 struct OneShotFlags(u8);
57 
58 bitflags! {
59     impl OneShotFlags: u8 {
60         /// No oneshot specified
61         const NONE = 0;
62         /// Oneshot boot to recovery mode
63         const RECOVERY = 1 << 0;
64         /// Oneshot boot to fastboot
65         const BOOTLOADER = 1 << 1;
66     }
67 }
68 
69 impl From<OneShotFlags> for Option<OneShot> {
from(flags: OneShotFlags) -> Self70     fn from(flags: OneShotFlags) -> Self {
71         match flags {
72             OneShotFlags::RECOVERY => Some(OneShot::Continue(RecoveryTarget::Dedicated)),
73             OneShotFlags::BOOTLOADER => Some(OneShot::Bootloader),
74             _ => None,
75         }
76     }
77 }
78 
79 impl From<Option<OneShot>> for OneShotFlags {
from(oneshot: Option<OneShot>) -> Self80     fn from(oneshot: Option<OneShot>) -> Self {
81         if let Some(target) = oneshot {
82             match target {
83                 OneShot::Bootloader => Self::BOOTLOADER,
84                 OneShot::Continue(RecoveryTarget::Dedicated) => Self::RECOVERY,
85                 _ => Self::NONE,
86             }
87         } else {
88             Self::NONE
89         }
90     }
91 }
92 
93 const ABR_MAGIC: &[u8; 4] = b"\0AB0";
94 const ABR_VERSION_MAJOR: u8 = 2;
95 const ABR_VERSION_MINOR: u8 = 3;
96 
97 #[repr(C, packed)]
98 #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
99 struct AbrData {
100     magic: [u8; 4],
101     version_major: u8,
102     version_minor: u8,
103     reserved: [u8; 2],
104     slot_data: [AbrSlotData; 2],
105     oneshot_flag: OneShotFlags,
106     reserved2: [u8; 11],
107     crc32: BigEndianU32,
108 }
109 
110 impl AbrData {
calculate_crc32(&self) -> u32111     fn calculate_crc32(&self) -> u32 {
112         let mut hasher = Hasher::new();
113         // Note: core::offset_of isn't stable yet,
114         // and size_of_val isn't permitted on unaligned structs.
115         hasher.update(&self.as_bytes()[..(size_of::<Self>() - size_of::<BigEndianU32>())]);
116         hasher.finalize()
117     }
118 }
119 
120 impl MetadataBytes for AbrData {
validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, AbrData>, MetadataParseError>121     fn validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, AbrData>, MetadataParseError> {
122         let abr_data =
123             Ref::<B, AbrData>::new_from_prefix(buffer).ok_or(MetadataParseError::BufferTooSmall)?.0;
124 
125         if abr_data.magic != *ABR_MAGIC {
126             return Err(MetadataParseError::BadMagic);
127         }
128         if abr_data.version_major > ABR_VERSION_MAJOR {
129             return Err(MetadataParseError::BadVersion);
130         }
131         if abr_data.crc32.get() != abr_data.calculate_crc32() {
132             return Err(MetadataParseError::BadChecksum);
133         }
134 
135         Ok(abr_data)
136     }
137 
prepare_for_sync(&mut self)138     fn prepare_for_sync(&mut self) {
139         self.version_minor = ABR_VERSION_MINOR;
140         self.crc32 = self.calculate_crc32().into();
141     }
142 }
143 
144 impl Default for AbrData {
default() -> Self145     fn default() -> Self {
146         let mut data = Self {
147             magic: *ABR_MAGIC,
148             version_major: ABR_VERSION_MAJOR,
149             version_minor: ABR_VERSION_MINOR,
150             reserved: Default::default(),
151             slot_data: Default::default(),
152             oneshot_flag: OneShotFlags::NONE,
153             reserved2: Default::default(),
154             crc32: BigEndianU32::ZERO,
155         };
156         data.crc32.set(data.calculate_crc32());
157         data
158     }
159 }
160 
161 impl super::private::SlotGet for SlotBlock<'_, AbrData> {
get_slot_by_number(&self, number: usize) -> Result<Slot, Error>162     fn get_slot_by_number(&self, number: usize) -> Result<Slot, Error> {
163         let lower_ascii_suffixes = ('a'..='z').map(Suffix);
164         let (suffix, &abr_slot) = zip(lower_ascii_suffixes, self.get_data().slot_data.iter())
165             .nth(number)
166             .ok_or_else(|| Suffix::try_from(number).map_or(Error::Other, Error::NoSuchSlot))?;
167 
168         let bootability = match (abr_slot.successful, abr_slot.tries) {
169             (s, _) if s != 0 => Bootability::Successful,
170             (0, t) if t > 0 => Bootability::Retriable(t.into()),
171             (_, _) => Bootability::Unbootable(abr_slot.unbootable_reason.into()),
172         };
173 
174         Ok(Slot { suffix, priority: abr_slot.priority.into(), bootability })
175     }
176 }
177 
178 impl Manager for SlotBlock<'_, AbrData> {
get_boot_target(&self) -> BootTarget179     fn get_boot_target(&self) -> BootTarget {
180         self.slots_iter()
181             .filter(Slot::is_bootable)
182             .max_by_key(|slot| (slot.priority, slot.suffix.rank()))
183             .map_or(BootTarget::Recovery(RecoveryTarget::Dedicated), BootTarget::NormalBoot)
184     }
185 
slots_iter(&self) -> SlotIterator186     fn slots_iter(&self) -> SlotIterator {
187         SlotIterator::new(self)
188     }
189 
set_slot_unbootable( &mut self, slot_suffix: Suffix, reason: UnbootableReason, ) -> Result<(), Error>190     fn set_slot_unbootable(
191         &mut self,
192         slot_suffix: Suffix,
193         reason: UnbootableReason,
194     ) -> Result<(), Error> {
195         let (idx, slot) = self.get_index_and_slot_with_suffix(slot_suffix)?;
196         if slot.bootability == Bootability::Unbootable(reason) {
197             return Ok(());
198         }
199 
200         let abr_data = self.get_mut_data();
201         let slot_data = &mut abr_data.slot_data[idx];
202         slot_data.unbootable_reason = reason.into();
203         slot_data.tries = 0;
204         slot_data.successful = 0;
205 
206         Ok(())
207     }
208 
mark_boot_attempt(&mut self) -> Result<BootToken, Error>209     fn mark_boot_attempt(&mut self) -> Result<BootToken, Error> {
210         let target = if let Some(OneShot::Continue(r)) = self.get_oneshot_status() {
211             BootTarget::Recovery(r)
212         } else {
213             self.get_boot_target()
214         };
215         let target_slot = match target {
216             BootTarget::NormalBoot(slot) => slot,
217             BootTarget::Recovery(RecoveryTarget::Slotted(_)) => Err(Error::OperationProhibited)?,
218             BootTarget::Recovery(RecoveryTarget::Dedicated) => {
219                 // Even though boot to recovery does not cause a metadata update,
220                 // we still need to gate access to the boot token.
221                 return self.take_boot_token().ok_or(Error::OperationProhibited);
222             }
223         };
224 
225         let (idx, slot) = self.get_index_and_slot_with_suffix(target_slot.suffix)?;
226 
227         match slot.bootability {
228             Bootability::Unbootable(_) => Err(Error::OperationProhibited),
229             Bootability::Retriable(_) => {
230                 let abr_slot = &mut self.get_mut_data().slot_data[idx];
231                 abr_slot.tries -= 1;
232                 if abr_slot.tries == 0 {
233                     abr_slot.unbootable_reason = UnbootableReason::NoMoreTries.into();
234                 }
235                 let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
236                 Ok(token)
237             }
238             Bootability::Successful => {
239                 let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
240                 Ok(token)
241             }
242         }
243     }
244 
set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error>245     fn set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error> {
246         let (idx, _) = self.get_index_and_slot_with_suffix(slot_suffix)?;
247 
248         let abr_data = self.get_mut_data();
249         for (i, slot) in abr_data.slot_data.iter_mut().enumerate() {
250             if i == idx {
251                 *slot = Default::default();
252             } else {
253                 slot.priority = DEFAULT_PRIORITY - 1;
254             }
255         }
256         Ok(())
257     }
258 
get_oneshot_status(&self) -> Option<OneShot>259     fn get_oneshot_status(&self) -> Option<OneShot> {
260         self.get_data().oneshot_flag.into()
261     }
262 
set_oneshot_status(&mut self, oneshot: OneShot) -> Result<(), Error>263     fn set_oneshot_status(&mut self, oneshot: OneShot) -> Result<(), Error> {
264         if Some(oneshot) == self.get_oneshot_status() {
265             return Ok(());
266         }
267 
268         let oneshot_flag = OneShotFlags::from(Some(oneshot));
269         if oneshot_flag == OneShotFlags::NONE {
270             Err(match oneshot {
271                 OneShot::Continue(RecoveryTarget::Slotted(_)) => Error::OperationProhibited,
272                 _ => Error::Other,
273             })
274         } else {
275             self.get_mut_data().oneshot_flag = oneshot_flag;
276             Ok(())
277         }
278     }
279 
clear_oneshot_status(&mut self)280     fn clear_oneshot_status(&mut self) {
281         if self.get_oneshot_status().is_some() {
282             self.get_mut_data().oneshot_flag = OneShotFlags::NONE;
283         }
284     }
285 
write_back<B: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut B)286     fn write_back<B: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut B) {
287         self.sync_to_disk(block_dev);
288     }
289 }
290 
291 impl<'a> SlotBlock<'a, AbrData> {
get_index_and_slot_with_suffix(&self, slot_suffix: Suffix) -> Result<(usize, Slot), Error>292     fn get_index_and_slot_with_suffix(&self, slot_suffix: Suffix) -> Result<(usize, Slot), Error> {
293         self.slots_iter()
294             .enumerate()
295             .find(|(_, s)| s.suffix == slot_suffix)
296             .ok_or(Error::NoSuchSlot(slot_suffix))
297     }
298 }
299 
300 #[cfg(test)]
301 mod test {
302     use super::*;
303     use crate::slots::{partition::CacheStatus, Cursor};
304     use gbl_storage::AsBlockDevice;
305     use gbl_storage_testlib::TestBlockDevice;
306 
307     #[test]
test_slot_block_defaults()308     fn test_slot_block_defaults() {
309         let sb: SlotBlock<AbrData> = Default::default();
310         let expected: Vec<Slot> = vec![
311             Slot {
312                 suffix: 'a'.into(),
313                 priority: DEFAULT_PRIORITY.into(),
314                 bootability: Bootability::Retriable(sb.get_max_retries()),
315             },
316             Slot {
317                 suffix: 'b'.into(),
318                 priority: DEFAULT_PRIORITY.into(),
319                 bootability: Bootability::Retriable(sb.get_max_retries()),
320             },
321         ];
322         let actual: Vec<Slot> = sb.slots_iter().collect();
323         assert_eq!(actual, expected);
324         assert_eq!(sb.get_oneshot_status(), None);
325     }
326 
327     #[test]
test_suffix()328     fn test_suffix() {
329         let slot = Slot { suffix: 'a'.into(), ..Default::default() };
330         assert_eq!(BootTarget::Recovery(RecoveryTarget::Dedicated).suffix(), 'r'.into());
331         assert_eq!(BootTarget::Recovery(RecoveryTarget::Slotted(slot)).suffix(), slot.suffix);
332         assert_eq!(BootTarget::NormalBoot(slot).suffix(), slot.suffix);
333     }
334 
335     #[test]
test_slot_block_parse()336     fn test_slot_block_parse() {
337         let abr: AbrData = Default::default();
338         assert_eq!(AbrData::validate(abr.as_bytes()), Ok(Ref::new(abr.as_bytes()).unwrap()));
339     }
340 
341     #[test]
test_slot_block_parse_buffer_too_small()342     fn test_slot_block_parse_buffer_too_small() {
343         let buffer: [u8; 0] = Default::default();
344         assert_eq!(AbrData::validate(&buffer[..]), Err(MetadataParseError::BufferTooSmall),);
345     }
346 
347     #[test]
test_slot_block_parse_bad_magic()348     fn test_slot_block_parse_bad_magic() {
349         let mut abr: AbrData = Default::default();
350         abr.magic[0] += 1;
351         assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadMagic));
352     }
353 
354     #[test]
test_slot_block_parse_bad_version_major()355     fn test_slot_block_parse_bad_version_major() {
356         let mut abr: AbrData = Default::default();
357         abr.version_major = 15;
358         assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadVersion));
359     }
360 
361     #[test]
test_slot_block_parse_bad_crc()362     fn test_slot_block_parse_bad_crc() {
363         let mut abr: AbrData = Default::default();
364         let bad_crc = abr.crc32.get() ^ BigEndianU32::MAX_VALUE.get();
365         abr.crc32 = bad_crc.into();
366         assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadChecksum));
367     }
368 
369     #[test]
test_slot_mark_boot_attempt()370     fn test_slot_mark_boot_attempt() {
371         let mut sb: SlotBlock<AbrData> = Default::default();
372         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
373         assert_eq!(
374             sb.slots_iter().next().unwrap(),
375             Slot {
376                 suffix: 'a'.into(),
377                 priority: DEFAULT_PRIORITY.into(),
378                 bootability: Bootability::Retriable((DEFAULT_RETRIES - 1).into())
379             }
380         );
381 
382         // Make sure we can call exactly once
383         assert_eq!(sb.mark_boot_attempt(), Err(Error::OperationProhibited));
384     }
385 
386     #[test]
test_slot_mark_boot_attempt_tracks_active()387     fn test_slot_mark_boot_attempt_tracks_active() {
388         let mut sb: SlotBlock<AbrData> = Default::default();
389         assert!(sb.set_active_slot('b'.into()).is_ok());
390 
391         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
392         assert_eq!(
393             sb.get_boot_target(),
394             BootTarget::NormalBoot(Slot {
395                 suffix: 'b'.into(),
396                 priority: DEFAULT_PRIORITY.into(),
397                 bootability: Bootability::Retriable((DEFAULT_RETRIES - 1).into())
398             })
399         );
400     }
401 
402     #[test]
test_slot_mark_boot_attempt_no_more_tries()403     fn test_slot_mark_boot_attempt_no_more_tries() {
404         let mut sb: SlotBlock<AbrData> = Default::default();
405         sb.get_mut_data().slot_data[0].tries = 1;
406         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
407         assert_eq!(
408             sb.slots_iter().next().unwrap(),
409             Slot {
410                 suffix: 'a'.into(),
411                 priority: DEFAULT_PRIORITY.into(),
412                 bootability: Bootability::Unbootable(UnbootableReason::NoMoreTries)
413             }
414         );
415     }
416 
417     #[test]
test_slot_mark_boot_attempt_successful()418     fn test_slot_mark_boot_attempt_successful() {
419         let mut sb: SlotBlock<AbrData> = Default::default();
420         sb.get_mut_data().slot_data[0].successful = 1;
421         let target = BootTarget::NormalBoot(Slot {
422             suffix: 'a'.into(),
423             priority: DEFAULT_PRIORITY.into(),
424             bootability: Bootability::Successful,
425         });
426         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
427         assert_eq!(sb.get_boot_target(), target);
428     }
429 
430     #[test]
test_slot_mark_tried_recovery()431     fn test_slot_mark_tried_recovery() {
432         let mut sb: SlotBlock<AbrData> = Default::default();
433         let recovery_tgt = BootTarget::Recovery(RecoveryTarget::Dedicated);
434         assert!(sb.set_slot_unbootable('a'.into(), UnbootableReason::UserRequested).is_ok());
435         assert!(sb.set_slot_unbootable('b'.into(), UnbootableReason::UserRequested).is_ok());
436         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
437 
438         // Make sure a second attempt fails due to the moved boot token
439         assert_eq!(sb.mark_boot_attempt(), Err(Error::OperationProhibited));
440     }
441 
442     #[test]
test_slot_mark_tried_recovery_oneshot()443     fn test_slot_mark_tried_recovery_oneshot() {
444         let mut sb: SlotBlock<AbrData> = Default::default();
445         let tgt = sb.get_boot_target();
446         assert!(sb.set_oneshot_status(OneShot::Continue(RecoveryTarget::Dedicated)).is_ok());
447         assert_eq!(sb.mark_boot_attempt(), Ok(BootToken(())));
448 
449         // Verify that tries weren't decremented
450         assert_eq!(sb.get_boot_target(), tgt);
451     }
452 
453     macro_rules! set_unbootable_tests {
454                 ($($name:ident: $value:expr,)*) => {
455                     $(
456                         #[test]
457                         fn $name() {
458                             let mut sb: SlotBlock<AbrData> = Default::default();
459                             let suffix: Suffix = 'a'.into();
460                             assert_eq!(sb.set_slot_unbootable(suffix, $value), Ok(()));
461                             assert_eq!(sb.slots_iter()
462                                        .find(|s| s.suffix == suffix)
463                                        .unwrap()
464                                        .bootability,
465                                        Bootability::Unbootable($value)
466                             );
467                         }
468                     )*
469                 }
470             }
471 
472     use UnbootableReason::*;
473     set_unbootable_tests! {
474         test_set_unbootable_no_more_tries: NoMoreTries,
475         test_set_unbootable_system_update: SystemUpdate,
476         test_set_unbootable_user_requested: UserRequested,
477         test_set_unbootable_verification_failure: VerificationFailure,
478         test_set_unbootable_unknown: Unknown,
479     }
480 
481     #[test]
test_no_bootable_slots_boot_recovery()482     fn test_no_bootable_slots_boot_recovery() {
483         let mut sb: SlotBlock<AbrData> = Default::default();
484         let v: Vec<Slot> = sb.slots_iter().collect();
485         for slot in v {
486             assert_eq!(
487                 sb.set_slot_unbootable(slot.suffix, UnbootableReason::UserRequested),
488                 Ok(())
489             );
490         }
491         assert_eq!(sb.get_boot_target(), BootTarget::Recovery(RecoveryTarget::Dedicated));
492     }
493 
494     #[test]
test_set_active_slot()495     fn test_set_active_slot() {
496         let mut sb: SlotBlock<AbrData> = Default::default();
497         let v: Vec<Slot> = sb.slots_iter().collect();
498 
499         assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(v[0]));
500         for slot in v.iter() {
501             assert_eq!(sb.set_active_slot(slot.suffix), Ok(()));
502             assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(*slot));
503         }
504     }
505 
506     #[test]
test_set_active_slot_no_such_slot()507     fn test_set_active_slot_no_such_slot() {
508         let mut sb: SlotBlock<AbrData> = Default::default();
509         let bad_suffix: Suffix = '$'.into();
510         assert_eq!(sb.set_active_slot(bad_suffix), Err(Error::NoSuchSlot(bad_suffix)));
511     }
512 
513     #[test]
test_get_slot_last_set_active()514     fn test_get_slot_last_set_active() {
515         let mut sb: SlotBlock<AbrData> = Default::default();
516         let v: Vec<Slot> = sb.slots_iter().collect();
517         assert_eq!(sb.set_active_slot(v[0].suffix), Ok(()));
518         assert_eq!(sb.get_slot_last_set_active(), v[0]);
519         for slot in v.iter() {
520             assert_eq!(sb.set_slot_unbootable(slot.suffix, NoMoreTries), Ok(()));
521         }
522 
523         assert_eq!(sb.get_slot_last_set_active(), sb.slots_iter().next().unwrap());
524     }
525 
526     macro_rules! set_oneshot_tests {
527                 ($($name:ident: $value:expr,)*) => {
528                     $(
529                         #[test]
530                         fn $name(){
531                             let mut sb: SlotBlock<AbrData> = Default::default();
532                             assert_eq!(sb.set_oneshot_status($value), Ok(()));
533                             assert_eq!(sb.get_oneshot_status(), Some($value));
534 
535                             assert_eq!(sb.get_boot_target(),
536                                        BootTarget::NormalBoot(
537                                            Slot{
538                                                suffix: 'a'.into(),
539                                                priority: DEFAULT_PRIORITY.into(),
540                                                bootability: Bootability::Retriable(sb.get_max_retries()),
541                                            },
542                                        ));
543                         }
544                     )*
545                 }
546             }
547 
548     set_oneshot_tests! {
549         test_set_oneshot_bootloader: OneShot::Bootloader,
550         test_set_oneshot_recovery: OneShot::Continue(RecoveryTarget::Dedicated),
551     }
552 
553     #[test]
test_clear_oneshot_status()554     fn test_clear_oneshot_status() {
555         let mut sb: SlotBlock<AbrData> = Default::default();
556         assert_eq!(sb.set_oneshot_status(OneShot::Bootloader), Ok(()));
557         sb.clear_oneshot_status();
558         assert_eq!(sb.get_oneshot_status(), None);
559     }
560 
561     #[test]
test_set_oneshot_mistaken_recovery_slotted()562     fn test_set_oneshot_mistaken_recovery_slotted() {
563         let mut sb: SlotBlock<AbrData> = Default::default();
564         let slot = sb.slots_iter().next().unwrap();
565         assert_eq!(
566             sb.set_oneshot_status(OneShot::Continue(RecoveryTarget::Slotted(slot))),
567             Err(Error::OperationProhibited)
568         );
569     }
570 
571     #[test]
test_deserialize_default_to_dirty_cache()572     fn test_deserialize_default_to_dirty_cache() {
573         let mut abr_data: AbrData = Default::default();
574         // Changing the success both invalidates the crc
575         // and lets us verify that the deserialized slot block
576         // uses defaulted backing bytes instead of the provided bytes.
577         abr_data.slot_data[0].successful = 1;
578         let sb = SlotBlock::<AbrData>::deserialize(
579             abr_data.as_bytes(),
580             "partition_moniker",
581             0,
582             BootToken(()),
583         );
584         assert_eq!(sb.cache_status(), CacheStatus::Dirty);
585         assert_eq!(
586             sb.slots_iter().next().unwrap().bootability,
587             Bootability::Retriable(DEFAULT_RETRIES.into())
588         );
589     }
590 
591     #[test]
test_deserialize_modified_to_clean_cache()592     fn test_deserialize_modified_to_clean_cache() {
593         let mut abr_data: AbrData = Default::default();
594         abr_data.slot_data[0].successful = 1;
595         // If we recalculate the crc,
596         // that just means we have a metadata block that stores
597         // relevant, non-default information.
598         abr_data.crc32.set(abr_data.calculate_crc32());
599         let sb = SlotBlock::<AbrData>::deserialize(
600             abr_data.as_bytes(),
601             "partition_moniker",
602             0,
603             BootToken(()),
604         );
605         assert_eq!(sb.cache_status(), CacheStatus::Clean);
606         assert_eq!(sb.slots_iter().next().unwrap().bootability, Bootability::Successful);
607     }
608 
609     #[test]
test_writeback()610     fn test_writeback() {
611         const PARTITION: &str = "test_partition";
612         const OFFSET: u64 = 2112; // Deliberately wrong to test propagation of parameter.
613         let mut block_dev: TestBlockDevice =
614             include_bytes!("../../testdata/writeback_test_disk.bin").as_slice().into();
615         assert!(block_dev.sync_gpt().is_ok());
616         let mut sb: SlotBlock<AbrData> = Default::default();
617         sb.partition = PARTITION;
618         sb.partition_offset = OFFSET;
619 
620         let mut read_buffer: [u8; size_of::<AbrData>()] = Default::default();
621 
622         // Clean cache, write_back is a no-op
623         sb.write_back(&mut block_dev);
624         let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
625         assert!(res.is_ok());
626         assert_eq!(read_buffer, [0; std::mem::size_of::<AbrData>()]);
627 
628         // Make a change, write_back writes back to the defined partition
629         // at the defined offset.
630         assert_eq!(sb.set_oneshot_status(OneShot::Bootloader), Ok(()));
631         assert_eq!(sb.cache_status(), CacheStatus::Dirty);
632 
633         sb.write_back(&mut block_dev);
634         let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
635         assert!(res.is_ok());
636         assert_eq!(read_buffer, sb.get_data().as_bytes());
637         assert_eq!(sb.cache_status(), CacheStatus::Clean);
638     }
639 
640     #[test]
test_writeback_with_cursor()641     fn test_writeback_with_cursor() {
642         const PARTITION: &str = "test_partition";
643         const OFFSET: u64 = 2112; // Deliberately wrong to test propagation of parameter.
644         let mut block_dev: TestBlockDevice =
645             include_bytes!("../../testdata/writeback_test_disk.bin").as_slice().into();
646         assert!(block_dev.sync_gpt().is_ok());
647         let mut read_buffer: [u8; size_of::<AbrData>()] = Default::default();
648         let mut abr_data;
649 
650         let mut sb: SlotBlock<AbrData> = Default::default();
651         sb.partition = PARTITION;
652         sb.partition_offset = OFFSET;
653 
654         // New block to trigger drop on the cursor.
655         {
656             let mut cursor = Cursor { ctx: sb, block_dev: &mut block_dev };
657             assert!(cursor.ctx.set_active_slot('b'.into()).is_ok());
658             abr_data = cursor.ctx.get_data().clone();
659         }
660 
661         // Need to manually recalculate crc because the cursor updates that
662         // right before writing to disk.
663         abr_data.prepare_for_sync();
664         let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
665         assert!(res.is_ok());
666         assert_eq!(read_buffer, abr_data.as_bytes());
667     }
668 }
669