blob: 335b357270e2788c09d6484f7b6ba7f529d646ab [file] [log] [blame]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Module for constructing bootconfig. See the following for more details:
//!
//! https://source.android.com/docs/core/architecture/bootloader/implementing-bootconfig#bootloader-changes
use liberror::{Error, Result};
/// A class for constructing bootconfig section.
pub struct BootConfigBuilder<'a> {
current_size: usize,
buffer: &'a mut [u8],
}
const BOOTCONFIG_MAGIC: &str = "#BOOTCONFIG\n";
// Trailer structure:
// struct {
// config_size: u32,
// checksum: u32,
// bootconfig_magic: [u8]
// }
/// Size of the bootconfig trailer.
pub const BOOTCONFIG_TRAILER_SIZE: usize = 4 + 4 + BOOTCONFIG_MAGIC.len();
impl<'a> BootConfigBuilder<'a> {
/// Initialize with a given buffer.
pub fn new(buffer: &'a mut [u8]) -> Result<Self> {
if buffer.len() < BOOTCONFIG_TRAILER_SIZE {
return Err(Error::BufferTooSmall(Some(BOOTCONFIG_TRAILER_SIZE)));
}
let mut ret = Self { current_size: 0, buffer: buffer };
ret.update_trailer()?;
Ok(ret)
}
/// Get the remaining capacity for adding new bootconfig.
pub fn remaining_capacity(&self) -> usize {
self.buffer
.len()
.checked_sub(self.current_size)
.unwrap()
.checked_sub(BOOTCONFIG_TRAILER_SIZE)
.unwrap()
}
/// Get the whole config bytes including trailer.
pub fn config_bytes(&self) -> &[u8] {
// Arithmetic not expected to fail.
&self.buffer[..self.current_size.checked_add(BOOTCONFIG_TRAILER_SIZE).unwrap()]
}
/// Append a new config via a reader callback.
///
/// A `&mut [u8]` that covers the remaining space is passed to the callback for reading the
/// config bytes. It should return the total size read if operation is successful or
/// `Error::BufferTooSmall(Some(<minimum_buffer_size>))`. Attempting to return a size
/// greater than the input will cause it to panic. Empty read is allowed. It's up to the caller
/// to make sure the read content will eventually form a valid boot config. The API is for
/// situations where configs are read from sources such as disk and separate buffer allocation
/// is not possible or desired.
pub fn add_with<F>(&mut self, reader: F) -> Result<()>
where
F: FnOnce(&[u8], &mut [u8]) -> Result<usize>,
{
let remains = self.remaining_capacity();
let (current_buffer, remains_buffer) = self.buffer.split_at_mut(self.current_size);
let size = reader(&current_buffer[..], &mut remains_buffer[..remains])?;
assert!(size <= remains);
self.current_size += size;
// Content may have been modified. Re-compute trailer.
self.update_trailer()
}
/// Append a new config from string.
pub fn add(&mut self, config: &str) -> Result<()> {
if self.remaining_capacity() < config.len() {
return Err(Error::BufferTooSmall(Some(config.len())));
}
self.add_with(|_, out| {
out[..config.len()].clone_from_slice(config.as_bytes());
Ok(config.len())
})
}
/// Update the boot config trailer at the end of parameter list.
/// See specification at:
/// https://source.android.com/docs/core/architecture/bootloader/implementing-bootconfig#bootloader-changes
fn update_trailer(&mut self) -> Result<()> {
// Config size
let size: u32 = self.current_size.try_into().or(Err(Error::Other(None)))?;
// Check sum.
let checksum = self.checksum();
let trailer = &mut self.buffer[self.current_size..];
trailer[..4].clone_from_slice(&size.to_le_bytes());
trailer[4..8].clone_from_slice(&checksum.to_le_bytes());
trailer[8..][..BOOTCONFIG_MAGIC.len()].clone_from_slice(BOOTCONFIG_MAGIC.as_bytes());
Ok(())
}
/// Compute the checksum value.
fn checksum(&self) -> u32 {
self.buffer[..self.current_size]
.iter()
.map(|v| *v as u32)
.reduce(|acc, v| acc.overflowing_add(v).0)
.unwrap_or(0)
}
}
impl core::fmt::Display for BootConfigBuilder<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let bytes = self.config_bytes();
for val in &bytes[..bytes.len().checked_sub(BOOTCONFIG_TRAILER_SIZE).unwrap()] {
write!(f, "{}", core::ascii::escape_default(*val))?;
}
Ok(())
}
}
impl core::fmt::Write for BootConfigBuilder<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.add_with(|_, out| {
out.get_mut(..s.len())
.ok_or(Error::BufferTooSmall(Some(s.len())))?
.clone_from_slice(s.as_bytes());
Ok(s.len())
})
.map_err(|_| core::fmt::Error)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use core::fmt::Write;
// Taken from Cuttlefish on QEMU aarch64.
const TEST_CONFIG: &str = "androidboot.hardware=cutf_cvm
kernel.mac80211_hwsim.radios=0
kernel.vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384
androidboot.vendor.apex.com.google.emulated.camera.provider.hal=com.google.emulated.camera.provider.hal
androidboot.slot_suffix=_a
androidboot.force_normal_boot=1
androidboot.hw_timeout_multiplier=50
androidboot.fstab_suffix=cf.f2fs.hctr2
androidboot.hypervisor.protected_vm.supported=0
androidboot.modem_simulator_ports=9600
androidboot.vsock_lights_port=6900
androidboot.lcd_density=320
androidboot.vendor.audiocontrol.server.port=9410
androidboot.vendor.audiocontrol.server.cid=3
androidboot.cuttlefish_config_server_port=6800
androidboot.hardware.gralloc=minigbm
androidboot.vsock_lights_cid=3
androidboot.enable_confirmationui=0
androidboot.hypervisor.vm.supported=0
androidboot.setupwizard_mode=DISABLED
androidboot.serialno=CUTTLEFISHCVD011
androidboot.enable_bootanimation=1
androidboot.hardware.hwcomposer.display_finder_mode=drm
androidboot.hardware.angle_feature_overrides_enabled=preferLinearFilterForYUV:mapUnspecifiedColorSpaceToPassThrough
androidboot.hardware.egl=mesa
androidboot.boot_devices=4010000000.pcie
androidboot.opengles.version=196608
androidboot.wifi_mac_prefix=5554
androidboot.vsock_tombstone_port=6600
androidboot.hardware.hwcomposer=ranchu
androidboot.hardware.hwcomposer.mode=client
androidboot.console=ttyAMA0
androidboot.ddr_size=4915MB
androidboot.cpuvulkan.version=0
androidboot.serialconsole=1
androidboot.vbmeta.device=PARTUUID=2b7e273a-42a1-654b-bbad-8cb6ab2b6911
androidboot.vbmeta.avb_version=1.1
androidboot.vbmeta.device_state=unlocked
androidboot.vbmeta.hash_alg=sha256
androidboot.vbmeta.size=23040
androidboot.vbmeta.digest=6d6cdbad779475dd945ed79e6bd79c0574541d34ff488fa5aeeb024d739dd0d2
androidboot.vbmeta.invalidate_on_error=yes
androidboot.veritymode=enforcing
androidboot.verifiedbootstate=orange
";
const TEST_CONFIG_TRAILER: &[u8; BOOTCONFIG_TRAILER_SIZE] =
b"i\x07\x00\x00\xf9\xc4\x02\x00#BOOTCONFIG\n";
#[test]
fn test_add() {
let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
builder.add(TEST_CONFIG).unwrap();
assert_eq!(
builder.config_bytes().to_vec(),
[TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec()
);
}
#[test]
fn test_add_incremental() {
let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
for ele in TEST_CONFIG.strip_suffix('\n').unwrap().split('\n') {
let config = std::string::String::from(ele) + "\n";
builder.add(config.as_str()).unwrap();
}
assert_eq!(
builder.config_bytes().to_vec(),
[TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec()
);
}
#[test]
fn test_add_with_incremental() {
let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
let mut offset = 0;
for ele in TEST_CONFIG.strip_suffix('\n').unwrap().split('\n') {
let config = std::string::String::from(ele) + "\n";
builder
.add_with(|current, out| {
assert_eq!(current, &TEST_CONFIG.as_bytes()[..offset]);
out[..config.len()].copy_from_slice(config.as_bytes());
Ok(config.len())
})
.unwrap();
offset += config.len();
}
assert_eq!(
builder.config_bytes().to_vec(),
[TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec()
);
}
#[test]
fn test_add_incremental_via_fmt_write() {
let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
for ele in TEST_CONFIG.strip_suffix('\n').unwrap().split('\n') {
write!(builder, "{}\n", ele).unwrap();
}
assert_eq!(
builder.config_bytes().to_vec(),
[TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec()
);
}
#[test]
fn test_new_buffer_too_small() {
let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE - 1];
assert!(BootConfigBuilder::new(&mut buffer[..]).is_err());
}
#[test]
fn test_add_buffer_too_small() {
let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
assert!(builder.add("a\n").is_err());
}
#[test]
fn test_add_empty_string() {
let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
builder.add("").unwrap();
}
#[test]
fn test_add_with_error() {
let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1];
let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap();
assert!(builder.add_with(|_, _| Err(Error::Other(None))).is_err());
}
}