• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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