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