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