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
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",