1 // Copyright 2024, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! This library provides APIs to work with data structures inside Android misc partition. 16 //! 17 //! Reference code: 18 //! https://cs.android.com/android/platform/superproject/main/+/main:bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h 19 //! 20 //! TODO(b/329716686): Generate rust bindings for misc API from recovery to reuse the up to date 21 //! implementation 22 23 #![cfg_attr(not(test), no_std)] 24 25 use core::ffi::CStr; 26 27 use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref}; 28 29 use liberror::{Error, Result}; 30 31 /// Android boot modes type 32 /// Usually obtained from BCB block of misc partition 33 #[derive(PartialEq, Debug)] 34 pub enum AndroidBootMode { 35 /// Boot normally using A/B slots. 36 Normal = 0, 37 /// Boot into recovery mode using A/B slots. 38 Recovery, 39 /// Stop in bootloader fastboot mode. 40 BootloaderBootOnce, 41 } 42 43 impl core::fmt::Display for AndroidBootMode { fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 45 match *self { 46 AndroidBootMode::Normal => write!(f, "AndroidBootMode::Normal"), 47 AndroidBootMode::Recovery => write!(f, "AndroidBootMode::Recovery"), 48 AndroidBootMode::BootloaderBootOnce => write!(f, "AndroidBootMode::BootloaderBootOnce"), 49 } 50 } 51 } 52 53 /// BCB command field offset within BCB block. 54 pub const COMMAND_FIELD_OFFSET: usize = 0; 55 56 /// BCB command field size in bytes. 57 pub const COMMAND_FIELD_SIZE: usize = 32; 58 59 /// Android bootloader message structure that usually placed in the first block of misc partition 60 /// 61 /// Reference code: 62 /// https://cs.android.com/android/platform/superproject/main/+/95ec3cc1d879b92dd9db3bb4c4345c5fc812cdaa:bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h;l=67 63 #[repr(C, packed)] 64 #[derive(IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Copy, Clone, Debug)] 65 pub struct BootloaderMessage { 66 command: [u8; COMMAND_FIELD_SIZE], 67 status: [u8; 32], 68 recovery: [u8; 768], 69 stage: [u8; 32], 70 reserved: [u8; 1184], 71 } 72 73 impl BootloaderMessage { 74 /// BCB size in bytes 75 pub const SIZE_BYTES: usize = 2048; 76 77 /// Extract BootloaderMessage reference from bytes from_bytes_ref(buffer: &[u8]) -> Result<&BootloaderMessage>78 pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootloaderMessage> { 79 Ok(Ref::into_ref( 80 Ref::<_, BootloaderMessage>::new_from_prefix(buffer) 81 .ok_or(Error::BufferTooSmall(Some(core::mem::size_of::<BootloaderMessage>())))? 82 .0 83 .into(), 84 )) 85 } 86 87 /// Extract AndroidBootMode from BCB command field boot_mode(&self) -> Result<AndroidBootMode>88 pub fn boot_mode(&self) -> Result<AndroidBootMode> { 89 let command = CStr::from_bytes_until_nul(&self.command) 90 .map_err(|_| Error::Other(Some("Cannot read BCB command")))? 91 .to_str() 92 .map_err(|_| Error::InvalidInput)?; 93 94 match command { 95 "" => Ok(AndroidBootMode::Normal), 96 "boot-recovery" | "boot-fastboot" => Ok(AndroidBootMode::Recovery), 97 "bootonce-bootloader" => Ok(AndroidBootMode::BootloaderBootOnce), 98 _ => Err(Error::Other(Some("Wrong BCB command"))), 99 } 100 } 101 } 102 103 #[cfg(test)] 104 mod test { 105 use crate::AndroidBootMode; 106 use crate::BootloaderMessage; 107 use zerocopy::IntoBytes; 108 109 impl Default for BootloaderMessage { default() -> Self110 fn default() -> Self { 111 BootloaderMessage { 112 command: [0; 32], 113 status: [0; 32], 114 recovery: [0; 768], 115 stage: [0; 32], 116 reserved: [0; 1184], 117 } 118 } 119 } 120 121 #[test] test_bcb_empty_parsed_as_normal()122 fn test_bcb_empty_parsed_as_normal() { 123 let bcb = BootloaderMessage::default(); 124 125 assert_eq!( 126 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 127 AndroidBootMode::Normal 128 ); 129 } 130 131 #[test] test_bcb_with_wrong_command_failed()132 fn test_bcb_with_wrong_command_failed() { 133 let command = "boot-wrong"; 134 let mut bcb = BootloaderMessage::default(); 135 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 136 137 assert!(BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().is_err()); 138 } 139 140 #[test] test_bcb_to_recovery_parsed()141 fn test_bcb_to_recovery_parsed() { 142 let command = "boot-recovery"; 143 let mut bcb = BootloaderMessage::default(); 144 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 145 146 assert_eq!( 147 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 148 AndroidBootMode::Recovery 149 ); 150 } 151 152 #[test] test_bcb_to_fastboot_parsed_as_recovery()153 fn test_bcb_to_fastboot_parsed_as_recovery() { 154 let command = "boot-fastboot"; 155 let mut bcb = BootloaderMessage::default(); 156 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 157 158 assert_eq!( 159 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 160 AndroidBootMode::Recovery 161 ); 162 } 163 164 #[test] test_bcb_to_bootloader_once_parsed()165 fn test_bcb_to_bootloader_once_parsed() { 166 let command = "bootonce-bootloader"; 167 let mut bcb = BootloaderMessage::default(); 168 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 169 170 assert_eq!( 171 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 172 AndroidBootMode::BootloaderBootOnce 173 ); 174 } 175 } 176