blob: 9746f384e36ba1f52103f93c96083c59222c40f5 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::package::{deserialize_pkg_index, serialize_pkg_index, PackageIndexContents};
use anyhow::{Error, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use log::trace;
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom};
use thiserror::Error;
pub(crate) const BOOTFS_MAGIC: u32 = 0xa56d3ff9;
#[allow(dead_code)]
#[derive(Serialize, Deserialize)]
pub(crate) struct BootfsHeader {
magic: u32,
dir_size: u32,
reserved_0: u32,
reserved_1: u32,
}
impl BootfsHeader {
pub fn parse(cursor: &mut Cursor<Vec<u8>>) -> Result<Self> {
Ok(Self {
magic: cursor.read_u32::<LittleEndian>()?,
dir_size: cursor.read_u32::<LittleEndian>()?,
reserved_0: cursor.read_u32::<LittleEndian>()?,
reserved_1: cursor.read_u32::<LittleEndian>()?,
})
}
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize)]
struct BootfsDirectoryEntry {
name_len: u32,
data_len: u32,
data_offset: u32,
name: String,
}
impl BootfsDirectoryEntry {
pub fn parse(cursor: &mut Cursor<Vec<u8>>) -> Result<Self> {
let name_len = cursor.read_u32::<LittleEndian>()?;
let data_len = cursor.read_u32::<LittleEndian>()?;
let data_offset = cursor.read_u32::<LittleEndian>()?;
let mut name_buffer: Vec<u8> = Vec::with_capacity(name_len.try_into()?);
for _i in 0..name_len {
name_buffer.push(cursor.read_u8()?);
}
let name = std::str::from_utf8(&name_buffer)?;
Ok(Self { name_len, data_len, data_offset, name: name.to_string() })
}
}
#[derive(Error, Debug)]
pub enum BootfsError {
#[error("Bootfs magic doesn't match expected")]
BootfsMagicInvalid,
}
#[derive(Clone, Default, Deserialize, Serialize, PartialEq, Eq, Debug)]
pub struct BootfsPackageIndex {
// Must be optional to re-use otherwise identical serde logic.
#[serde(serialize_with = "serialize_pkg_index", deserialize_with = "deserialize_pkg_index")]
pub bootfs_pkgs: Option<PackageIndexContents>,
}
#[derive(Clone, Default, Deserialize, Serialize, PartialEq, Eq, Debug)]
pub struct BootfsFileIndex {
// File names to data contained in bootfs.
#[serde(
serialize_with = "serialize_bootfs_file_index",
deserialize_with = "deserialize_bootfs_file_index"
)]
pub bootfs_files: HashMap<String, Vec<u8>>,
}
/// Responsible for extracting the zbi from the package and reading the zbi
/// data from it.
#[allow(dead_code)]
pub struct BootfsReader {
cursor: Cursor<Vec<u8>>,
}
impl BootfsReader {
pub fn new(buffer: Vec<u8>) -> Self {
Self { cursor: Cursor::new(buffer) }
}
pub fn parse(&mut self) -> Result<HashMap<String, Vec<u8>>> {
let header = BootfsHeader::parse(&mut self.cursor)?;
if header.magic != BOOTFS_MAGIC {
return Err(Error::new(BootfsError::BootfsMagicInvalid {}));
}
let mut directory_entries = vec![];
// Read all the directory entries.
let end_position = self.cursor.position() + u64::try_from(header.dir_size)? - 1;
while self.cursor.position() < end_position {
let start = self.cursor.position();
directory_entries.push(BootfsDirectoryEntry::parse(&mut self.cursor)?);
let end = self.cursor.position();
let diff = end - start;
// Directory entries are variable in size but must end up 4 byte aligned.
if diff % 4 != 0 {
let padding: i64 = (4 - (diff % 4)).try_into().unwrap();
self.cursor.seek(SeekFrom::Current(padding))?;
}
}
// Parse all the directory entries and retrieve the files.
let mut files = HashMap::new();
for directory in directory_entries.iter() {
trace!(
name:% = directory.name,
offset:% = directory.data_offset,
len:% = directory.data_len;
"Extracting bootfs file",
);
self.cursor.set_position(directory.data_offset.into());
let mut file_data = vec![0; directory.data_len.try_into()?];
self.cursor.read_exact(&mut file_data)?;
let mut dir_name = directory.name.clone();
if let Some(stripped_path) = dir_name.strip_suffix("\u{0000}") {
dir_name = stripped_path.to_string();
}
files.insert(dir_name, file_data);
}
Ok(files)
}
}
/// Serialize a set of bootfs files.
/// We avoid the default serializer because we want to serialize it as a vector of file names,
/// and prevent serializing the content of all the files.
pub fn serialize_bootfs_file_index<S>(
files: &HashMap<String, Vec<u8>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let keys: Vec<&String> = files.keys().collect();
let mut seq = serializer.serialize_seq(Some(keys.len()))?;
for key in keys {
seq.serialize_element(key)?;
}
seq.end()
}
/// Deserialize a set of bootfs files.
/// We avoid the default serializer because we want to serialize it as a vector of file names,
/// and prevent serializing the content of all the files.
pub fn deserialize_bootfs_file_index<'de, D>(
deserializer: D,
) -> Result<HashMap<String, Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
struct VecVisitor;
impl<'de> Visitor<'de> for VecVisitor {
type Value = HashMap<String, Vec<u8>>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("bootfs file map")
}
fn visit_seq<A>(self, mut seq_access: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut map = HashMap::with_capacity(seq_access.size_hint().unwrap_or(0));
loop {
let element: Option<String> = seq_access.next_element()?;
let _ = match element {
None => break,
Some(name) => map.insert(name, vec![]),
};
}
Ok(map)
}
}
let visitor = VecVisitor;
deserializer.deserialize_seq(visitor)
}
/// Test helpers for pre-computed bootfs images.
pub mod test {
use super::*;
/// Returns raw bytes for a bootfs image that contains no file entries.
pub fn empty_bootfs_bytes() -> Vec<u8> {
let bootfs_header =
BootfsHeader { magic: BOOTFS_MAGIC, dir_size: 0, reserved_0: 0, reserved_1: 0 };
bincode::serialize(&bootfs_header).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::test::*;
use super::*;
#[test]
fn test_bootfs_empty() {
let bytes = empty_bootfs_bytes();
let mut reader = BootfsReader::new(bytes);
let files = reader.parse().unwrap();
assert_eq!(files.len(), 0);
}
#[test]
fn test_bootfs_with_file() {
// Get the meta bytes size, we don't serialize the raw structure here
// since it contains a String which isn't the true underlying packed
// data type. So we manually dump the data as if it was a packed struct.
let name_len: u32 = 3;
let data_len: u32 = 10;
let mut data_offset: u32 = 0;
let mut meta_bytes: Vec<u8> = vec![];
meta_bytes.extend(&name_len.to_le_bytes());
meta_bytes.extend(&data_len.to_le_bytes());
meta_bytes.extend(&data_offset.to_le_bytes());
meta_bytes.push(0x41 as u8);
meta_bytes.push(0x42 as u8);
meta_bytes.push(0x43 as u8);
// Construct the bootfs header using the total size of the file.
let bootfs_header = BootfsHeader {
magic: BOOTFS_MAGIC,
dir_size: u32::try_from(meta_bytes.len()).unwrap(),
reserved_0: 0,
reserved_1: 0,
};
let mut bytes = bincode::serialize(&bootfs_header).unwrap();
// Update the bootfs files to calculate its offset from the header + dir.
data_offset = u32::try_from(bytes.len() + meta_bytes.len()).unwrap();
meta_bytes.clear();
meta_bytes.extend(&name_len.to_le_bytes());
meta_bytes.extend(&data_len.to_le_bytes());
meta_bytes.extend(&data_offset.to_le_bytes());
meta_bytes.push(0x41 as u8);
meta_bytes.push(0x42 as u8);
meta_bytes.push(0x43 as u8);
bytes.extend(&meta_bytes);
let file_data = vec![1; data_len.try_into().unwrap()];
bytes.extend(&file_data);
let mut reader = BootfsReader::new(bytes);
let files = reader.parse().unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files["ABC"].len(), 10);
for i in 0..10 {
assert_eq!(files["ABC"][i], 1);
}
}
}