summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYecheng Zhao <zyecheng@google.com>2024-04-23 19:08:02 +0000
committerYecheng Zhao <zyecheng@google.com>2024-05-01 19:09:09 +0000
commit611dbc99bb47faafcd5d8356f455d122660e80d5 (patch)
tree1d31309953278665c00565cfae14d86c99bc14ba
parent3b27c6f519f4ca5a96466129046397d1f79574f8 (diff)
downloadlibbootloader-611dbc99bb47faafcd5d8356f455d122660e80d5.tar.gz
Put A/B/R logic in a separate libabr
Adds a libabr library to host Fuchsia A/B/R logic without the gbl dependencies. This will be the first candidate for code share with Fuchsia and will be replacing the Fuchsia libabr C library. libgbl may also use the library to ensure backward compatibility for existing devices that use the original libabr. The library is mostly a line to line translation, including code comments, from the Fuchsia C libabr. This makes sure that we are keeping all subtle considerations and bug fixes that we have accumulated over the past. The library provides a C static library build target that exports the same C interfaces as C libabr. It currently passes all upstream tests in "src/firmware/lib/abr/test/libabr_test.cc". The only exceptions are the `read/write_abr_metadata_custom` usage which we'll deprecate since atlas is no longer relevant. Bug: 336318818 Change-Id: Ifa179ef6cd50a887dacefd8b92ac790918d34fe0
-rw-r--r--gbl/libabr/BUILD56
-rw-r--r--gbl/libabr/build/fuchsia/BUILD.gn39
-rw-r--r--gbl/libabr/src/c_staticlib.rs413
-rw-r--r--gbl/libabr/src/lib.rs662
-rw-r--r--gbl/libabr/src/utils.rs87
-rw-r--r--gbl/tests/BUILD1
6 files changed, 1258 insertions, 0 deletions
diff --git a/gbl/libabr/BUILD b/gbl/libabr/BUILD
new file mode 100644
index 0000000..aa1eef1
--- /dev/null
+++ b/gbl/libabr/BUILD
@@ -0,0 +1,56 @@
+# Copyright (C) 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.
+
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_static_library", "rust_test")
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+rust_library(
+ name = "libabr",
+ srcs = ["src/lib.rs"],
+ crate_name = "abr",
+ edition = "2021",
+)
+
+rust_test(
+ name = "libabr_test",
+ crate = ":libabr",
+)
+
+rust_static_library(
+ name = "libabr_c",
+ srcs = [
+ "src/c_staticlib.rs",
+ "src/utils.rs",
+ ],
+ crate_name = "abr_c",
+ crate_root = "src/c_staticlib.rs",
+ edition = "2021",
+ deps = [":libabr"],
+)
+
+rust_test(
+ name = "libabr_c_test",
+ crate = ":libabr_c",
+)
+
+test_suite(
+ name = "libabr_tests",
+ tests = [
+ ":libabr_c_test",
+ ":libabr_test",
+ ],
+)
diff --git a/gbl/libabr/build/fuchsia/BUILD.gn b/gbl/libabr/build/fuchsia/BUILD.gn
new file mode 100644
index 0000000..78cbe61
--- /dev/null
+++ b/gbl/libabr/build/fuchsia/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright (C) 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.
+
+import("//build/rust/rustc_library.gni")
+import("//build/rust/rustc_staticlib.gni")
+
+rustc_library("libabr") {
+ edition = "2021"
+ name = "abr"
+ source_root = "../../src/lib.rs"
+ sources = [ "../../src/lib.rs" ]
+}
+
+config("panic_as_abort") {
+ rustflags = [ "-Cpanic=abort" ]
+}
+
+rustc_staticlib("libabr_c") {
+ edition = "2021"
+ name = "abr"
+ source_root = "../../src/c_staticlib.rs"
+ sources = [
+ "../../src/c_staticlib.rs",
+ "../../src/utils.rs",
+ ]
+ deps = [ ":libabr" ]
+ configs += [":panic_as_abort"]
+}
diff --git a/gbl/libabr/src/c_staticlib.rs b/gbl/libabr/src/c_staticlib.rs
new file mode 100644
index 0000000..0110580
--- /dev/null
+++ b/gbl/libabr/src/c_staticlib.rs
@@ -0,0 +1,413 @@
+// 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.
+
+//! This file provides C interface wrappers of libabr APIs.
+
+#![cfg_attr(not(test), no_std)]
+
+use abr::{
+ get_and_clear_one_shot_flag, get_boot_slot, get_slot_info, get_slot_last_marked_active,
+ mark_slot_active, mark_slot_successful, mark_slot_unbootable, set_one_shot_bootloader,
+ set_one_shot_recovery, AbrResult, Error, Ops, SlotIndex, SlotInfo as AbrSlotInfo, SlotState,
+};
+use core::{
+ ffi::{c_char, c_uint, c_void},
+ fmt::Write,
+};
+
+pub mod utils;
+
+pub const ABR_RESULT_OK: c_uint = 0;
+pub const ABR_RESULT_ERR_IO: c_uint = 1;
+pub const ABR_RESULT_ERR_INVALID_DATA: c_uint = 2;
+pub const ABR_RESULT_ERR_UNSUPPORTED_VERSION: c_uint = 3;
+
+// ABR system dependencies.
+//
+// These correspond to the definitions in Fuchsia upstream header
+// "src/firmware/lib/abr/include/lib/abr/sysdeps.h", which will eventually migrate over.
+extern "C" {
+ /// Prints out a NULL-terminated string.
+ pub fn AbrPrint(message: *const c_char);
+
+ /// Aborts the program or reboots the device if |abort| is not implemented.
+ pub fn AbrAbort();
+}
+
+/// A helper to print an ASCII character via `AbrPrint()`.
+fn abr_print_ascii_char(ch: u8) {
+ let s = [ch, 0];
+ // SAFETY:
+ // * `s` is a valid buffer
+ // * `s` is for input only and will not be retained by the function.
+ unsafe { AbrPrint(s.as_ptr() as _) }
+}
+
+/// A helper structure that implements formatted write using `AbrPrint()`.
+struct AbrPrintSysdeps {}
+
+impl Write for AbrPrintSysdeps {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ if s.is_ascii() {
+ s.as_bytes().iter().for_each(|v| abr_print_ascii_char(*v));
+ }
+ Ok(())
+ }
+}
+
+/// A panic handler is needed when building as a static library. We simply call into
+/// the AbrAbort() system dependency.
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(panic: &core::panic::PanicInfo<'_>) -> ! {
+ write!(AbrPrintSysdeps {}, "libabr panics! {}", panic).unwrap();
+ // SAFETY: Call to external C function. The function simply aborts/reboots the system.
+ unsafe { AbrAbort() };
+ unreachable!()
+}
+
+/// This corresponds to the `AbrOps` C definition in Fuchsia upstream header
+/// "src/firmware/lib/abr/include/lib/abr/ops.h", which will eventually migrate over.
+///
+/// typedef struct AbrOps {
+/// void* context;
+/// bool (*read_abr_metadata)(void* context, size_t size, uint8_t* buffer);
+/// bool (*write_abr_metadata)(void* context, const uint8_t* buffer, size_t size);
+/// } AbrOps;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct AbrOps {
+ pub context: *mut c_void,
+ pub read_abr_metadata:
+ Option<unsafe extern "C" fn(context: *mut c_void, size: usize, buffer: *mut u8) -> bool>,
+ pub write_abr_metadata:
+ Option<unsafe extern "C" fn(context: *mut c_void, buffer: *const u8, size: usize) -> bool>,
+}
+
+/// `AbrOpsSafe` wraps a reference to `AbrOps` and is created by an unsafe constructor that
+/// establishes necessary safety invariants on `AbrOps`.
+struct AbrOpsSafe<'a> {
+ ops: &'a AbrOps,
+ log: AbrPrintSysdeps,
+}
+
+impl<'a> AbrOpsSafe<'a> {
+ /// Creates a new instance from a reference to `AbrOps`.
+ ///
+ /// # Safety
+ ///
+ /// * Caller must make sure that `ops.context` is either not used, or points to a valid and
+ /// correct type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+ unsafe fn new(ops: &'a AbrOps) -> Self {
+ Self { ops, log: AbrPrintSysdeps {} }
+ }
+}
+
+type AbrSlotIndex = c_uint;
+
+impl Ops for AbrOpsSafe<'_> {
+ fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<(), Option<&'static str>> {
+ let read_abr_metadata =
+ self.ops.read_abr_metadata.ok_or(Some("Missing read_abr_metadata() method"))?;
+ // SAFETY:
+ // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, or
+ // a valid pointer to a correct type of object used by `self.ops.read_abr_metadata`.
+ // * `out` is a valid buffer
+ // * `out` is for reading data only and will not be retained by the function.
+ match unsafe { read_abr_metadata(self.ops.context, out.len(), out.as_mut_ptr() as _) } {
+ false => Err(Some("read_abr_metadata() failed")),
+ _ => Ok(()),
+ }
+ }
+
+ fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<(), Option<&'static str>> {
+ let write_abr_metadata =
+ self.ops.write_abr_metadata.ok_or(Some("Missing write_abr_metadata() method"))?;
+ // SAFETY:
+ // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, or
+ // a valid pointer to a correct type of object used by `self.ops.write_abr_metadata`.
+ // * `data` is a valid buffer.
+ // * `data` is for input only and will not be retained by the function.
+ match unsafe { write_abr_metadata(self.ops.context, data.as_ptr() as _, data.len()) } {
+ false => Err(Some("write_abr_metadata() failed")),
+ _ => Ok(()),
+ }
+ }
+
+ fn console(&mut self) -> Option<&mut dyn core::fmt::Write> {
+ Some(&mut self.log)
+ }
+}
+
+/// A helper that extracts the return value and maps the result to an integer A/B/R result code.
+fn unpack_result<T: Into<O>, O>(res: AbrResult<T>, val: &mut O) -> c_uint {
+ match res {
+ Err(e) => match e {
+ Error::BadMagic | Error::BadChecksum | Error::InvalidData => {
+ ABR_RESULT_ERR_INVALID_DATA
+ }
+ Error::UnsupportedVersion => ABR_RESULT_ERR_UNSUPPORTED_VERSION,
+ Error::OpsError(_) => ABR_RESULT_ERR_IO,
+ },
+ Ok(v) => {
+ *val = v.into();
+ ABR_RESULT_OK
+ }
+ }
+}
+
+/// C interface wrapper of `abr::get_boot_slot()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+/// * Caller must make sure to pass either a NULL or valid pointer for `is_slot_marked_successful`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrGetBootSlot(
+ abr_ops: *const AbrOps,
+ update_metadata: bool,
+ is_slot_marked_successful: *mut bool,
+) -> AbrSlotIndex {
+ let mut abr_ops = AbrOpsSafe::new(abr_ops.as_ref().unwrap());
+ let (slot_index, successful) = get_boot_slot(&mut abr_ops, update_metadata);
+ match is_slot_marked_successful.as_mut() {
+ Some(v) => *v = successful,
+ _ => {}
+ };
+ slot_index.into()
+}
+
+// NULL terminated strings for slot suffixes.
+const SLOT_A_SUFFIX: &[u8] = b"_a\0";
+const SLOT_B_SUFFIX: &[u8] = b"_b\0";
+const SLOT_R_SUFFIX: &[u8] = b"_r\0";
+const SLOT_SUFFIX_INVALID: &[u8] = b"\0";
+
+/// C interface for getting slot suffix.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn AbrGetSlotSuffix(slot_index: AbrSlotIndex) -> *const c_char {
+ match slot_index.try_into() {
+ Ok(SlotIndex::A) => &SLOT_A_SUFFIX,
+ Ok(SlotIndex::B) => &SLOT_B_SUFFIX,
+ Ok(SlotIndex::R) => &SLOT_R_SUFFIX,
+ Err(_) => &SLOT_SUFFIX_INVALID,
+ }
+ .as_ptr() as _
+}
+
+/// C interface wrapper of `abr::mark_slot_active()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrMarkSlotActive(
+ abr_ops: *const AbrOps,
+ slot_index: AbrSlotIndex,
+) -> c_uint {
+ let slot_index = match slot_index.try_into() {
+ Ok(v) => v,
+ Err(_) => return ABR_RESULT_ERR_INVALID_DATA,
+ };
+ unpack_result(
+ mark_slot_active(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index),
+ &mut (),
+ )
+}
+
+/// C interface wrapper of `abr::get_slot_last_marked_active()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops` and `out_slot`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrGetSlotLastMarkedActive(
+ abr_ops: *const AbrOps,
+ out_slot: *mut AbrSlotIndex,
+) -> c_uint {
+ unpack_result(
+ get_slot_last_marked_active(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap())),
+ out_slot.as_mut().unwrap(),
+ )
+}
+
+/// C interface wrapper of `abr::mark_slot_unbootable()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrMarkSlotUnbootable(
+ abr_ops: *const AbrOps,
+ slot_index: AbrSlotIndex,
+) -> c_uint {
+ let slot_index = match slot_index.try_into() {
+ Ok(v) => v,
+ Err(_) => return ABR_RESULT_ERR_INVALID_DATA,
+ };
+ unpack_result(
+ mark_slot_unbootable(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index),
+ &mut (),
+ )
+}
+
+/// C interface wrapper of `abr::mark_slot_successful()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrMarkSlotSuccessful(
+ abr_ops: *const AbrOps,
+ slot_index: AbrSlotIndex,
+) -> c_uint {
+ let slot_index = match slot_index.try_into() {
+ Ok(v) => v,
+ Err(_) => return ABR_RESULT_ERR_INVALID_DATA,
+ };
+ unpack_result(
+ mark_slot_successful(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index),
+ &mut (),
+ )
+}
+
+/// `SlotInfo` contains the current state of a A/B/R slot.
+///
+/// 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.
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct SlotInfo {
+ /// Whether the slot is expected to be bootable.
+ pub is_bootable: bool,
+ /// Whether the slot is the highest priority A/B slot.
+ pub is_active: bool,
+ /// Whether the slot is currently marked successful.
+ pub is_marked_successful: bool,
+ /// If not marked successful, this represents the number of attempts left for booting this slot.
+ pub num_tries_remaining: u8,
+}
+
+impl From<AbrSlotInfo> for SlotInfo {
+ fn from(val: abr::SlotInfo) -> Self {
+ let is_marked_successful = matches!(val.state, SlotState::Successful);
+ let num_tries_remaining = match val.state {
+ SlotState::Bootable(v) => v,
+ _ => 0,
+ };
+ Self {
+ is_bootable: is_marked_successful || num_tries_remaining > 0,
+ is_active: val.is_active,
+ is_marked_successful,
+ num_tries_remaining,
+ }
+ }
+}
+
+/// C interface wrapper of `abr::get_slot_info()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops` and 'info'.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrGetSlotInfo(
+ abr_ops: *const AbrOps,
+ slot_index: AbrSlotIndex,
+ info: *mut SlotInfo,
+) -> c_uint {
+ let slot_index = match slot_index.try_into() {
+ Ok(v) => v,
+ Err(_) => return ABR_RESULT_ERR_INVALID_DATA,
+ };
+ unpack_result(
+ get_slot_info(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index)
+ .map(|v| SlotInfo::from(v)),
+ info.as_mut().unwrap(),
+ )
+}
+
+/// C interface wrapper of `abr::set_one_shot_recovery()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrSetOneShotRecovery(abr_ops: *const AbrOps, enable: bool) -> c_uint {
+ unpack_result(
+ set_one_shot_recovery(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), enable),
+ &mut (),
+ )
+}
+
+/// C interface wrapper of `abr::set_one_shot_bootloader()`
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrSetOneShotBootloader(abr_ops: *const AbrOps, enable: bool) -> c_uint {
+ unpack_result(
+ set_one_shot_bootloader(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), enable),
+ &mut (),
+ )
+}
+
+/// Gets and clears the one shot flag.
+///
+/// # Safety
+///
+/// * Caller must make sure to pass a valid pointer for `abr_ops` and `flags`.
+/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct
+/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrGetAndClearOneShotFlags(
+ abr_ops: *const AbrOps,
+ flags: *mut c_uint,
+) -> c_uint {
+ unpack_result(
+ get_and_clear_one_shot_flag(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap())),
+ flags.as_mut().unwrap(),
+ )
+}
+
+// Needed because of no-std environment in static lib build.
+#[cfg(not(test))]
+#[no_mangle]
+pub extern "C" fn rust_eh_personality() {}
diff --git a/gbl/libabr/src/lib.rs b/gbl/libabr/src/lib.rs
new file mode 100644
index 0000000..dd0075c
--- /dev/null
+++ b/gbl/libabr/src/lib.rs
@@ -0,0 +1,662 @@
+// 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.
+
+#![cfg_attr(not(test), no_std)]
+
+use core::{cmp::min, ffi::c_uint, fmt::Write, mem::size_of};
+
+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;
+const ABR_MAX_TRIES_REMAINING: u8 = 7;
+
+/// Error type for this library.
+#[derive(Debug)]
+pub enum Error {
+ BadMagic,
+ UnsupportedVersion,
+ BadChecksum,
+ InvalidData,
+ OpsError(Option<&'static str>),
+}
+
+impl From<Option<&'static str>> for Error {
+ fn from(val: Option<&'static str>) -> Self {
+ Error::OpsError(val)
+ }
+}
+
+/// `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<(), Option<&'static str>>;
+
+ /// 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<(), Option<&'static str>>;
+
+ /// Returns an optional console writer for logging error messages.
+ fn console(&mut self) -> Option<&mut dyn Write>;
+}
+
+/// 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,
+ B,
+ 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<SlotIndex> for c_uint {
+ fn from(_val: SlotIndex) -> Self {
+ match _val {
+ SlotIndex::A => 0,
+ SlotIndex::B => 1,
+ SlotIndex::R => 2,
+ }
+ }
+}
+
+// Implement conversion from c_uint for C interfaces.
+impl TryFrom<c_uint> for SlotIndex {
+ type Error = Error;
+
+ fn try_from(val: c_uint) -> core::result::Result<SlotIndex, Self::Error> {
+ 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::InvalidData),
+ }
+ }
+}
+
+/// `SlotInfo` represents the current state of a A/B/R slot.
+pub enum SlotState {
+ Successful,
+ Bootable(u8), // u8 = tries remaining
+ Unbootable,
+}
+
+/// `SlotInfo` contains the current state and active status of a A/B/R slot.
+pub struct SlotInfo {
+ pub state: SlotState,
+ pub is_active: bool,
+}
+
+/// Error type for this library.
+pub type AbrResult<T> = Result<T, Error>;
+
+/// `AbrSlotData` is the wire format metadata for A/B slot.
+#[repr(C, packed)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct AbrSlotData {
+ pub priority: u8,
+ pub tries_remaining: u8,
+ pub successful_boot: u8,
+ pub reserved: u8,
+}
+
+const ABR_SLOT_DATA_SIZE: usize = size_of::<AbrSlotData>();
+
+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 {
+ pub magic: [u8; 4],
+ pub version_major: u8,
+ pub version_minor: u8,
+ pub reserved: [u8; 2],
+ pub slot_data: [AbrSlotData; 2],
+ pub one_shot_flags: u8,
+ pub reserved2: [u8; 11],
+ pub crc32: u32,
+}
+
+const ABR_DATA_SIZE: usize = size_of::<AbrData>();
+
+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.
+ fn deserialize(abr_ops: &mut dyn Ops) -> AbrResult<Self> {
+ 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.
+ 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) -> AbrResult<(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::OpsError(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) -> AbrResult<()> {
+ 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,
+) -> AbrResult<()> {
+ 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) -> AbrResult<()> {
+ if slot_index == SlotIndex::R {
+ avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as active.\n");
+ return Err(Error::InvalidData);
+ }
+ 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) -> AbrResult<SlotIndex> {
+ 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) -> AbrResult<()> {
+ if slot_index == SlotIndex::R {
+ avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as unbootable.\n");
+ return Err(Error::InvalidData);
+ }
+ 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) -> AbrResult<()> {
+ if slot_index == SlotIndex::R {
+ avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as successful.\n");
+ return Err(Error::InvalidData);
+ }
+ 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::InvalidData);
+ }
+
+ 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) -> AbrResult<SlotInfo> {
+ 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) -> AbrResult<()> {
+ 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) -> AbrResult<()> {
+ 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) -> AbrResult<u8> {
+ 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)
+}
+
+/// 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 {
+ // 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.
+}
diff --git a/gbl/libabr/src/utils.rs b/gbl/libabr/src/utils.rs
new file mode 100644
index 0000000..0c446d3
--- /dev/null
+++ b/gbl/libabr/src/utils.rs
@@ -0,0 +1,87 @@
+// 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.
+
+use abr::{AbrData, ONE_SHOT_BOOTLOADER, ONE_SHOT_RECOVERY};
+
+/// Converts big endian order to host order.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn AbrBigEndianToHost(val: u32) -> u32 {
+ u32::from_be(val)
+}
+
+/// Converts host order to big endian.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn AbrHostToBigEndian(val: u32) -> u32 {
+ val.to_be()
+}
+
+/// Checks if one-shot recovery boot is set in the given one-shot flags
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn AbrIsOneShotRecoveryBootSet(flags: u8) -> bool {
+ (flags & ONE_SHOT_RECOVERY) != 0
+}
+
+/// Checks if one-shot recovery boot is set in the given AbrData
+///
+/// # Safety
+///
+/// Caller must make sure to pass a valid pointer for `abr_data`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrIsOneShotRecoveryBoot(abr_data: *const AbrData) -> bool {
+ AbrIsOneShotRecoveryBootSet(abr_data.as_ref().unwrap().one_shot_flags)
+}
+
+/// Checks if one-shot bootloader boot is set in the given one-shot flags
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn AbrIsOneShotBootloaderBootSet(flags: u8) -> bool {
+ (flags & ONE_SHOT_BOOTLOADER) != 0
+}
+
+/// Checks if one-shot bootloader boot is set in the given AbrData
+///
+/// # Safety
+///
+/// Caller must make sure to pass a valid pointer for `abr_data`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrIsOneShotBootloaderBoot(abr_data: *const AbrData) -> bool {
+ AbrIsOneShotBootloaderBootSet(abr_data.as_ref().unwrap().one_shot_flags)
+}
+
+/// Sets the one-shot recovery flag in the given AbrData.
+///
+/// # Safety
+///
+/// Caller must make sure to pass a valid pointer for `abr_data`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrSetOneShotRecoveryBoot(abr_data: *mut AbrData, enable: bool) {
+ abr_data.as_mut().unwrap().set_one_shot_recovery(enable);
+}
+
+/// Sets the one-shot bootloader flag in the given AbrData.
+///
+/// # Safety
+///
+/// Caller must make sure to pass a valid pointer for `abr_data`.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub unsafe extern "C" fn AbrSetOneShotBootloaderBoot(abr_data: *mut AbrData, enable: bool) {
+ abr_data.as_mut().unwrap().set_one_shot_bootloader(enable);
+}
diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD
index 27b6952..833d7a6 100644
--- a/gbl/tests/BUILD
+++ b/gbl/tests/BUILD
@@ -15,6 +15,7 @@
test_suite(
name = "tests",
tests = [
+ "@gbl//libabr:libabr_tests",
"@gbl//libbootconfig:libbootconfig_test",
"@gbl//libbootimg:libbootimg_test",
"@gbl//libefi:libefi_test",