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