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::{AsBytes, FromBytes, FromZeroes, Ref}; 28 29 /// Libmisc BCB error type 30 #[derive(Debug)] 31 pub enum BcbError { 32 InvalidInput(&'static str), 33 } 34 35 /// Libmisc BCB result type 36 pub type Result<T> = core::result::Result<T, BcbError>; 37 38 /// Android boot modes type 39 /// Usually obtained from BCB block of misc partition 40 #[derive(PartialEq, Debug)] 41 pub enum AndroidBootMode { 42 Normal = 0, 43 Recovery, 44 BootloaderBootOnce, 45 } 46 47 impl core::fmt::Display for AndroidBootMode { fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 49 match *self { 50 AndroidBootMode::Normal => write!(f, "AndroidBootMode::Normal"), 51 AndroidBootMode::Recovery => write!(f, "AndroidBootMode::Recovery"), 52 AndroidBootMode::BootloaderBootOnce => write!(f, "AndroidBootMode::BootloaderBootOnce"), 53 } 54 } 55 } 56 57 /// Android bootloader message structure that usually placed in the first block of misc partition 58 /// 59 /// Reference code: 60 /// https://cs.android.com/android/platform/superproject/main/+/95ec3cc1d879b92dd9db3bb4c4345c5fc812cdaa:bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h;l=67 61 #[repr(C, packed)] 62 #[derive(AsBytes, FromBytes, FromZeroes, PartialEq, Copy, Clone, Debug)] 63 pub struct BootloaderMessage { 64 command: [u8; 32], 65 status: [u8; 32], 66 recovery: [u8; 768], 67 stage: [u8; 32], 68 reserved: [u8; 1184], 69 } 70 71 impl BootloaderMessage { 72 /// BCB size in bytes 73 pub const SIZE_BYTES: usize = 2048; 74 75 /// Extract BootloaderMessage reference from bytes from_bytes_ref(buffer: &[u8]) -> Result<&BootloaderMessage>76 pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootloaderMessage> { 77 Ok(Ref::<_, BootloaderMessage>::new_from_prefix(buffer) 78 .ok_or(BcbError::InvalidInput("Cannot read BCB message from buffer"))? 79 .0 80 .into_ref()) 81 } 82 83 /// Extract AndroidBootMode from BCB command field boot_mode(&self) -> Result<AndroidBootMode>84 pub fn boot_mode(&self) -> Result<AndroidBootMode> { 85 let command = CStr::from_bytes_until_nul(&self.command) 86 .map_err(|_| BcbError::InvalidInput("Cannot read BCB command"))? 87 .to_str() 88 .map_err(|_| BcbError::InvalidInput("Cannot convert BCB command to string"))?; 89 90 match command { 91 "" => Ok(AndroidBootMode::Normal), 92 "boot-recovery" | "boot-fastboot" => Ok(AndroidBootMode::Recovery), 93 "bootonce-bootloader" => Ok(AndroidBootMode::BootloaderBootOnce), 94 _ => Err(BcbError::InvalidInput("Wrong BCB command")), 95 } 96 } 97 } 98 99 #[cfg(test)] 100 mod test { 101 use crate::AndroidBootMode; 102 use crate::BootloaderMessage; 103 use zerocopy::AsBytes; 104 105 impl Default for BootloaderMessage { default() -> Self106 fn default() -> Self { 107 BootloaderMessage { 108 command: [0; 32], 109 status: [0; 32], 110 recovery: [0; 768], 111 stage: [0; 32], 112 reserved: [0; 1184], 113 } 114 } 115 } 116 117 #[test] test_bcb_empty_parsed_as_normal()118 fn test_bcb_empty_parsed_as_normal() { 119 let bcb = BootloaderMessage::default(); 120 121 assert_eq!( 122 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 123 AndroidBootMode::Normal 124 ); 125 } 126 127 #[test] test_bcb_with_wrong_command_failed()128 fn test_bcb_with_wrong_command_failed() { 129 let command = "boot-wrong"; 130 let mut bcb = BootloaderMessage::default(); 131 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 132 133 assert!(BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().is_err()); 134 } 135 136 #[test] test_bcb_to_recovery_parsed()137 fn test_bcb_to_recovery_parsed() { 138 let command = "boot-recovery"; 139 let mut bcb = BootloaderMessage::default(); 140 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 141 142 assert_eq!( 143 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 144 AndroidBootMode::Recovery 145 ); 146 } 147 148 #[test] test_bcb_to_fastboot_parsed_as_recovery()149 fn test_bcb_to_fastboot_parsed_as_recovery() { 150 let command = "boot-fastboot"; 151 let mut bcb = BootloaderMessage::default(); 152 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 153 154 assert_eq!( 155 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 156 AndroidBootMode::Recovery 157 ); 158 } 159 160 #[test] test_bcb_to_bootloader_once_parsed()161 fn test_bcb_to_bootloader_once_parsed() { 162 let command = "bootonce-bootloader"; 163 let mut bcb = BootloaderMessage::default(); 164 bcb.command[..command.len()].copy_from_slice(command.as_bytes()); 165 166 assert_eq!( 167 BootloaderMessage::from_bytes_ref(bcb.as_bytes()).unwrap().boot_mode().unwrap(), 168 AndroidBootMode::BootloaderBootOnce 169 ); 170 } 171 } 172