blob: 208dd88f6ff704cf185ee4a41b0430172e04b9c5 [file] [log] [blame]
// 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 library provides APIs to work with data structures inside Android misc partition.
//!
//! Reference code:
//! https://cs.android.com/android/platform/superproject/main/+/main:bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h
//!
//! TODO(b/329716686): Generate rust bindings for misc API from recovery to reuse the up to date
//! implementation
#![cfg_attr(not(test), no_std)]
use core::ffi::CStr;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref};
use liberror::{Error, Result};
/// Android boot modes type
/// Usually obtained from BCB block of misc partition
#[derive(PartialEq, Debug)]
pub enum AndroidBootMode {
/// Boot normally using A/B slots.
Normal = 0,
/// Boot into recovery mode using A/B slots.
Recovery,
/// Stop in bootloader fastboot mode.
BootloaderBootOnce,
}
impl core::fmt::Display for AndroidBootMode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
AndroidBootMode::Normal => write!(f, "AndroidBootMode::Normal"),
AndroidBootMode::Recovery => write!(f, "AndroidBootMode::Recovery"),
AndroidBootMode::BootloaderBootOnce => write!(f, "AndroidBootMode::BootloaderBootOnce"),
}
}
}
/// BCB command field offset within BCB block.
pub const COMMAND_FIELD_OFFSET: usize = 0;
/// BCB command field size in bytes.
pub const COMMAND_FIELD_SIZE: usize = 32;
/// Android bootloader message structure that usually placed in the first block of misc partition
///
/// Reference code:
/// https://cs.android.com/android/platform/superproject/main/+/95ec3cc1d879b92dd9db3bb4c4345c5fc812cdaa:bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h;l=67
#[repr(C, packed)]
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Copy, Clone, Debug)]
pub struct BootloaderMessage {
command: [u8; COMMAND_FIELD_SIZE],
status: [u8; 32],
recovery: [u8; 768],
stage: [u8; 32],
reserved: [u8; 1184],
}
impl BootloaderMessage {
/// BCB size in bytes
pub const SIZE_BYTES: usize = 2048;
/// Extract BootloaderMessage reference from bytes
pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootloaderMessage> {
Ok(Ref::into_ref(
Ref::<_, BootloaderMessage>::new_from_prefix(buffer)
.ok_or(Error::BufferTooSmall(Some(core::mem::size_of::<BootloaderMessage>())))?
.0
.into(),
))
}
/// Extract AndroidBootMode from BCB command field
pub fn boot_mode(&self) -> Result<AndroidBootMode> {
let command = CStr::from_bytes_until_nul(&self.command)
.map_err(|_| Error::Other(Some("Cannot read BCB command")))?
.to_str()
.map_err(|_| Error::InvalidInput)?;
match command {
"" => Ok(AndroidBootMode::Normal),
"boot-recovery" | "boot-fastboot" => Ok(AndroidBootMode::Recovery),
"bootonce-bootloader" => Ok(AndroidBootMode::BootloaderBootOnce),
_ => Err(Error::Other(Some("Wrong BCB command"))),
}
}
}
#[cfg(test)]
mod test {
use crate::AndroidBootMode;
use crate::BootloaderMessage;
use zerocopy::IntoBytes;
impl Default for BootloaderMessage {
fn default() -> Self {
BootloaderMessage {
command: [0; 32],
status: [0; 32],
recovery: [0; 768],
stage: [0; 32],
reserved: [0; 1184],
}
}
}
#[test]
fn test_bcb_empty_parsed_as_normal() {
let bcb = BootloaderMessage::default();
assert_eq!(
BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(),
AndroidBootMode::Normal
);
}
#[test]
fn test_bcb_with_wrong_command_failed() {
let command = "boot-wrong";
let mut bcb = BootloaderMessage::default();
bcb.command[..command.len()].copy_from_slice(command.as_bytes());
assert!(BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().is_err());
}
#[test]
fn test_bcb_to_recovery_parsed() {
let command = "boot-recovery";
let mut bcb = BootloaderMessage::default();
bcb.command[..command.len()].copy_from_slice(command.as_bytes());
assert_eq!(
BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(),
AndroidBootMode::Recovery
);
}
#[test]
fn test_bcb_to_fastboot_parsed_as_recovery() {
let command = "boot-fastboot";
let mut bcb = BootloaderMessage::default();
bcb.command[..command.len()].copy_from_slice(command.as_bytes());
assert_eq!(
BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(),
AndroidBootMode::Recovery
);
}
#[test]
fn test_bcb_to_bootloader_once_parsed() {
let command = "bootonce-bootloader";
let mut bcb = BootloaderMessage::default();
bcb.command[..command.len()].copy_from_slice(command.as_bytes());
assert_eq!(
BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(),
AndroidBootMode::BootloaderBootOnce
);
}
}