// Copyright 2024, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Fuchsia A/B/R boot slot library. #![cfg_attr(not(test), no_std)] use core::{cmp::min, ffi::c_uint, fmt::Write, mem::size_of}; use liberror::{Error, Result}; const ABR_MAGIC: &[u8; 4] = b"\0AB0"; const ABR_MAJOR_VERSION: u8 = 2; const ABR_MINOR_VERSION: u8 = 2; // The following flags are harcoded as u8 instead of using the bitflag crate to avoid additional // crate dependency and improve portability. /// One-shot recovery boot bit for the flag returned by `get_and_clear_one_shot_flag()`. pub const ONE_SHOT_RECOVERY: u8 = 1 << 0; /// One-shot bootloader boot bit for the flag returned by `get_and_clear_one_shot_flag()`. pub const ONE_SHOT_BOOTLOADER: u8 = 1 << 1; const ABR_MAX_PRIORITY: u8 = 15; /// Maximum number of retries. pub const ABR_MAX_TRIES_REMAINING: u8 = 7; /// `Ops` provides the backend interfaces needed by A/B/R APIs. pub trait Ops { /// Reads exactly `out.len()` bytes into `out` from the persistent storage hosting the A/B/R /// metadata. fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()>; /// Writes exactly `data.len()` bytes from `data` to the persistent storage hosting the A/B/R /// metadata. fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()>; /// Returns an optional console writer for logging error messages. fn console(&mut self) -> Option<&mut dyn Write>; } impl Ops for [u8; ABR_DATA_SIZE] { fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> { Ok(out .clone_from_slice(self.get(..out.len()).ok_or(Error::BufferTooSmall(Some(out.len())))?)) } fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()> { Ok(self .get_mut(..data.len()) .ok_or(Error::BufferTooSmall(Some(data.len())))? .clone_from_slice(data)) } fn console(&mut self) -> Option<&mut dyn Write> { None } } /// Helper macro for printing ABR log messages. macro_rules! avb_print { ( $abr_ops:expr, $( $x:expr ),* $(,)? ) => { match $abr_ops.console() { Some(f) => write!(f, $($x,)*).unwrap(), _ => {} } }; } /// `SlotIndex` represents the A/B/R slot index. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum SlotIndex { /// A slot; normal boot. A, /// B slot; normal boot. B, /// R slot; recovery boot. Doesn't have any associated metadata (e.g. cannot be active, no /// retries), but is unconditionally used as a fallback if both A and B are unbootable. R, } impl SlotIndex { // Get the other counterpart of a A/B slot. fn other(&self) -> Self { match self { SlotIndex::A => SlotIndex::B, SlotIndex::B => SlotIndex::A, _ => panic!("Invalid slot index for `fn other()`"), } } } // Implement conversion to c_uint for C interfaces impl From for c_uint { fn from(_val: SlotIndex) -> Self { match _val { SlotIndex::A => 0, SlotIndex::B => 1, SlotIndex::R => 2, } } } // Implement conversion to char impl From for char { fn from(_val: SlotIndex) -> Self { match _val { SlotIndex::A => 'a', SlotIndex::B => 'b', SlotIndex::R => 'r', } } } // Implement conversion from c_uint for C interfaces. impl TryFrom for SlotIndex { type Error = Error; fn try_from(val: c_uint) -> Result { match val { v if v == (SlotIndex::A).into() => Ok(SlotIndex::A), v if v == (SlotIndex::B).into() => Ok(SlotIndex::B), v if v == (SlotIndex::R).into() => Ok(SlotIndex::R), _ => Err(Error::InvalidInput), } } } /// `SlotInfo` represents the current state of a A/B/R slot. pub enum SlotState { /// Slot has successfully booted. Successful, /// Slot can be attempted but is not known to be successful. Contained value is the number /// of boot attempts remaining before being marked as `Unbootable`. Bootable(u8), /// Slot is unbootable. Unbootable, } /// `SlotInfo` contains the current state and active status of a A/B/R slot. pub struct SlotInfo { /// The [SlotState] describing the bootability. pub state: SlotState, /// Whether this is currently the active slot. pub is_active: bool, } /// `AbrSlotData` is the wire format metadata for A/B slot. #[repr(C, packed)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] pub struct AbrSlotData { /// Slot priority. Unbootable slots should always have priority 0. pub priority: u8, /// Boot attempts remaining. pub tries_remaining: u8, /// Whether this slot is known successful. pub successful_boot: u8, /// Reserved for future use; must be set to 0. pub reserved: u8, } const ABR_SLOT_DATA_SIZE: usize = size_of::(); impl AbrSlotData { /// Parses from bytes. pub fn deserialize(bytes: &[u8; ABR_SLOT_DATA_SIZE]) -> Self { Self { priority: bytes[0], tries_remaining: bytes[1], successful_boot: bytes[2], reserved: bytes[3], } } /// Serializes to bytes. pub fn serialize(&self) -> [u8; ABR_SLOT_DATA_SIZE] { [self.priority, self.tries_remaining, self.successful_boot, self.reserved] } /// Returns if slot is bootable fn is_slot_bootable(&self) -> bool { self.priority > 0 && (self.successful_boot == 1 || self.tries_remaining > 0) } fn set_slot_unbootable(&mut self) { self.tries_remaining = 0; self.successful_boot = 0; } /// Gets normalized priority. fn get_normalized_priority(&self) -> u8 { match self.is_slot_bootable() { true => self.priority, _ => 0, } } /// Ensures all unbootable or invalid states are marked as the canonical `unbootable` state. /// That is priority=0, tries_remaining=0, and successful_boot=0. fn slot_normalize(&mut self) { if self.priority > 0 { if self.tries_remaining == 0 && self.successful_boot == 0 { // All tries exhausted self.set_slot_unbootable(); } if self.tries_remaining > 0 && self.successful_boot == 1 { // Illegal state. Reset to not successful state self.tries_remaining = ABR_MAX_TRIES_REMAINING; self.successful_boot = 0; } self.priority = min(self.priority, ABR_MAX_PRIORITY); self.tries_remaining = min(self.tries_remaining, ABR_MAX_TRIES_REMAINING); } else { self.set_slot_unbootable(); } } } /// `AbrData` is the wire format of A/B/R metadata. #[repr(C, packed)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct AbrData { /// Magic value; must be [ABR_MAGIC]. pub magic: [u8; 4], /// Metadata major version, incremented when changes may break backwards compatibility. pub version_major: u8, /// Metadata minor version, incremented when changes do not break backwards compatibility. pub version_minor: u8, /// Reserved for future use; must be 0. pub reserved: [u8; 2], /// A/B slot data. pub slot_data: [AbrSlotData; 2], /// One-shot to bootloader/recovery. pub one_shot_flags: u8, /// Reserved for future use; must be 0. pub reserved2: [u8; 11], /// CRC32 checksum of this struct. pub crc32: u32, } /// Size of `AbrData` pub const ABR_DATA_SIZE: usize = size_of::(); impl AbrData { /// Returns the numeric index value for a `SlotIndex`. This is for indexing into /// `Self::slot_data`. fn slot_num_index(slot_index: SlotIndex) -> usize { match slot_index { SlotIndex::A => 0, SlotIndex::B => 1, _ => panic!("Invalid slot index"), } } /// Returns a const reference to `Self::slot_data['slot_index']` fn slot_data(&self, slot_index: SlotIndex) -> &AbrSlotData { &self.slot_data[Self::slot_num_index(slot_index)] } /// Returns a mutable reference to `Self::slot_data[`slot_index`]` fn slot_data_mut(&mut self, slot_index: SlotIndex) -> &mut AbrSlotData { &mut self.slot_data[Self::slot_num_index(slot_index)] } /// Reads, parses and checks metadata from persistent storage. pub fn deserialize(abr_ops: &mut dyn Ops) -> Result { let mut bytes = [0u8; ABR_DATA_SIZE]; abr_ops.read_abr_metadata(&mut bytes[..])?; // Usually, the parsing below should be done using the zerocopy crate. However, the Fuchsia // source tree uses the unreleased alpha/beta version of zerocopy which can have // drastically different usage and bound requirements. In order to minimize maintenance // burden for Android and Fuchsia build, we manually copy and parse from the bytes directly // to avoid zerocopy crate dependency. let res = Self { magic: bytes[..4].try_into().unwrap(), version_major: bytes[4], version_minor: bytes[5], reserved: bytes[6..8].try_into().unwrap(), slot_data: [ AbrSlotData::deserialize(&bytes[8..12].try_into().unwrap()), AbrSlotData::deserialize(&bytes[12..16].try_into().unwrap()), ], one_shot_flags: bytes[16], reserved2: bytes[17..28].try_into().unwrap(), crc32: u32::from_be_bytes(bytes[28..ABR_DATA_SIZE].try_into().unwrap()), }; if res.magic != *ABR_MAGIC { avb_print!(abr_ops, "Magic is incorrect.\n"); return Err(Error::BadMagic); } if res.crc32 != crc32(&bytes[..28]) { avb_print!(abr_ops, "CRC32 does not match.\n"); return Err(Error::BadChecksum); } if res.version_major > ABR_MAJOR_VERSION { avb_print!(abr_ops, "No support for given major version.\n"); return Err(Error::UnsupportedVersion); } Ok(res) } /// Updates CRC32 and writes metadata to persistent storage. pub fn serialize(&mut self) -> [u8; ABR_DATA_SIZE] { let mut res = [0u8; ABR_DATA_SIZE]; res[..4].clone_from_slice(&self.magic); res[4] = self.version_major; res[5] = self.version_minor; res[6..8].clone_from_slice(&self.reserved); res[8..12].clone_from_slice(&self.slot_data(SlotIndex::A).serialize()); res[12..16].clone_from_slice(&self.slot_data(SlotIndex::B).serialize()); res[16] = self.one_shot_flags; res[17..28].clone_from_slice(&self.reserved2[..]); self.crc32 = crc32(&res[..28]); res[28..ABR_DATA_SIZE].clone_from_slice(&self.crc32.to_be_bytes()); res } /// Returns an invalid instance. fn null() -> Self { Self { magic: [0u8; 4], ..Default::default() } } /// Gets the active slot fn get_active_slot(&self) -> SlotIndex { let priority_a = self.slot_data(SlotIndex::A).get_normalized_priority(); let priority_b = self.slot_data(SlotIndex::B).get_normalized_priority(); if priority_b > priority_a { return SlotIndex::B; } else if priority_a > 0 { return SlotIndex::A; } return SlotIndex::R; } /// Is the given slot active. fn is_slot_active(&self, slot_index: SlotIndex) -> bool { self.get_active_slot() == slot_index } /// Returns if one-shot recovery is set. fn is_one_shot_recovery(&self) -> bool { (self.one_shot_flags & ONE_SHOT_RECOVERY) != 0 } /// Sets one-shot recovery. pub fn set_one_shot_recovery(&mut self, enable: bool) { match enable { true => self.one_shot_flags |= ONE_SHOT_RECOVERY, _ => self.one_shot_flags &= !ONE_SHOT_RECOVERY, } } /// Sets one-shot bootloader pub fn set_one_shot_bootloader(&mut self, enable: bool) { match enable { true => self.one_shot_flags |= ONE_SHOT_BOOTLOADER, _ => self.one_shot_flags &= !ONE_SHOT_BOOTLOADER, } } } impl Default for AbrData { fn default() -> Self { Self { magic: *ABR_MAGIC, version_major: ABR_MAJOR_VERSION, version_minor: ABR_MINOR_VERSION, reserved: Default::default(), slot_data: [ AbrSlotData { priority: ABR_MAX_PRIORITY, tries_remaining: ABR_MAX_TRIES_REMAINING, successful_boot: 0, reserved: 0, }, AbrSlotData { priority: ABR_MAX_PRIORITY - 1, tries_remaining: ABR_MAX_TRIES_REMAINING, successful_boot: 0, reserved: 0, }, ], one_shot_flags: 0, reserved2: Default::default(), crc32: 0, } } } /// Loads |abr_data| from persistent storage and normalizes it, initializing new data if necessary. /// Changes as a result of normalization are not written back to persistent storage but a copy of /// the exact original data from persistent storage is provided in |abr_data_orig| for future use /// with save_metadata_if_changed(). /// /// On success returns Ok((abr_data, abr_data_orig)). On failure an Error is returned. fn load_metadata(abr_ops: &mut dyn Ops) -> Result<(AbrData, AbrData)> { let mut abr_data_orig = AbrData::null(); let mut abr_data = match AbrData::deserialize(abr_ops) { Ok(v) => { abr_data_orig = v; v } Err(Error::Other(e)) => { avb_print!(abr_ops, "read_abr_metadata error: {:?}\n", e); return Err(e.into()); } Err(Error::UnsupportedVersion) => { // We don't want to clobber valid data in persistent storage, but we can't use this // data, so bail out. return Err(Error::UnsupportedVersion); } _ => Default::default(), }; abr_data.slot_data_mut(SlotIndex::A).slot_normalize(); abr_data.slot_data_mut(SlotIndex::B).slot_normalize(); Ok((abr_data, abr_data_orig)) } /// Serializes and saves metadata to persistent storage. fn save_metadata(abr_ops: &mut dyn Ops, abr_data: &mut AbrData) -> Result<()> { let mut bytes = abr_data.serialize(); abr_ops.write_abr_metadata(&mut bytes)?; Ok(()) } /// Writes metadata to disk only if it has changed. `abr_data_orig` should be from load_metadata(). fn save_metadata_if_changed( abr_ops: &mut dyn Ops, abr_data: &mut AbrData, abr_data_orig: &AbrData, ) -> Result<()> { match abr_data == abr_data_orig { true => Ok(()), _ => save_metadata(abr_ops, abr_data), } } /// Equivalent to C API `AbrGetBootSlot()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn get_boot_slot(abr_ops: &mut dyn Ops, update_metadata: bool) -> (SlotIndex, bool) { let mut is_slot_marked_successful = false; let (mut abr_data, abr_data_orig) = match load_metadata(abr_ops) { Ok(v) => v, Err(e) => { avb_print!( abr_ops, "Failed to load metadata {:?}, falling back to recovery mode.\n", e ); return (SlotIndex::R, is_slot_marked_successful); } }; if abr_data.is_one_shot_recovery() && update_metadata { abr_data.set_one_shot_recovery(false); match save_metadata(abr_ops, &mut abr_data) { Ok(()) => return (SlotIndex::R, is_slot_marked_successful), Err(e) => { avb_print!( abr_ops, "Failed to update one-shot state {:?}. Ignoring one-shot request.\n", e ); abr_data.set_one_shot_recovery(true); } } } // Chooses the highest priority and bootable slot. Otherwise R slot. let slot_to_boot = abr_data.get_active_slot(); match slot_to_boot { SlotIndex::R => {} v => { is_slot_marked_successful = abr_data.slot_data(v).successful_boot == 1; } }; if update_metadata { // In addition to any changes that resulted from normalization, there are a couple changes // to be made here. First is to decrement the tries remaining for a slot not yet marked as // successful. if slot_to_boot != SlotIndex::R && !is_slot_marked_successful { let slot_data = abr_data.slot_data_mut(slot_to_boot); slot_data.tries_remaining = slot_data.tries_remaining.checked_sub(1).unwrap(); } // Second is to clear the successful_boot bit from any successfully-marked slots that // aren't the slot we're booting. It's possible that booting from one slot will render the // other slot unbootable (say, by migrating a config file format in a shared partiton). // Clearing these bits minimizes the risk we'll have an unhealthy slot marked // "successful_boot", which would prevent the system from automatically booting into // recovery. for slot in [SlotIndex::A, SlotIndex::B] { if slot != slot_to_boot && abr_data.slot_data(slot).successful_boot == 1 { abr_data.slot_data_mut(slot).tries_remaining = ABR_MAX_TRIES_REMAINING; abr_data.slot_data_mut(slot).successful_boot = 0; } } if let Err(e) = save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) { // We have no choice but to proceed without updating metadata. avb_print!(abr_ops, "Failed to update metadata {:?}, proceeding anyways.\n", e); } } (slot_to_boot, is_slot_marked_successful) } /// Equivalent to C API `AbrMarkSlotActive()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn mark_slot_active(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> { if slot_index == SlotIndex::R { avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as active.\n"); return Err(Error::InvalidInput); } let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; // Make requested slot top priority, unsuccessful, and with max tries. abr_data.slot_data_mut(slot_index).priority = ABR_MAX_PRIORITY; abr_data.slot_data_mut(slot_index).tries_remaining = ABR_MAX_TRIES_REMAINING; abr_data.slot_data_mut(slot_index).successful_boot = 0; // Ensure other slot doesn't have as high a priority let other = slot_index.other(); abr_data.slot_data_mut(other).priority = min(abr_data.slot_data_mut(other).priority, ABR_MAX_PRIORITY - 1); save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) } /// Equivalent to C API `AbrGetSlotLastMarkedActive()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn get_slot_last_marked_active(abr_ops: &mut dyn Ops) -> Result { let (abr_data, _) = load_metadata(abr_ops)?; Ok( match abr_data.slot_data(SlotIndex::B).priority > abr_data.slot_data(SlotIndex::A).priority { true => SlotIndex::B, false => SlotIndex::A, }, ) } /// Equivalent to C API `AbrMarkSlotUnbootable()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn mark_slot_unbootable(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> { if slot_index == SlotIndex::R { avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as unbootable.\n"); return Err(Error::InvalidInput); } let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; abr_data.slot_data_mut(slot_index).set_slot_unbootable(); save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) } /// Equivalent to C API `AbrMarkSlotSuccessful()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn mark_slot_successful(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> { if slot_index == SlotIndex::R { avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as successful.\n"); return Err(Error::InvalidInput); } let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; if !abr_data.slot_data(slot_index).is_slot_bootable() { avb_print!(abr_ops, "Invalid argument: Cannot mark unbootable slot as successful.\n"); return Err(Error::InvalidInput); } abr_data.slot_data_mut(slot_index).tries_remaining = 0; abr_data.slot_data_mut(slot_index).successful_boot = 1; // Proactively remove any success mark on the other slot // // This can theoretically be removed since get_boot_slot() clear successful bit on non-boot // slots. However, legacy devices might still be using old versions of ABR implementation that // don't clear it. Therefore, we keep this logic to be safe. // // Context: https://fxbug.dev/42142842, https://crbug.com/fuchsia/64057. let other = slot_index.other(); if abr_data.slot_data(other).is_slot_bootable() { abr_data.slot_data_mut(other).tries_remaining = ABR_MAX_TRIES_REMAINING; abr_data.slot_data_mut(other).successful_boot = 0; } save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) } /// Equivalent to C API `AbrGetSlotInfo()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn get_slot_info(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result { let (abr_data, _) = load_metadata(abr_ops)?; Ok(match slot_index { // Assume that R slot is always OK. SlotIndex::R => SlotInfo { state: SlotState::Successful, is_active: abr_data.is_slot_active(SlotIndex::R), }, _ => { let slot_data = abr_data.slot_data(slot_index); let state = match slot_data.successful_boot == 1 { true => SlotState::Successful, _ if slot_data.is_slot_bootable() => SlotState::Bootable(slot_data.tries_remaining), _ => SlotState::Unbootable, }; SlotInfo { state, is_active: abr_data.is_slot_active(slot_index) } } }) } /// Equivalent to C API `AbrSetOneShotRecovery()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn set_one_shot_recovery(abr_ops: &mut dyn Ops, enable: bool) -> Result<()> { let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; abr_data.set_one_shot_recovery(enable); save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) } /// Equivalent to C API `AbrSetOneShotBootloader()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn set_one_shot_bootloader(abr_ops: &mut dyn Ops, enable: bool) -> Result<()> { let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; abr_data.set_one_shot_bootloader(enable); save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) } /// Equivalent to C API `AbrGetAndClearOneShotFlags()`. /// /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. pub fn get_and_clear_one_shot_flag(abr_ops: &mut dyn Ops) -> Result { let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; let res = abr_data.one_shot_flags; abr_data.one_shot_flags = 0; save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?; Ok(res) } /// Gets and clears one shot bootloader flag only. pub fn get_and_clear_one_shot_bootloader(abr_ops: &mut dyn Ops) -> Result { let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; let res = abr_data.one_shot_flags; abr_data.one_shot_flags &= !ONE_SHOT_BOOTLOADER; save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?; Ok((res & ONE_SHOT_BOOTLOADER) != 0) } /// Reverses the bit of a byte. fn reverse_byte(b: u8) -> u8 { const LOOKUP_TABLE_4BIT_REVERSE: &[u8] = &[0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF]; LOOKUP_TABLE_4BIT_REVERSE[(b >> 4) as usize] | (LOOKUP_TABLE_4BIT_REVERSE[(b & 0xf) as usize] << 4) } // Reverses the bits of a u32; fn reverse_u32(val: u32) -> u32 { let mut bytes = val.to_le_bytes(); bytes.iter_mut().for_each(|v| *v = reverse_byte(*v)); u32::from_be_bytes(bytes) } // Calculates the crc32 of the given bytes. fn crc32(data: &[u8]) -> u32 { let mut res: u32 = 0xffffffff; for b in data { res ^= (reverse_byte(*b) as u32) << 24; for _ in 0..8 { if (res & 0x80000000) != 0 { res = (res << 1) ^ 0x04C11DB7; } else { res <<= 1; } } } reverse_u32(!res) } #[cfg(test)] mod test { use super::*; // Testing is currently done against the C interface tests in upstream Fuchsia: // https://fuchsia.googlesource.com/fuchsia/+/96f7268b497f998ffcbeef73425b031bd7f4db65/src/firmware/lib/abr/test/libabr_test.cc // These tests will be ported to here as rust tests in the future. #[test] fn test_get_and_clear_one_shot_bootloader() { let mut meta = [0u8; ABR_DATA_SIZE]; set_one_shot_bootloader(&mut meta, true).unwrap(); set_one_shot_recovery(&mut meta, true).unwrap(); assert!(get_and_clear_one_shot_bootloader(&mut meta).unwrap()); assert_eq!(get_and_clear_one_shot_flag(&mut meta).unwrap(), ONE_SHOT_RECOVERY); } }