blob: 69e941f8735e766eca444a5ff017509b40a9f3a4 [file]
// Copyright 2024, 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.
#![cfg_attr(not(any(test, android_dylib)), no_std)]
//! ZBI Processing Library
//!
//! This library is meant to be a generic processing library for the ZBI format
//! defined in sdk/lib/zbi-format/include/lib/zbi-format/zbi.h.
//!
//! Mainly it provides [`ZbiContainer`] that can create ([`ZbiContainer::new`]) valid container in
//! the provided buffer. Or parses and checks ([`ZbiContainer::parse`]) existing container in the
//! buffer. In both cases it provides iterator to walk thorough the items in container.
//!
//! Note: in both cases provided buffer must be properly aligned to [`ZBI_ALIGNMENT_USIZE`].
//! Using [`align_buffer`] would do proper alignment for you.
//!
//! ```
//! use zbi::{ZbiContainer, ZbiFlags, ZbiType, align_buffer};
//!
//! let mut buffer = [0; 200];
//! let mut buffer = align_buffer(&mut buffer[..]).unwrap();
//! let mut container = ZbiContainer::new(buffer).unwrap();
//! container.create_entry(ZbiType::DebugData, 0, ZbiFlags::default(), 10).unwrap();
//! container.create_entry_with_payload(ZbiType::DebugData, 0, ZbiFlags::default(), &[]).unwrap();
//!
//! assert_eq!(container.iter().count(), 2);
//!
//! let mut it = container.iter();
//! assert_eq!(it.next().unwrap().header.length, 10);
//! assert_eq!(it.next().unwrap().header.length, 0);
//! assert_eq!(it.next(), None);
//! ```
mod zbi_format;
use bitflags::bitflags;
use core::fmt::{Debug, Display, Formatter};
use core::mem::{size_of, take};
use core::ops::DerefMut;
use zbi_format::*;
use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, Ref};
type ZbiResult<T> = Result<T, ZbiError>;
/// [`ZbiContainer`] requires buffer and each entry to be aligned to this amount of bytes.
/// [`align_buffer`] can be used to adjust buffer alignment to match this requirement.
// ZBI_ALIGNMENT is u32 and it is not productive to `try_into()` to usize all the time.
// Expectation is that value should always fit in `u32` and `usize`, which we test.
pub const ZBI_ALIGNMENT_USIZE: usize = ZBI_ALIGNMENT as usize;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
const ZBI_ARCH_KERNEL_TYPE: ZbiType = ZbiType::KernelArm64;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const ZBI_ARCH_KERNEL_TYPE: ZbiType = ZbiType::KernelX64;
#[cfg(any(target_arch = "riscv", target_arch = "riscv64"))]
const ZBI_ARCH_KERNEL_TYPE: ZbiType = ZbiType::KernelRiscv64;
/// Aligns provided slice to [`ZBI_ALIGNMENT_USIZE`] bytes.
///
/// # Returns
///
/// * `Ok(aligned_slice)` - on success, which can have `length == 0`
/// * [`ZbiError::TooBig`] - returned if there is not enough space to align the slice
pub fn align_buffer<B: ByteSlice>(buffer: B) -> ZbiResult<B> {
let tail_offset = get_align_buffer_offset(&buffer[..])?;
let (_, aligned_buffer) = buffer.split_at(tail_offset);
Ok(aligned_buffer)
}
/// ZbiItem is element representation in [`ZbiContainer`]
///
/// It contains of `header` and `payload`. Both contain references to actual location in buffer.
/// And `payload` goes right after `header` in the buffer.
///
/// Header must be [`ZBI_ALIGNMENT_USIZE`] aligned in the buffer.
/// The length field specifies the actual payload length and does not include the size of padding.
/// Since all headers in [`ZbiContainer`] are [`ZBI_ALIGNMENT_USIZE`] aligned payload may be followed by padding,
/// which is included in [`ZbiContainer`] length, but not in each [`ZbiItem`]
#[derive(Debug)]
pub struct ZbiItem<B: ByteSlice> {
/// ZBI header
pub header: Ref<B, ZbiHeader>,
/// Payload corresponding to ZBI header
pub payload: B,
}
impl<B: ByteSlice, C: ByteSlice> PartialEq<ZbiItem<C>> for ZbiItem<B> {
fn eq(&self, other: &ZbiItem<C>) -> bool {
self.header.as_bytes() == other.header.as_bytes()
&& self.payload.as_bytes() == other.payload.as_bytes()
}
}
impl<B: ByteSlice + PartialEq> ZbiItem<B> {
/// Attempts to parse provided buffer.
///
/// # Arguments
/// * `buffer` - buffer to parse (can be mutable if further changes to element is required)
///
/// # Returns
///
/// * `Ok((ZbiItem, tail))` - if parsing was successful function returns `ZbiItem` and tail of
/// buffer that wasn't used.
/// * `Err(ZbiError)` - if parsing fails Err is returned.
///
/// # Example
///
/// ```
/// use zbi::ZbiItem;
///
/// # const LEN: usize = 100;
/// # #[repr(align(8))]
/// # struct ZbiAligned([u8; LEN]);
/// # let buffer = ZbiAligned(core::array::from_fn::<_, LEN, _>(|_| 0u8));
/// # let buffer = &buffer.0[..];
/// let (zbi_item, tail) = ZbiItem::parse(buffer).unwrap();
/// println!("{}", zbi_item.header.type_);
/// println!("{}", tail.len());
/// assert_eq!(zbi_item.header.length, zbi_item.payload.len() as u32);
/// ```
pub fn parse(buffer: B) -> ZbiResult<(ZbiItem<B>, B)> {
is_zbi_aligned(&buffer)?;
let (hdr, payload) = Ref::<B, ZbiHeader>::new_from_prefix(buffer).ok_or(ZbiError::Error)?;
let item_payload_len =
usize::try_from(hdr.length).map_err(|_| ZbiError::PlatformBadLength)?;
if payload.len() < item_payload_len {
return Err(ZbiError::TooBig);
}
let (item_payload, tail) = payload.split_at(item_payload_len);
let item = ZbiItem { header: hdr, payload: item_payload };
Ok((item, tail))
}
/// Validates `ZbiItem` header values.
///
/// # Example
///
/// ```
/// use zbi::{ZbiItem, ZbiError};
///
/// # const LEN: usize = 100;
/// # #[repr(align(8))]
/// # struct ZbiAligned([u8; LEN]);
/// # let buffer = ZbiAligned(core::array::from_fn::<_, LEN, _>(|_| 0u8));
/// # let buffer = &buffer.0[..];
/// // E.g. if `header.magic = 0` this is invalid value.
/// let (zbi_item, tail) = ZbiItem::parse(buffer).unwrap();
/// assert_eq!(zbi_item.header.magic, 0);
/// assert_eq!(zbi_item.is_valid(), Err(ZbiError::BadMagic));
/// ```
pub fn is_valid(&self) -> ZbiResult<()> {
if self.header.magic != ZBI_ITEM_MAGIC {
Err(ZbiError::BadMagic)
} else if !self.header.get_flags().contains(ZbiFlags::VERSION) {
Err(ZbiError::BadVersion)
} else if !self.header.get_flags().contains(ZbiFlags::CRC32)
&& (self.header.crc32 != ZBI_ITEM_NO_CRC32)
{
Err(ZbiError::BadCrc)
} else {
Ok(())
}
}
}
impl<B: ByteSliceMut + PartialEq> ZbiItem<B> {
/// Create `ZbiItem` with provided information and payload length.
///
///
/// # Result
///
/// * `(ZbiItem, tail)` - returned on success. `ZbiItem` would have payload of requested
/// length. And tail would be remaining part of the `buffer` that wasn't
/// used.
/// * `ZbiError::BadAlignment` - if buffer wasn't aligned.
/// * `ZbiError::TooBig` - if buffer is not long enough to hold
/// [`ZbiHeader`] + `payload` of `payload_len`.
/// * `ZbiError::PlatformBadLength` - if `payload_len` value is bigger than `u32::MAX`
///
/// # Example
/// ```
/// use zbi::{ZbiItem, ZbiFlags, ZbiType};
///
/// # const LEN: usize = 100;
/// # #[repr(align(8))]
/// # struct ZbiAligned([u8; LEN]);
/// # let mut buffer = ZbiAligned(core::array::from_fn::<_, LEN, _>(|_| 0u8));
/// # let mut buffer = &mut buffer.0[..];
/// let (item, _tail) = ZbiItem::new(
/// &mut buffer[..],
/// ZbiType::KernelX64,
/// 0,
/// ZbiFlags::default(),
/// 2,
/// ).unwrap();
/// assert_eq!(item.header.length, 2);
/// assert_eq!(item.payload.len(), 2);
/// ```
pub fn new(
buffer: B,
type_: ZbiType,
extra: u32,
flags: ZbiFlags,
payload_len: usize,
) -> ZbiResult<(ZbiItem<B>, B)> {
if buffer.len() < core::mem::size_of::<ZbiHeader>()
|| buffer.len() - core::mem::size_of::<ZbiHeader>() < payload_len
{
return Err(ZbiError::TooBig);
}
is_zbi_aligned(&buffer)?;
// Need to convert payload_len to u32 type to put in structure
let payload_len_u32 =
u32::try_from(payload_len).map_err(|_| ZbiError::PlatformBadLength)?;
let (mut header, item_tail) =
Ref::<B, ZbiHeader>::new_from_prefix(buffer).ok_or(ZbiError::Error)?;
header.type_ = type_ as u32;
header.length = payload_len_u32;
header.extra = extra;
header.set_flags(&flags);
header.reserved0 = 0;
header.reserved1 = 0;
header.magic = ZBI_ITEM_MAGIC;
header.crc32 = ZBI_ITEM_NO_CRC32;
// It is safe to do split because we checked if input buffer big enough to contain header
// and requested payload size.
let (payload, tail) = item_tail.split_at(payload_len);
Ok((ZbiItem { header, payload }, tail))
}
}
/// Main structure to work with ZBI format.
///
/// It allows to create valid buffer as well as parse existing one.
/// Both cases would allow to iterate over elements in the container via [`ZbiContainer::iter`] or
/// [`ZbiContainer::iter_mut`].
#[derive(Debug, PartialEq)]
pub struct ZbiContainer<B: ByteSlice> {
/// Container specific [`ZbiHeader`], witch would be first element if ZBI buffer.
///
/// `header.length` would show how many bytes after this header is used for ZBI elements and
/// padding.
///
/// `header.type_` is always [`ZbiType::Container`]
pub header: Ref<B, ZbiHeader>,
// Same as header.header.length, but for convenience is `usize` to avoid use of try_into and
// returning ZbiError if we need to use it.
// Use getters and setters to access length:
// - set_payload_length_usize()
// - get_payload_length_u32()
// - get_payload_length_usize()
payload_length: usize,
// Buffer that follows `header`. It contains ZbiItems + padding if any and remaining tail for
// possible growth.
buffer: B,
}
impl<B: ByteSlice> ZbiContainer<B> {
// Helper to construct [`ZbiContainer`] which handles `paload_length` value, which should be
// in sync with `header.length`.
fn construct(header: Ref<B, ZbiHeader>, buffer: B) -> ZbiResult<Self> {
Ok(Self {
payload_length: usize::try_from(header.length)
.map_err(|_| ZbiError::PlatformBadLength)?,
header,
buffer,
})
}
/// Returns current container length as `u32`. Length doesn't include container header, only
/// items and padding.
pub fn get_payload_length_u32(&self) -> u32 {
self.header.length
}
/// Returns current container length as `usize`. Length doesn't include container header, only
/// items and padding.
pub fn get_payload_length_usize(&self) -> usize {
self.payload_length
}
/// Returns the total size including the ZBI container header, payload length after padding.
pub fn container_size(&self) -> usize {
self.get_payload_length_usize() + size_of::<ZbiHeader>()
}
/// Immutable iterator over ZBI elements. First element is first ZBI element after
/// container header. Container header is not available via iterator.
pub fn iter(&self) -> ZbiContainerIterator<impl ByteSlice + Default + Debug + PartialEq + '_> {
ZbiContainerIterator {
state: Ok(()),
buffer: &self.buffer[..self.get_payload_length_usize()],
}
}
/// Validates if ZBI is bootable for the target platform.
///
/// # Returns
///
/// * `Ok(item)` - if bootable, where `item` is the ZBI kernel item.
/// * Err([`ZbiError::IncompleteKernel`]) - if first element in container has type not bootable
/// on target platform.
/// * Err([`ZbiError::Truncated`]) - if container is empty
pub fn is_bootable(
&self,
) -> ZbiResult<ZbiItem<impl ByteSlice + Default + Debug + PartialEq + '_>> {
let hdr = &self.header;
if hdr.length == 0 {
return Err(ZbiError::Truncated);
}
match self.iter().next() {
Some(v) if v.header.type_ == ZBI_ARCH_KERNEL_TYPE as u32 => Ok(v),
Some(_) => Err(ZbiError::IncompleteKernel),
None => Err(ZbiError::Truncated),
}
}
/// Returns the ZBI kernel `entry` and `reserved_memory_size` field value if the container is a
/// bootable ZBI kernel.
///
/// # Returns
///
/// * Returns `Ok((entry, reserved_memory_size))` on success.
/// * Returns `Err` if container is not a bootable ZBI kernel or is truncated.
pub fn get_kernel_entry_and_reserved_memory_size(&self) -> ZbiResult<(u64, u64)> {
let kernel = self.is_bootable()?;
let vals = Ref::<_, [u64]>::new_slice_from_prefix(kernel.payload, 2)
.ok_or(ZbiError::IncompleteKernel)?
.0
.into_slice();
Ok((vals[0], vals[1]))
}
/// Computes the required buffer size needed for relocating this ZBI kernel.
///
/// # Returns
///
/// * Returns `Ok(size)` on success.
/// * Returns `Err` if container is not a valid bootable ZBI kernel.
pub fn get_buffer_size_for_kernel_relocation(&self) -> ZbiResult<usize> {
let kernel = self.is_bootable()?;
let (_, reserve_memory_size) = self.get_kernel_entry_and_reserved_memory_size()?;
let kernel_size = 2 * size_of::<ZbiHeader>() + kernel.payload.as_bytes().len();
let reserve_memory_size =
usize::try_from(reserve_memory_size).map_err(|_| ZbiError::LengthOverflow)?;
kernel_size.checked_add(reserve_memory_size).ok_or(ZbiError::LengthOverflow)
}
/// Creates `ZbiContainer` from provided buffer.
///
/// Buffer must be aligned to [`ZBI_ALIGNMENT_USIZE`] ([`align_buffer`] could be
/// used for that). If buffer is mutable than container can be mutable.
///
/// # Returns
///
/// * `Ok(ZbiContainer)` - if buffer is aligned and contain valid buffer.
/// * Err([`ZbiError`]) - if error occurred.
pub fn parse(buffer: B) -> ZbiResult<Self> {
is_zbi_aligned(&buffer)?;
let (header, payload) =
Ref::<B, ZbiHeader>::new_from_prefix(buffer).ok_or(ZbiError::Error)?;
let length: usize = header.length.try_into().map_err(|_| ZbiError::TooBig)?;
if length > payload.len() {
return Err(ZbiError::Truncated);
}
if header.type_ != ZbiType::Container as u32 {
return Err(ZbiError::BadType);
} else if header.extra != ZBI_CONTAINER_MAGIC || header.magic != ZBI_ITEM_MAGIC {
return Err(ZbiError::BadMagic);
} else if !header.get_flags().contains(ZbiFlags::VERSION) {
return Err(ZbiError::BadVersion);
} else if !header.get_flags().contains(ZbiFlags::CRC32) && header.crc32 != ZBI_ITEM_NO_CRC32
{
return Err(ZbiError::BadCrc);
}
let res = Self::construct(header, payload)?;
// Compiler thinks it is still borrowed when we reach Ok(res), so adding scope for it
{
let mut it = res.iter();
for b in &mut it {
b.is_valid()?;
}
// Check if there were item parsing errors
it.state?;
}
Ok(res)
}
}
impl<B: ByteSliceMut + PartialEq> ZbiContainer<B> {
fn set_payload_length_usize(&mut self, len: usize) -> ZbiResult<()> {
if self.buffer.len() < len {
return Err(ZbiError::Truncated);
}
self.header.length = u32::try_from(len).map_err(|_| ZbiError::PlatformBadLength)?;
self.payload_length = len;
Ok(())
}
/// Creates new empty `ZbiContainer` using provided buffer.
///
/// # Returns
///
/// * `Ok(ZbiContainer)` - on success
/// * Err([`ZbiError`]) - on error
pub fn new(buffer: B) -> ZbiResult<Self> {
let (item, buffer) =
ZbiItem::new(buffer, ZbiType::Container, ZBI_CONTAINER_MAGIC, ZbiFlags::default(), 0)?;
Self::construct(item.header, buffer)
}
fn align_tail(&mut self) -> ZbiResult<()> {
let length = self.get_payload_length_usize();
let align_offset = get_align_buffer_offset(&self.buffer[length..])?;
let new_length = length + align_offset;
self.set_payload_length_usize(new_length)?;
Ok(())
}
/// Get payload slice for the next ZBI entry Next.
///
/// Next entry should be added using [`ZbiContainer::create_entry`].
///
/// This is useful when it's non-trivial to determine the length of a payload ahead of time -
/// for example, loading a variable-length string from persistent storage.
///
/// Rather than loading the payload into a temporary buffer, determining the length, then
/// copying it into the ZBI, this function allows loading data directly into the ZBI. Since this
/// buffer is currently unused area, loading data here does not affect the ZBI until
/// zbi_create_entry() is called.
///
/// # Example
///
/// ```
/// # use zbi::{ZbiContainer, ZbiFlags, ZbiType, align_buffer};
/// #
/// # let mut buffer = [0; 100];
/// # let mut buffer = align_buffer(&mut buffer[..]).unwrap();
/// # let mut container = ZbiContainer::new(buffer).unwrap();
/// #
/// # let payload_to_use = [1, 2, 3, 4];
/// let next_payload = container.get_next_payload().unwrap();
/// next_payload[..payload_to_use.len()].copy_from_slice(&payload_to_use[..]);
///
/// container
/// .create_entry(ZbiType::KernelX64, 0, ZbiFlags::default(), payload_to_use.len())
/// .unwrap();
///
/// assert_eq!(container.iter().count(), 1);
/// assert_eq!(&*container.iter().next().unwrap().payload, &payload_to_use[..]);
/// ```
///
/// # Returns:
/// `Ok(&mut [u8])` - on success; slice of buffer where next entries payload would be located.
/// Err([`ZbiError::TooBig`]) - if buffer is not big enough for new element without payload.
pub fn get_next_payload(&mut self) -> ZbiResult<&mut [u8]> {
let length = self.get_payload_length_usize();
let align_payload_offset = length
.checked_add(size_of::<ZbiHeader>())
.ok_or(ZbiError::LengthOverflow)?
.checked_add(get_align_buffer_offset(&self.buffer[length..])?)
.ok_or(ZbiError::LengthOverflow)?;
if self.buffer.len() < align_payload_offset {
return Err(ZbiError::TooBig);
}
Ok(&mut self.buffer[align_payload_offset..])
}
/// Creates a new ZBI entry with the provided payload.
///
/// The new entry is aligned to [`ZBI_ALIGNMENT_USIZE`]. The capacity of the base ZBI must
/// be large enough to fit the new entry.
///
/// The [`ZbiFlags::VERSION`] is unconditionally set for the new entry.
///
/// The [`ZbiFlags::CRC32`] flag yields an error because CRC computation is not yet
/// supported.
///
/// # Arguments
/// * `type_` - The new entry's type
/// * `extra` - The new entry's type-specific data
/// * `flags` - The new entry's flags
/// * `payload` - The payload, copied into the new entry
///
/// # Returns:
/// * Ok(()) - on success
/// * Err([`ZbiError::TooBig`]) - if buffer is not big enough for new element with payload
/// * Err([`ZbiError::Crc32NotSupported`]) - if unsupported [`ZbiFlags::CRC32`] is set
/// * Err([`ZbiError`]) - if other errors occurred
///
/// # Example
/// ```
/// # use zbi::{ZbiContainer, ZbiFlags, ZbiType, align_buffer};
/// #
/// # let mut buffer = [0; 100];
/// # let mut buffer = align_buffer(&mut buffer[..]).unwrap();
/// # let mut container = ZbiContainer::new(buffer).unwrap();
/// #
/// container
/// .create_entry_with_payload(ZbiType::KernelX64, 0, ZbiFlags::default(), &[1, 2, 3, 4])
/// .unwrap();
/// assert_eq!(container.iter().count(), 1);
/// assert_eq!(&*container.iter().next().unwrap().payload, &[1, 2, 3, 4]);
/// ```
pub fn create_entry_with_payload(
&mut self,
type_: ZbiType,
extra: u32,
flags: ZbiFlags,
payload: &[u8],
) -> ZbiResult<()> {
self.get_next_payload()?[..payload.len()].copy_from_slice(payload);
self.create_entry(type_, extra, flags, payload.len())
}
/// Creates a new ZBI entry and returns a pointer to the payload.
///
/// The new entry is aligned to [`ZBI_ALIGNMENT_USIZE`]. The capacity of the base ZBI must
/// be large enough to fit the new entry.
///
/// The [`ZbiFlags::VERSION`] is unconditionally set for the new entry.
///
/// The [`ZbiFlags::CRC32`] flag yields an error because CRC computation is not yet
/// supported.
///
/// # Arguments
/// * `type_` - The new entry's type.
/// * `extra` - The new entry's type-specific data.
/// * `flags` - The new entry's flags.
/// * `payload_length` - The length of the new entry's payload.
///
/// # Returns
/// * Ok(()) - On success.
/// * Err([`ZbiError::TooBig`]) - if buffer is not big enough for new element with payload
/// * Err([`ZbiError::Crc32NotSupported`]) - if unsupported [`ZbiFlags::CRC32`] is set
/// * Err([`ZbiError`]) - if other errors occurred
///
/// # Example
/// ```
/// # use zbi::{ZbiContainer, ZbiFlags, ZbiType, align_buffer};
/// #
/// # let mut buffer = [0; 100];
/// # let mut buffer = align_buffer(&mut buffer[..]).unwrap();
/// # let mut container = ZbiContainer::new(buffer).unwrap();
/// #
/// # let payload_to_use = [1, 2, 3, 4];
/// let next_payload = container.get_next_payload().unwrap();
/// next_payload[..payload_to_use.len()].copy_from_slice(&payload_to_use[..]);
///
/// container
/// .create_entry(ZbiType::KernelX64, 0, ZbiFlags::default(), payload_to_use.len())
/// .unwrap();
///
/// assert_eq!(container.iter().count(), 1);
/// assert_eq!(&*container.iter().next().unwrap().payload, &payload_to_use[..]);
/// ```
pub fn create_entry(
&mut self,
type_: ZbiType,
extra: u32,
flags: ZbiFlags,
payload_length: usize,
) -> ZbiResult<()> {
// We don't support CRC computation (yet?)
if flags.contains(ZbiFlags::CRC32) {
return Err(ZbiError::Crc32NotSupported);
}
let length = self.get_payload_length_usize();
let (item, _) =
ZbiItem::new(&mut self.buffer[length..], type_, extra, flags, payload_length)?;
let used = length
.checked_add(core::mem::size_of::<ZbiHeader>())
.ok_or(ZbiError::LengthOverflow)?
.checked_add(item.payload.len())
.ok_or(ZbiError::LengthOverflow)?;
self.set_payload_length_usize(used)?;
self.align_tail()?;
Ok(())
}
/// Extends a ZBI container with another container's payload.
///
/// # Arguments
/// * `other` - The container to copy the payload from.
///
/// # Returns
/// * `Ok(())` - On success.
/// * Err([`ZbiError::TooBig`]) - If container is too small.
///
/// # Example
/// ```
/// # use zbi::{ZbiContainer, ZbiType, ZbiFlags, align_buffer};
/// #
/// # let mut buffer = [0; 200];
/// # let mut buffer = align_buffer(&mut buffer[..]).unwrap();
/// let mut container_0 = ZbiContainer::new(&mut buffer[..]).unwrap();
/// container_0
/// .create_entry_with_payload(ZbiType::DebugData, 0, ZbiFlags::default(), &[0, 1])
/// .unwrap();
///
/// # let mut buffer = [0; 200];
/// # let mut buffer = align_buffer(&mut buffer[..]).unwrap();
/// let mut container_1 = ZbiContainer::new(&mut buffer[..]).unwrap();
/// container_1
/// .create_entry_with_payload(ZbiType::KernelX64, 0, ZbiFlags::default(), &[0, 1, 3, 4])
/// .unwrap();
///
/// container_0.extend(&container_1).unwrap();
///
/// assert_eq!(container_0.iter().count(), 2);
/// # let cont0_element_1 = &container_0
/// # .iter()
/// # .enumerate()
/// # .filter_map(|(i, e)| if i == 1 { Some(e) } else { None })
/// # .collect::<Vec<_>>()[0];
/// # let cont1_element_0 = &container_1.iter().next().unwrap();
/// # assert_eq!(cont0_element_1, cont1_element_0);
/// ```
pub fn extend(&mut self, other: &ZbiContainer<impl ByteSlice + PartialEq>) -> ZbiResult<()> {
let new_length = self
.get_payload_length_usize()
.checked_add(other.get_payload_length_usize())
.ok_or(ZbiError::LengthOverflow)?;
if self.buffer.len() < new_length {
return Err(ZbiError::TooBig);
}
for b in other.iter() {
let start = self.get_payload_length_usize();
let end = start + core::mem::size_of::<ZbiHeader>();
self.buffer[start..end].clone_from_slice(b.header.bytes());
let start = end;
let end = start + b.payload.len();
self.buffer[start..end].clone_from_slice(&b.payload);
self.set_payload_length_usize(end)?;
self.align_tail()?;
}
Ok(())
}
/// Extends with another ZBI container stored on a potentially unaligned buffer.
///
/// The method copies `other` into the unused space first before checking validity and
/// extending. Thus if `other.len()` is greater than the remaining space in the container, it
/// it will be rejected, regardless of the actual container size.
pub fn extend_unaligned(&mut self, other: &[u8]) -> ZbiResult<()> {
let sz = self.get_payload_length_usize();
let remains = &mut self.buffer[sz..];
// Copies `other` to `dst` which is guaranteed aligned.
let dst = remains.get_mut(..other.len()).ok_or(ZbiError::TooBig)?;
dst.clone_from_slice(other);
// Checks the incoming container and extracts payload length (without padding).
let new_payload_len = ZbiContainer::parse(&mut dst[..])?.header.length;
// Shifts forward the payload to remove the ZBI header. This effectively appends the
// payload.
dst.copy_within(size_of::<ZbiHeader>().., 0);
self.set_payload_length_usize(
sz + usize::try_from(new_payload_len).map_err(|_| ZbiError::LengthOverflow)?,
)?;
self.align_tail()
}
}
impl<B: ByteSlice + PartialEq + DerefMut> ZbiContainer<B> {
/// Mutable iterator over ZBI elements. First element is first ZBI element after
/// container header. Container header is not available via iterator.
pub fn iter_mut(
&mut self,
) -> ZbiContainerIterator<impl ByteSliceMut + Debug + Default + PartialEq + '_> {
let length = self.get_payload_length_usize();
ZbiContainerIterator { state: Ok(()), buffer: &mut self.buffer[..length] }
}
}
/// Container iterator
// State is required to check elements are valid during parsing.
// During this parsing can fail and we need to check if iterator returned `None` because there are
// no more elements left or because there was an error.
// If container object already exist state should never contain error, since container was already
// verified.
pub struct ZbiContainerIterator<B> {
state: ZbiResult<()>,
buffer: B,
}
impl<B: ByteSlice + PartialEq + Default + Debug> Iterator for ZbiContainerIterator<B> {
type Item = ZbiItem<B>;
fn next(&mut self) -> Option<Self::Item> {
// Align buffer before parsing
match align_buffer(take(&mut self.buffer)) {
Ok(v) => self.buffer = v,
Err(_) => {
self.state = Err(ZbiError::Truncated);
return None;
}
};
if self.buffer.is_empty() {
return None;
}
match ZbiItem::<B>::parse(take(&mut self.buffer)) {
Ok((item, mut tail)) => {
// Remove item that was just parsed from the buffer for next
// iteration before returning it.
core::mem::swap(&mut tail, &mut self.buffer);
Some(item)
}
Err(e) => {
// If there was an error during item parsing,
// make sure to set state to error, before signalling end of iteration.
self.state = Err(e);
None
}
}
}
}
#[repr(u32)]
#[derive(AsBytes, Clone, Copy, Debug, Eq, PartialEq)]
/// All possible [`ZbiHeader`]`.type` values.
pub enum ZbiType {
/// Each ZBI starts with a container header.
/// * `length`: Total size of the image after this header. This includes all item headers,
/// payloads, and padding. It does not include the container header itself.
/// Must be a multiple of [`ZBI_ALIGNMENT_USIZE`].
/// * `extra`: Must be `ZBI_CONTAINER_MAGIC`.
/// * `flags`: Must be [`ZbiFlags::VERSION`] and no other flags.
Container = ZBI_TYPE_CONTAINER,
/// x86-64 kernel. See [`ZbiKernel`] for a payload description.
//
// 'KRNL'
KernelX64 = ZBI_TYPE_KERNEL_X64,
/// ARM64 kernel. See [`ZbiKernel`] for a payload description.
//
// KRN8
KernelArm64 = ZBI_TYPE_KERNEL_ARM64,
/// RISC-V kernel. See [`ZbiKernel`] for a payload description.
//
// 'KRNV'
KernelRiscv64 = ZBI_TYPE_KERNEL_RISCV64,
/// A discarded item that should just be ignored. This is used for an
/// item that was already processed and should be ignored by whatever
/// stage is now looking at the ZBI. An earlier stage already "consumed"
/// this information, but avoided copying data around to remove it from
/// the ZBI item stream.
//
// 'SKIP'
Discard = ZBI_TYPE_DISCARD,
/// A virtual disk image. This is meant to be treated as if it were a
/// storage device. The payload (after decompression) is the contents of
/// the storage device, in whatever format that might be.
//
// 'RDSK'
StorageRamdisk = ZBI_TYPE_STORAGE_RAMDISK,
/// The /boot filesystem in BOOTFS format, specified in
/// <lib/zbi-format/internal/bootfs.h>. This represents an internal
/// contract between Zircon userboot (//docs/userboot.md), which handles
/// the contents of this filesystem, and platform tooling, which prepares
/// them.
//
// 'BFSB'
StorageBootFs = ZBI_TYPE_STORAGE_BOOTFS,
/// Storage used by the kernel (such as a compressed image containing the
/// actual kernel). The meaning and format of the data is specific to the
/// kernel, though it always uses the standard (private) storage
/// compression protocol. Each particular `ZbiType::Kernel{ARCH}` item image and its
/// `StorageKernel` item image are intimately tied and one cannot work
/// without the exact correct corresponding other.
//
// 'KSTR'
StorageKernel = ZBI_TYPE_STORAGE_KERNEL,
/// Device-specific factory data, stored in BOOTFS format.
//
// TODO(fxbug.dev/42109921): This should not use the "STORAGE" infix.
//
// 'BFSF'
StorageBootFsFactory = ZBI_TYPE_STORAGE_BOOTFS_FACTORY,
/// A kernel command line fragment, a UTF-8 string that need not be
/// NULL-terminated. The kernel's own option parsing accepts only printable
/// 'ASCI'I and treats all other characters as equivalent to whitespace. Multiple
/// `ZbiType::CmdLine` items can appear. They are treated as if concatenated with
/// ' ' between each item, in the order they appear: first items in the bootable
/// ZBI containing the kernel; then items in the ZBI synthesized by the boot
/// loader. The kernel interprets the [whole command line](../../../../docs/kernel_cmdline.md).
//
// 'CMDL'
CmdLine = ZBI_TYPE_CMDLINE,
/// The crash log from the previous boot, a UTF-8 string.
//
// 'BOOM'
CrashLog = ZBI_TYPE_CRASHLOG,
/// Physical memory region that will persist across warm boots. See `zbi_nvram_t`
/// for payload description.
//
// 'NVLL'
Nvram = ZBI_TYPE_NVRAM,
/// Platform ID Information.
//
// 'PLID'
PlatformId = ZBI_TYPE_PLATFORM_ID,
/// Board-specific information.
//
// mBSI
DrvBoardInfo = ZBI_TYPE_DRV_BOARD_INFO,
/// CPU configuration. See `zbi_topology_node_t` for a description of the payload.
CpuTopology = ZBI_TYPE_CPU_TOPOLOGY,
/// Device memory configuration. See `zbi_mem_range_t` for a description of the payload.
//
// 'MEMC'
MemConfig = ZBI_TYPE_MEM_CONFIG,
/// Kernel driver configuration. The `ZbiHeader.extra` field gives a
/// ZBI_KERNEL_DRIVER_* type that determines the payload format.
/// See <lib/zbi-format/driver-config.h> for details.
//
// 'KDRV'
KernelDriver = ZBI_TYPE_KERNEL_DRIVER,
/// 'ACPI' Root Table Pointer, a `u64` physical address.
//
// 'RSDP'
AcpiRsdp = ZBI_TYPE_ACPI_RSDP,
/// 'SMBI'OS entry point, a [u64] physical address.
//
// 'SMBI'
Smbios = ZBI_TYPE_SMBIOS,
/// EFI system table, a [u64] physical address.
//
// 'EFIS'
EfiSystemTable = ZBI_TYPE_EFI_SYSTEM_TABLE,
/// EFI memory attributes table. An example of this format can be found in UEFI 2.10
/// section 4.6.4, but the consumer of this item is responsible for interpreting whatever
/// the bootloader supplies (in particular the "version" field may differ as the format
/// evolves).
//
// 'EMAT'
EfiMemoryAttributesTable = ZBI_TYPE_EFI_MEMORY_ATTRIBUTES_TABLE,
/// Framebuffer parameters, a `zbi_swfb_t` entry.
//
// 'SWFB'
FrameBuffer = ZBI_TYPE_FRAMEBUFFER,
/// The image arguments, data is a trivial text format of one "key=value" per line
/// with leading whitespace stripped and "#" comment lines and blank lines ignored.
/// It is processed by bootsvc and parsed args are shared to others via Arguments service.
/// TODO: the format can be streamlined after the /config/additional_boot_args compat support is
/// removed.
//
// 'IARG'
ImageArgs = ZBI_TYPE_IMAGE_ARGS,
/// A copy of the boot version stored within the sysconfig partition
//
// 'BVRS'
BootVersion = ZBI_TYPE_BOOT_VERSION,
/// MAC address for Ethernet, Wifi, Bluetooth, etc. `ZbiHeader.extra`
/// is a board-specific index to specify which device the MAC address
/// applies to. `ZbiHeader.length` gives the size in bytes, which
/// varies depending on the type of address appropriate for the device.
//
// mMAC
DrvMacAddress = ZBI_TYPE_DRV_MAC_ADDRESS,
/// A partition map for a storage device, a `zbi_partition_map_t` header
/// followed by one or more `zbi_partition_t` entries. `ZbiHeader.extra`
/// is a board-specific index to specify which device this applies to.
//
// mPRT
DrvPartitionMap = ZBI_TYPE_DRV_PARTITION_MAP,
/// Private information for the board driver.
//
// mBOR
DrvBoardPrivate = ZBI_TYPE_DRV_BOARD_PRIVATE,
/// Information about reboot
// 'HWRB'
HwRebootReason = ZBI_TYPE_HW_REBOOT_REASON,
/// The serial number, an unterminated ASCII string of printable non-whitespace
/// characters with length `ZbiHeader.length`.
//
// 'SRLN'
SerialNumber = ZBI_TYPE_SERIAL_NUMBER,
/// This type specifies a binary file passed in by the bootloader.
/// The first byte specifies the length of the filename without a NUL terminator.
/// The filename starts on the second byte.
/// The file contents are located immediately after the filename.
/// ```none
/// Layout: | name_len | name | payload
/// ^(1 byte) ^(name_len bytes) ^(length of file)
/// ```
//
// 'BTFL'
BootloaderFile = ZBI_TYPE_BOOTLOADER_FILE,
/// The devicetree blob from the legacy boot loader, if any. This is used only
/// for diagnostic and development purposes. Zircon kernel and driver
/// configuration is entirely driven by specific ZBI items from the boot
/// loader. The boot shims for legacy boot loaders pass the raw devicetree
/// along for development purposes, but extract information from it to populate
/// specific ZBI items such as [`ZbiType::KernelDriver`] et al.
DeviceTree = ZBI_TYPE_DEVICETREE,
/// An arbitrary number of random bytes attested to have high entropy. Any
/// number of items of any size can be provided, but no data should be provided
/// that is not true entropy of cryptographic quality. This is used to seed
/// secure cryptographic pseudo-random number generators.
//
// 'RAND'
SecureEntropy = ZBI_TYPE_SECURE_ENTROPY,
/// This provides a data dump and associated logging from a boot loader,
/// shim, or earlier incarnation that wants its data percolated up by the
/// booting Zircon kernel. See `zbi_debugdata_t` for a description of the
/// payload.
//
// 'DBGD'
DebugData = ZBI_TYPE_DEBUGDATA,
}
impl ZbiType {
/// Checks if [`ZbiType`] is a Kernel type. (E.g. [`ZbiType::KernelX64`])
/// ```
/// # use zbi::ZbiType;
/// assert!(ZbiType::KernelX64.is_kernel());
/// ```
pub fn is_kernel(&self) -> bool {
((*self as u32) & ZBI_TYPE_KERNEL_MASK) == ZBI_TYPE_KERNEL_PREFIX
}
/// Checks if [`ZbiType`] is a Driver Metadata type. (E.g. [`ZbiType::DrvBoardInfo`])
/// ```
/// # use zbi::ZbiType;
/// assert!(ZbiType::DrvBoardInfo.is_driver_metadata());
/// ```
pub fn is_driver_metadata(&self) -> bool {
((*self as u32) & ZBI_TYPE_DRIVER_METADATA_MASK) == ZBI_TYPE_DRIVER_METADATA_PREFIX
}
}
impl From<ZbiType> for u32 {
fn from(val: ZbiType) -> Self {
val as u32
}
}
impl TryFrom<u32> for ZbiType {
type Error = ZbiError;
fn try_from(val: u32) -> Result<Self, Self::Error> {
match val {
ZBI_TYPE_KERNEL_X64 => Ok(Self::KernelX64),
ZBI_TYPE_KERNEL_ARM64 => Ok(Self::KernelArm64),
ZBI_TYPE_KERNEL_RISCV64 => Ok(Self::KernelRiscv64),
ZBI_TYPE_CONTAINER => Ok(Self::Container),
ZBI_TYPE_DISCARD => Ok(Self::Discard),
ZBI_TYPE_STORAGE_RAMDISK => Ok(Self::StorageRamdisk),
ZBI_TYPE_STORAGE_BOOTFS => Ok(Self::StorageBootFs),
ZBI_TYPE_STORAGE_KERNEL => Ok(Self::StorageKernel),
ZBI_TYPE_STORAGE_BOOTFS_FACTORY => Ok(Self::StorageBootFsFactory),
ZBI_TYPE_CMDLINE => Ok(Self::CmdLine),
ZBI_TYPE_CRASHLOG => Ok(Self::CrashLog),
ZBI_TYPE_NVRAM => Ok(Self::Nvram),
ZBI_TYPE_PLATFORM_ID => Ok(Self::PlatformId),
ZBI_TYPE_DRV_BOARD_INFO => Ok(Self::DrvBoardInfo),
ZBI_TYPE_CPU_TOPOLOGY => Ok(Self::CpuTopology),
ZBI_TYPE_MEM_CONFIG => Ok(Self::MemConfig),
ZBI_TYPE_KERNEL_DRIVER => Ok(Self::KernelDriver),
ZBI_TYPE_ACPI_RSDP => Ok(Self::AcpiRsdp),
ZBI_TYPE_SMBIOS => Ok(Self::Smbios),
ZBI_TYPE_EFI_SYSTEM_TABLE => Ok(Self::EfiSystemTable),
ZBI_TYPE_EFI_MEMORY_ATTRIBUTES_TABLE => Ok(Self::EfiMemoryAttributesTable),
ZBI_TYPE_FRAMEBUFFER => Ok(Self::FrameBuffer),
ZBI_TYPE_IMAGE_ARGS => Ok(Self::ImageArgs),
ZBI_TYPE_BOOT_VERSION => Ok(Self::BootVersion),
ZBI_TYPE_DRV_MAC_ADDRESS => Ok(Self::DrvMacAddress),
ZBI_TYPE_DRV_PARTITION_MAP => Ok(Self::DrvPartitionMap),
ZBI_TYPE_DRV_BOARD_PRIVATE => Ok(Self::DrvBoardPrivate),
ZBI_TYPE_HW_REBOOT_REASON => Ok(Self::HwRebootReason),
ZBI_TYPE_SERIAL_NUMBER => Ok(Self::SerialNumber),
ZBI_TYPE_BOOTLOADER_FILE => Ok(Self::BootloaderFile),
ZBI_TYPE_DEVICETREE => Ok(Self::DeviceTree),
ZBI_TYPE_SECURE_ENTROPY => Ok(Self::SecureEntropy),
ZBI_TYPE_DEBUGDATA => Ok(Self::DebugData),
_ => Err(ZbiError::BadType),
}
}
}
bitflags! {
/// Flags associated with an item.
///
/// A valid flags value must always include [`ZbiFlags::VERSION`].
/// Values should also contain [`ZbiFlags::CRC32`] for any item
/// where it's feasible to compute the [`ZbiFlags::CRC32`] at build time.
/// Other flags are specific to each type.
///
/// Matches C-reference `zbi_flags_t` which is `uint32_t`.
pub struct ZbiFlags: u32 {
/// This flag is always required.
const VERSION = ZBI_FLAGS_VERSION;
/// ZBI items with the `CRC32` flag must have a valid `crc32`.
/// Otherwise their `crc32` field must contain `ZBI_ITEM_NO_CRC32`
const CRC32 = ZBI_FLAGS_CRC32;
}
}
/// A valid flags must always include [`ZbiFlags::VERSION`].
impl Default for ZbiFlags {
fn default() -> ZbiFlags {
ZbiFlags::VERSION
}
}
/// Rust type generated from C-reference structure `zbi_header_t`.
///
/// It must correspond to following definition:
/// ```c++
/// typedef struct {
/// // ZBI_TYPE_* constant.
/// zbi_type_t type;
///
/// // Size of the payload immediately following this header. This
/// // does not include the header itself nor any alignment padding
/// // after the payload.
/// uint32_t length;
///
/// // Type-specific extra data. Each type specifies the use of this
/// // field. When not explicitly specified, it should be zero.
/// uint32_t extra;
///
/// // Flags for this item.
/// zbi_flags_t flags;
///
/// // For future expansion. Set to 0.
/// uint32_t reserved0;
/// uint32_t reserved1;
///
/// // Must be ZBI_ITEM_MAGIC.
/// uint32_t magic;
///
/// // Must be the CRC32 of payload if ZBI_FLAGS_CRC32 is set,
/// // otherwise must be ZBI_ITEM_NO_CRC32.
/// uint32_t crc32;
/// } zbi_header_t;
/// ```
pub type ZbiHeader = zbi_header_t;
impl ZbiHeader {
/// Helper function to get `ZbiHeader.flags: u32` as `ZbiFlags`.
pub fn get_flags(&self) -> ZbiFlags {
ZbiFlags::from_bits_truncate(self.flags)
}
/// Helper function to set `ZbiHeader.flags: u32` from `ZbiFlags`.
pub fn set_flags(&mut self, flags: &ZbiFlags) {
self.flags = flags.bits();
}
}
/// The kernel image.
///
/// In a bootable ZBI this item must always be first,
/// immediately after the [`ZbiType::Container`] header. The contiguous memory
/// image of the kernel is formed from the [`ZbiType::Container`] header, the
/// `ZbiType::Kernel{ARCH}` header, and the payload.
///
/// The boot loader loads the whole image starting with the container header
/// through to the end of the kernel item's payload into contiguous physical
/// memory. It then constructs a partial ZBI elsewhere in memory, which has
/// a [`ZbiType::Container`] header of its own followed by all the other items
/// that were in the booted ZBI plus other items synthesized by the boot
/// loader to describe the machine. This partial ZBI must be placed at an
/// address (where the container header is found) that is aligned to the
/// machine's page size. The precise protocol for transferring control to
/// the kernel's entry point varies by machine.
///
/// On all machines, the kernel requires some amount of scratch memory to be
/// available immediately after the kernel image at boot. It needs this
/// space for early setup work before it has a chance to read any memory-map
/// information from the boot loader. The `reserve_memory_size` field tells
/// the boot loader how much space after the kernel's load image it must
/// leave available for the kernel's use. The boot loader must place its
/// constructed ZBI or other reserved areas at least this many bytes after
/// the kernel image.
///
/// # x86-64
///
/// The kernel assumes it was loaded at a fixed physical address of
/// 0x100000 (1MB). `ZbiKernel.entry` is the absolute physical address
/// of the PC location where the kernel will start.
/// TODO(https://fxbug.dev/42098994): Perhaps this will change??
/// The processor is in 64-bit mode with direct virtual to physical
/// mapping covering the physical memory where the kernel and
/// bootloader-constructed ZBI were loaded.
/// The %rsi register holds the physical address of the
/// bootloader-constructed ZBI.
/// All other registers are unspecified.
///
/// # ARM64
///
/// `ZbiKernel.entry` is an offset from the beginning of the image
/// (i.e., the [`ZbiType::Container`] header before the [`ZbiType::KernelArm64`]
/// header) to the PC location in the image where the kernel will
/// start. The processor is in physical address mode at EL1 or
/// above. The kernel image and the bootloader-constructed ZBI each
/// can be loaded anywhere in physical memory. The x0 register
/// holds the physical address of the bootloader-constructed ZBI.
/// All other registers are unspecified.
///
/// # RISCV64
///
/// `ZbiKernel.entry` is an offset from the beginning of the image (i.e.,
/// the [`ZbiType::Container`] header before the [`ZbiType::KernelRiscv64`] header)
/// to the PC location in the image where the kernel will start. The
/// processor is in S mode, satp is zero, sstatus.SIE is zero. The kernel
/// image and the bootloader-constructed ZBI each can be loaded anywhere in
/// physical memory, aligned to 4KiB. The a0 register holds the HART ID,
/// and the a1 register holds the 4KiB-aligned physical address of the
/// bootloader-constructed ZBI. All other registers are unspecified.
///
/// # C-reference type
/// ```c
/// typedef struct {
/// // Entry-point address. The interpretation of this differs by machine.
/// uint64_t entry;
///
/// // Minimum amount (in bytes) of scratch memory that the kernel requires
/// // immediately after its load image.
/// uint64_t reserve_memory_size;
/// } zbi_kernel_t;
/// ```
pub type ZbiKernel = zbi_kernel_t;
#[derive(Debug, PartialEq, Eq)]
/// Error values that can be returned by function in this library
pub enum ZbiError {
/// Generic error
Error,
/// Bad type
BadType,
/// Bad magic
BadMagic,
/// Bad version
BadVersion,
/// Bad CRC
BadCrc,
/// Bad Alignment
BadAlignment,
/// Truncaded error
Truncated,
/// Too big
TooBig,
/// Incomplete Kernel
IncompleteKernel,
/// Bad ZBI length for this platform
PlatformBadLength,
/// CRC32 is not supported yet
Crc32NotSupported,
/// Length type overflow
LengthOverflow,
}
// Unfortunately thiserror is not available in `no_std` world.
// Thus `Display` implementation is required.
impl Display for ZbiError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let str = match self {
ZbiError::Error => "Generic error",
ZbiError::BadType => "Bad type",
ZbiError::BadMagic => "Bad magic",
ZbiError::BadVersion => "Bad version",
ZbiError::BadCrc => "Bad CRC",
ZbiError::BadAlignment => "Bad Alignment",
ZbiError::Truncated => "Truncaded error",
ZbiError::TooBig => "Too big",
ZbiError::IncompleteKernel => "Incomplete Kernel",
ZbiError::PlatformBadLength => "Bad ZBI length for this platform",
ZbiError::Crc32NotSupported => "CRC32 is not supported yet",
ZbiError::LengthOverflow => "Length type overflow",
};
write!(f, "{str}")
}
}
// Returns offset/idx of the first buffer element that will be aligned to `ZBI_ALIGNMENT`
fn get_align_buffer_offset(buffer: impl ByteSlice) -> ZbiResult<usize> {
let addr = buffer.as_ptr() as usize;
match addr % ZBI_ALIGNMENT_USIZE {
0 => Ok(0),
rem => {
let tail_offset = ZBI_ALIGNMENT_USIZE - rem;
if tail_offset > buffer.len() {
return Err(ZbiError::TooBig);
}
Ok(tail_offset)
}
}
}
// Check if buffer is ZbiAligned
fn is_zbi_aligned(buffer: &impl ByteSlice) -> ZbiResult<()> {
match (buffer.as_ptr() as usize) % ZBI_ALIGNMENT_USIZE {
0 => Ok(()),
_ => Err(ZbiError::BadAlignment),
}
}
/// Merges two ZBI containers stored on the same buffer.
///
/// A typical use scenario is when the caller wants to append ZBI items that need to borrow the
/// existing container in order to be created, but wants to resuse the unused buffer for memory
/// optimization. The caller can split out the unused buffer, borrow the existing container,
/// creates a new container in the unused buffer, and then use this API to merge them together.
///
/// # Args:
///
/// * `buffer`: The buffer that contains the two ZBI containers. The first container must start
/// from the beginning.
/// * `second_start`: The offset to the second container in the buffer. The offset must be aligned
/// to `ZBI_ALIGNMENT_USIZE`.
///
/// # Returns
///
/// * On success returns an instance of `ZbiContainer` representing the merged container.
pub fn merge_within(buffer: &mut [u8], second_start: usize) -> ZbiResult<ZbiContainer<&mut [u8]>> {
let first_container_size = ZbiContainer::parse(&mut buffer[..])?.container_size();
if first_container_size > second_start {
return Err(ZbiError::Error);
}
let second_payload_len =
ZbiContainer::parse(&mut buffer[second_start..])?.get_payload_length_usize();
// Copies the payload part directly to the end of the first container.
let second_payload_start = second_start + size_of::<ZbiHeader>();
let second_payload_end = second_payload_start + second_payload_len;
buffer.copy_within(second_payload_start..second_payload_end, first_container_size);
// Updates first ZBI header length
let hdr = Ref::<_, ZbiHeader>::new_from_prefix(&mut buffer[..]).unwrap().0.into_mut();
hdr.length = hdr
.length
.checked_add(u32::try_from(second_payload_len).unwrap())
.ok_or(ZbiError::LengthOverflow)?;
ZbiContainer::parse(buffer)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Default)]
struct TestZbiBuilder<'a> {
buffer: &'a mut [u8],
tail_offset: usize,
}
impl<'a> TestZbiBuilder<'a> {
pub fn new(buffer: &'a mut [u8]) -> TestZbiBuilder<'a> {
TestZbiBuilder { buffer, tail_offset: 0 }
}
pub fn add<T: AsBytes>(mut self, t: T) -> Self {
t.write_to_prefix(&mut self.buffer[self.tail_offset..]).unwrap();
self.tail_offset += size_of::<T>();
self
}
pub fn add_slice(mut self, buf: &'a [u8]) -> Self {
self.buffer[self.tail_offset..self.tail_offset + buf.len()].copy_from_slice(buf);
self.tail_offset += buf.len();
self
}
pub fn get_header_default() -> ZbiHeader {
ZbiHeader {
type_: ZbiType::KernelX64 as u32,
length: 0,
extra: ZBI_ITEM_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
}
}
pub fn item_default(self, payload: &'a [u8]) -> Self {
self.item(
ZbiHeader {
length: payload.len().try_into().unwrap(),
..Self::get_header_default()
},
payload,
)
}
pub fn item(self, header: ZbiHeader, payload: &'a [u8]) -> Self {
self.add(header).add_slice(&payload[..payload.len()])
}
pub fn container_hdr(self, payload_len: usize) -> Self {
self.item(
ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: payload_len.try_into().unwrap(),
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
&[],
)
}
pub fn padding(mut self, val: u8, bytes: usize) -> Self {
self.buffer[self.tail_offset..self.tail_offset + bytes].fill(val);
self.tail_offset += bytes;
self
}
pub fn align(mut self) -> Self {
let rem = self.tail_offset % ZBI_ALIGNMENT_USIZE;
if rem != 0 {
self.tail_offset += ZBI_ALIGNMENT_USIZE - rem;
}
self
}
// Assumption is that first item in buffer is container header/item
pub fn update_container_length(self) -> Self {
let payload_length = self.tail_offset - size_of::<ZbiHeader>();
let item = ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: payload_length.try_into().unwrap(),
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
};
item.write_to_prefix(&mut self.buffer[..]).unwrap();
self
}
pub fn build(self) -> &'a mut [u8] {
&mut self.buffer[..self.tail_offset]
}
}
const ZBI_HEADER_SIZE: usize = core::mem::size_of::<ZbiHeader>();
const ALIGNED_8_SIZE: usize = ZBI_HEADER_SIZE * 20;
#[repr(align(8))]
struct ZbiAligned([u8; ALIGNED_8_SIZE]);
impl Default for ZbiAligned {
fn default() -> Self {
ZbiAligned(core::array::from_fn::<_, ALIGNED_8_SIZE, _>(|_| 0u8))
}
}
#[test]
fn test_zbi_align_overflow() {
assert!(usize::MAX > ZBI_ALIGNMENT.try_into().unwrap());
assert_eq!(u32::try_from(ZBI_ALIGNMENT_USIZE).unwrap(), ZBI_ALIGNMENT);
}
#[test]
fn test_zbi_item_new() {
let mut buffer = ZbiAligned::default();
let expect = get_test_zbi_headers(1)[0];
let (item, _) = ZbiItem::new(
&mut buffer.0[..],
expect.type_.try_into().unwrap(),
expect.extra,
expect.get_flags(),
expect.length.try_into().unwrap(),
)
.unwrap();
assert_eq!(*item.header, expect);
assert_eq!(item.payload.len(), expect.length.try_into().unwrap());
let u32_array =
Ref::<&[u8], [u32]>::new_slice_from_prefix(&buffer.0[..ZBI_HEADER_SIZE], 8).unwrap().0;
assert_eq!(u32_array[0], expect.type_);
assert_eq!(u32_array[1], expect.length);
assert_eq!(u32_array[2], expect.extra);
assert_eq!(u32_array[3], expect.flags);
// u32_array[4..5] - reserved
assert_eq!(u32_array[6], expect.magic);
assert_eq!(u32_array[7], expect.crc32);
}
#[test]
fn test_zbi_item_new_too_small() {
let mut buffer = ZbiAligned::default();
assert_eq!(
ZbiItem::new(
&mut buffer.0[..ZBI_HEADER_SIZE - 1],
ZbiType::Container,
0,
ZbiFlags::default(),
0
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_item_new_not_aligned() {
let mut buffer = ZbiAligned::default();
for offset in [1, 2, 4] {
assert_eq!(
ZbiItem::new(
&mut buffer.0[offset..ZBI_HEADER_SIZE + offset],
ZbiType::Container,
0,
ZbiFlags::default(),
0
),
Err(ZbiError::BadAlignment)
);
}
}
#[test]
fn test_zbi_item_parse() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0).build();
let buffer_hdr_extra_expected =
Ref::<&[u8], [u32]>::new_slice_from_prefix(&buffer[8..12], 1).unwrap().0[0];
let (zbi_item, _tail) = ZbiItem::parse(buffer).unwrap();
assert_eq!(zbi_item.header.extra, buffer_hdr_extra_expected);
}
#[test]
fn test_zbi_item_edit() {
let mut buffer = ZbiAligned::default();
let buffer_build = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0).build();
let buffer_hdr_type =
Ref::<&[u8], [u32]>::new_slice_from_prefix(&buffer_build[0..4], 1).unwrap().0[0];
assert_eq!(buffer_hdr_type, ZBI_TYPE_CONTAINER);
let (mut zbi_item, _tail) = ZbiItem::parse(&mut buffer_build[..]).unwrap();
zbi_item.header.type_ = ZBI_TYPE_KERNEL_X64;
let buffer_hdr_type =
Ref::<&[u8], [u32]>::new_slice_from_prefix(&buffer_build[0..4], 1).unwrap().0[0];
assert_eq!(buffer_hdr_type, ZBI_TYPE_KERNEL_X64);
}
#[test]
fn test_zbi_container_new() {
let mut buffer = ZbiAligned::default();
let _container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let expect_hdr = ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
};
let (item, _) = ZbiItem::parse(&buffer.0[..]).unwrap();
assert_eq!(*item.header, expect_hdr);
assert_eq!(item.payload.len(), 0);
}
#[test]
fn test_zbi_container_new_too_small() {
let mut buffer = ZbiAligned::default();
assert_eq!(ZbiContainer::new(&mut buffer.0[..ZBI_HEADER_SIZE - 1]), Err(ZbiError::TooBig));
}
#[test]
fn test_zbi_container_new_unaligned() {
let mut buffer = ZbiAligned::default();
for offset in [1, 2, 3, 4, 5, 6, 7] {
assert_eq!(
ZbiContainer::new(&mut buffer.0[offset..ZBI_HEADER_SIZE + offset]),
Err(ZbiError::BadAlignment)
);
}
}
#[test]
fn test_zbi_container_parse_empty() {
let mut buffer = ZbiAligned::default();
let _container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let expect_hdr = ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
};
let ZbiContainer { header, buffer: _, payload_length } =
ZbiContainer::parse(&buffer.0[..]).unwrap();
assert_eq!(*header, expect_hdr);
assert_eq!(payload_length, 0);
}
#[test]
fn test_zbi_container_parse_bad_type() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.item(
ZbiHeader {
type_: 0,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
&[],
)
.build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadType))
}
#[test]
fn test_zbi_container_parse_bad_magic() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.item(
ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: ZbiFlags::default().bits(),
magic: 0,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
&[],
)
.build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadMagic))
}
#[test]
fn test_zbi_container_parse_bad_version() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.item(
ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: (ZbiFlags::default() & !ZbiFlags::VERSION).bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
&[],
)
.build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadVersion))
}
#[test]
fn test_zbi_container_parse_bad_crc32() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.item(
ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: (ZbiFlags::default() & !ZbiFlags::CRC32).bits(),
magic: ZBI_ITEM_MAGIC,
crc32: 0,
..Default::default()
},
&[],
)
.build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadCrc))
}
#[test]
fn test_zbi_container_parse_entries_bad_magic() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.item(
ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
length: 0,
extra: ZBI_CONTAINER_MAGIC,
flags: (ZbiFlags::default() & !ZbiFlags::CRC32).bits(),
magic: ZBI_ITEM_MAGIC,
crc32: 0,
..Default::default()
},
&[],
)
.build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadCrc))
}
#[test]
fn test_zbi_container_parse() {
let expected_payloads: [&[u8]; 9] = [
&[1],
&[1, 2],
&[1, 2, 3],
&[1, 2, 3, 4],
&[1, 2, 3, 4, 5],
&[1, 2, 3, 4, 5, 6],
&[1, 2, 3, 4, 5, 6, 7],
&[1, 2, 3, 4, 5, 6, 7, 8],
&[1, 2, 3, 4, 5, 6, 7, 8, 9],
];
let expected_items = expected_payloads.map(|x| {
(
ZbiHeader {
length: x.len().try_into().unwrap(),
..TestZbiBuilder::get_header_default()
},
x,
)
});
let mut buffer = ZbiAligned::default();
let mut builder = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0);
for payloads in expected_payloads {
builder = builder.align().item_default(payloads).align()
}
let buffer = builder.update_container_length().build();
let zbi_container = ZbiContainer::parse(&*buffer).unwrap();
let mut it = zbi_container.iter();
for (expected_hdr, expected_payload) in expected_items.iter() {
let Some(item) = it.next() else { panic!("expecting iterator with value") };
assert_eq!(item.header.into_ref(), expected_hdr);
assert_eq!(&item.payload[..], *expected_payload);
}
assert!(it.next().is_none());
}
#[test]
fn test_zbi_container_parse_unaligned() {
let buffer = ZbiAligned::default();
for offset in [1, 2, 3, 4, 5, 6, 7] {
assert_eq!(ZbiContainer::parse(&buffer.0[offset..]), Err(ZbiError::BadAlignment));
}
}
#[test]
fn test_zbi_container_parse_without_last_padding_fail_truncated() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1])
.align()
.item_default(&[1, 2])
.update_container_length()
.build();
assert_eq!(ZbiContainer::parse(&*buffer), Err(ZbiError::Truncated));
}
#[test]
fn test_zbi_container_parse_error_payload_truncated() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.add_slice(&[1])
.update_container_length()
.build();
assert_eq!(ZbiContainer::parse(&buffer[..buffer.len() - 1]), Err(ZbiError::Truncated));
}
#[test]
fn test_zbi_container_parse_error_truncated() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.padding(0, 1)
.update_container_length()
.build();
assert_eq!(ZbiContainer::parse(&buffer[..buffer.len() - 1]), Err(ZbiError::Truncated));
}
#[test]
fn test_zbi_container_parse_bad_first_entry_marked() {
let mut buffer = get_test_creference_buffer();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
container
.iter_mut()
.filter(|e| {
[ZbiType::CmdLine as u32, ZbiType::StorageRamdisk as u32].contains(&e.header.type_)
})
.for_each(|mut e| e.header.magic = 0);
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadMagic));
}
#[test]
fn test_zbi_container_parse_bad_entry_magic() {
let mut buffer = get_test_creference_buffer();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
container
.iter_mut()
.filter(|e| ZbiType::CmdLine as u32 == e.header.type_)
.for_each(|mut e| e.header.magic = 0);
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadMagic));
}
#[test]
fn test_zbi_container_parse_bad_entry_version() {
let mut buffer = get_test_creference_buffer();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
container
.iter_mut()
.filter(|e| ZbiType::CmdLine as u32 == e.header.type_)
.for_each(|mut e| e.header.flags &= (!ZbiFlags::VERSION).bits());
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadVersion));
}
#[test]
fn test_zbi_container_parse_bad_entry_crc() {
let mut buffer = get_test_creference_buffer();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
container.iter_mut().filter(|e| ZbiType::CmdLine as u32 == e.header.type_).for_each(
|mut e| {
e.header.flags &= (!ZbiFlags::CRC32).bits();
e.header.crc32 = 0;
},
);
assert_eq!(ZbiContainer::parse(&buffer.0[..]), Err(ZbiError::BadCrc));
}
#[test]
fn test_zbi_container_new_entry() {
let mut buffer = ZbiAligned::default();
let new_entries = get_test_entries_all();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
for (e, payload) in &new_entries {
container.get_next_payload().unwrap()[..payload.len()].copy_from_slice(payload);
container
.create_entry(e.type_.try_into().unwrap(), e.extra, e.get_flags(), payload.len())
.unwrap();
}
let container = ZbiContainer::parse(&buffer.0[..]).unwrap();
check_container_made_of(&container, &new_entries);
}
#[test]
fn test_zbi_container_new_entry_crc32_not_supported() {
let mut buffer = ZbiAligned::default();
let (new_entry, payload) = get_test_entry_nonempty_payload();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
assert_eq!(
container.create_entry_with_payload(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
ZbiFlags::default() | ZbiFlags::CRC32,
payload,
),
Err(ZbiError::Crc32NotSupported)
);
}
#[test]
fn test_zbi_container_new_entry_no_space_left() {
let mut buffer = ZbiAligned::default();
let new_entry = get_test_entry_empty_payload().0;
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
for _ in 1..(ALIGNED_8_SIZE / ZBI_HEADER_SIZE) {
container
.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
)
.unwrap();
}
// Now there is not enough space and it should fail
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_container_new_entry_no_space_for_header() {
let mut buffer = ZbiAligned::default();
let new_entry = get_test_entry_empty_payload().0;
let buf_len = 2 * core::mem::size_of::<ZbiHeader>() - 1;
let mut container = ZbiContainer::new(&mut buffer.0[..buf_len]).unwrap();
// Now there is not enough space for header and it should fail
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
0,
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_container_new_entry_no_space_for_payload() {
let mut buffer = ZbiAligned::default();
let (new_entry, payload) = get_test_entry_nonempty_payload();
let buf_len = 2 * core::mem::size_of::<ZbiHeader>() + payload.len() - 1;
let mut container = ZbiContainer::new(&mut buffer.0[..buf_len]).unwrap();
// Now there is not enough space for header and it should fail
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_container_new_entry_with_payload_just_enough_to_fit_no_align() {
let mut buffer = ZbiAligned::default();
let (new_entry, _payload) = get_test_entry_empty_payload();
let payload = [0; ZBI_ALIGNMENT_USIZE];
let buf_len = 2 * core::mem::size_of::<ZbiHeader>()
+ payload.len()
+ (/*alignment*/ZBI_ALIGNMENT_USIZE - payload.len());
let mut container = ZbiContainer::new(&mut buffer.0[..buf_len]).unwrap();
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
payload.len(),
),
Ok(())
);
}
#[test]
fn test_zbi_container_new_entry_with_payload_just_enough_to_fit_with_alignment() {
let mut buffer = ZbiAligned::default();
let (new_entry, payload) = get_test_entry_nonempty_payload();
let buf_len = 2 * core::mem::size_of::<ZbiHeader>()
+ payload.len()
+ (ZBI_ALIGNMENT_USIZE - payload.len()/*alignment*/);
let mut container = ZbiContainer::new(&mut buffer.0[..buf_len]).unwrap();
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
),
Ok(())
);
}
#[test]
fn test_zbi_container_new_entry_payload_too_big() {
let mut buffer = ZbiAligned::default();
let (new_entry, _payload) = get_test_entry_nonempty_payload();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
usize::MAX,
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_container_new_entry_no_space_left_unaligned() {
let mut buffer = ZbiAligned::default();
let new_entry = get_test_entry_empty_payload().0;
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
for _ in 1..(ALIGNED_8_SIZE / ZBI_HEADER_SIZE) {
container
.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
)
.unwrap();
}
// Now there is not enough space and it should fail
assert_eq!(
container.create_entry(
new_entry.type_.try_into().unwrap(),
new_entry.extra,
new_entry.get_flags(),
new_entry.length.try_into().unwrap(),
),
Err(ZbiError::TooBig)
);
}
#[test]
fn test_zbi_container_extend_new() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1])
.align()
.update_container_length()
.build();
let container_0 = ZbiContainer::parse(buffer).unwrap();
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1, 2])
.align()
.update_container_length()
.build();
let container_1 = ZbiContainer::parse(buffer).unwrap();
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
container.extend(&container_0).unwrap();
container.extend(&container_1).unwrap();
let container_check = ZbiContainer::parse(&buffer.0[..]).unwrap();
assert_eq!(container_check.iter().count(), 2);
assert_eq!(container_0.iter().count(), 1);
assert_eq!(container_1.iter().count(), 1);
let mut it = container_check.iter();
assert_eq!(it.next().unwrap(), container_0.iter().next().unwrap());
assert_eq!(it.next().unwrap(), container_1.iter().next().unwrap());
assert!(it.next().is_none());
}
#[test]
fn test_zbi_container_extend_unaligned() {
let mut buffer_0 = ZbiAligned::default();
let mut container_0 = ZbiContainer::new(&mut buffer_0.0[..]).unwrap();
container_0
.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), b"0")
.unwrap();
let container_size_0 = container_0.container_size();
// Copies to unaligned address.
let mut unaligned_0 = ZbiAligned::default();
let unaligned_0 = &mut unaligned_0.0[1..][..container_size_0];
unaligned_0.clone_from_slice(&buffer_0.0[..unaligned_0.len()]);
let mut buffer_1 = ZbiAligned::default();
let mut container_1 = ZbiContainer::new(&mut buffer_1.0[..]).unwrap();
container_1
.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), b"1")
.unwrap();
let container_size_1 = container_1.container_size();
// Copies to unaligned address.
let mut unaligned_1 = ZbiAligned::default();
let unaligned_1 = &mut unaligned_1.0[1..][..container_size_1];
unaligned_1.clone_from_slice(&buffer_1.0[..unaligned_1.len()]);
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let container_0 = ZbiContainer::parse(&mut buffer_0.0[..]).unwrap();
let container_1 = ZbiContainer::parse(&mut buffer_1.0[..]).unwrap();
container.extend_unaligned(unaligned_0).unwrap();
container.extend_unaligned(unaligned_1).unwrap();
let mut it = container.iter();
assert_eq!(it.next().unwrap(), container_0.iter().next().unwrap());
assert_eq!(it.next().unwrap(), container_1.iter().next().unwrap());
assert!(it.next().is_none());
}
#[test]
fn test_zbi_container_extend_unaligned_too_big() {
let mut buffer = ZbiAligned::default();
let buffer_len = buffer.0.len();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let remains = buffer_len - container.container_size();
let mut extend = ZbiAligned::default();
ZbiContainer::new(&mut extend.0[..]).unwrap();
container.extend_unaligned(&extend.0[..remains]).unwrap();
// Should fail since there is not enough space to copy the incoming buffer first, despite
// that the container to extend has zero payload.
assert!(container.extend_unaligned(&extend.0[..remains + 1]).is_err());
}
#[test]
fn test_zbi_container_extend_unaligned_invalid_container() {
let mut buffer = ZbiAligned::default();
let buffer_len = buffer.0.len();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let remains = buffer_len - container.container_size();
assert!(container.extend_unaligned(&vec![0u8; remains][..]).is_err());
}
#[test]
fn test_zbi_container_extend_with_empty() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1])
.align()
.update_container_length()
.build();
let mut container_0 = ZbiContainer::parse(&mut buffer[..]).unwrap();
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0).build();
let container_1 = ZbiContainer::parse(&mut buffer[..]).unwrap();
assert_eq!(container_0.iter().count(), 1);
container_0.extend(&container_1).unwrap();
assert_eq!(container_0.iter().count(), 1);
}
#[test]
fn test_zbi_container_extend_full() {
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.update_container_length()
.build();
let mut container_full = ZbiContainer::parse(&mut buffer[..]).unwrap();
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1, 2])
.align()
.update_container_length()
.build();
let container = ZbiContainer::parse(buffer).unwrap();
assert_eq!(container_full.extend(&container), Err(ZbiError::TooBig));
}
#[test]
fn test_zbi_container_extend_1_byte_short() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.update_container_length()
.build();
let mut container_small =
ZbiContainer::parse(&mut buffer.0[..ZBI_HEADER_SIZE * 2 + ZBI_ALIGNMENT_USIZE - 1])
.unwrap();
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1, 2])
.align()
.update_container_length()
.build();
let container = ZbiContainer::parse(buffer).unwrap();
assert_eq!(container_small.extend(&container), Err(ZbiError::TooBig));
}
#[test]
fn test_zbi_container_extend_use_all_buffer() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.update_container_length()
.build();
let mut container_full = ZbiContainer::parse(
&mut buffer.0[..ZBI_HEADER_SIZE + ZBI_HEADER_SIZE + ZBI_ALIGNMENT_USIZE],
)
.unwrap();
let mut buffer = ZbiAligned::default();
let buffer = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1, 2])
.align()
.update_container_length()
.build();
let container = ZbiContainer::parse(buffer).unwrap();
assert!(container_full.extend(&container).is_ok());
}
#[test]
fn test_zbi_container_new_entry_with_payload() {
let mut buffer = ZbiAligned::default();
let new_entries = get_test_entries_all();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
for (e, payload) in &new_entries {
container
.create_entry_with_payload(
e.type_.try_into().unwrap(),
e.extra,
e.get_flags(),
payload,
)
.unwrap();
}
let container = ZbiContainer::parse(&buffer.0[..]).unwrap();
check_container_made_of(&container, &new_entries);
}
fn check_container_made_of<B: ByteSlice + PartialEq>(
container: &ZbiContainer<B>,
expected_items: &[(ZbiHeader, &[u8])],
) {
// Check container header length
assert_eq!(
container.get_payload_length_usize(),
expected_items.len() * ZBI_HEADER_SIZE // add header len
+ expected_items // add payloads
.iter()
.map(|(_, payload)| -> usize {
payload.len() +
match payload.len() % ZBI_ALIGNMENT_USIZE{
0 => 0,
rem => ZBI_ALIGNMENT_USIZE- rem,
}
})
.sum::<usize>()
);
assert_eq!(
container.container_size(),
container.get_payload_length_usize() + size_of::<ZbiHeader>()
);
// Check if container elements match provided items
let mut it = expected_items.iter();
for b in container.iter() {
let (header, payload) = it.next().unwrap();
assert_eq!(*b.header, *header);
assert_eq!(b.payload.len(), payload.len());
assert!(b.payload.iter().zip(payload.iter()).all(|(a, b)| a == b))
}
}
#[test]
fn test_zbi_container_get_next_paylad() {
let mut buffer = ZbiAligned::default();
let new_entries = get_test_entries_all();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
for (e, payload) in &new_entries {
let next_payload: &mut [u8] = container.get_next_payload().unwrap();
next_payload[..payload.len()].copy_from_slice(payload);
container
.create_entry(e.type_.try_into().unwrap(), e.extra, e.get_flags(), payload.len())
.unwrap();
}
let container = ZbiContainer::parse(&buffer.0[..]).unwrap();
check_container_made_of(&container, &new_entries);
}
#[test]
fn test_zbi_container_get_next_paylad_length() {
let mut buffer = ZbiAligned::default();
// Expected payload length is same as buffer - container header - item header
let expected_payload_len = buffer.0.len() - 2 * core::mem::size_of::<ZbiHeader>();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let next_payload: &mut [u8] = container.get_next_payload().unwrap();
assert_eq!(next_payload.len(), expected_payload_len);
}
#[test]
fn test_zbi_container_get_next_paylad_only_header_can_fit() {
let mut buffer = ZbiAligned::default();
// Buffer length that only fits container and item header.
let len = 2 * core::mem::size_of::<ZbiHeader>();
let mut container = ZbiContainer::new(&mut buffer.0[..len]).unwrap();
let next_payload: &mut [u8] = container.get_next_payload().unwrap();
assert_eq!(next_payload.len(), 0);
}
#[test]
fn test_zbi_container_get_next_paylad_header_cant_fit() {
let mut buffer = ZbiAligned::default();
// Buffer length that only fits container but not item header.
let len = 2 * core::mem::size_of::<ZbiHeader>() - 1;
let mut container = ZbiContainer::new(&mut buffer.0[..len]).unwrap();
assert_eq!(container.get_next_payload(), Err(ZbiError::TooBig));
}
#[test]
fn test_zbi_container_get_next_paylad_length_overflow() {
let mut buffer = ZbiAligned::default();
// Buffer length that only fits container but not item header.
let len = 2 * core::mem::size_of::<ZbiHeader>() - 1;
let mut container = ZbiContainer::new(&mut buffer.0[..len]).unwrap();
container.payload_length = usize::MAX; // Pretend that length is too big and cause
// overflow in following functions
assert_eq!(container.get_next_payload(), Err(ZbiError::LengthOverflow));
}
/* Binary blob for parsing container was generated from C implementation, running following
* test:
* --- a/src/firmware/lib/zbi/test/zbi.cc
* +++ b/src/firmware/lib/zbi/test/zbi.cc
* @@ -926,3 +926,21 @@ TEST(ZbiTests, ZbiTestNoOverflow) {
*
* ASSERT_NE(zbi_extend(dst_buffer, kUsableBufferSize, src_buffer), ZBI_RESULT_OK);
* }
* +
* +TEST(ZbiTests, ZbiTestGenDataForRustTest) {
* + const size_t kExtraBytes = 10;
* + uint8_t* buffer = get_test_zbi_extra(kExtraBytes);
* + // Based on `get_test_zbi_extra()` implementation this is buffer size
* + const size_t kBufferSize = sizeof(test_zbi_t) + kExtraBytes;
* +
* + printf("buffer length = %zu\n", kBufferSize);
* + printf("----BEGIN----\n");
* + for (size_t i = 0; i < kBufferSize; i++) {
* + if (i % 16 == 0) {
* + printf("\n");
* + }
* + printf("%02x", buffer[i]);
* + }
* + printf("\n");
* + printf("-----END-----\n");
* +}
*/
#[test]
fn test_zbi_container_parse_c_reference() {
let ref_buffer = get_test_creference_buffer_vec();
let expected_container_hdr = ZbiHeader {
type_: ZBI_TYPE_CONTAINER,
extra: ZBI_CONTAINER_MAGIC,
length: 184,
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
flags: ZbiFlags::default().bits(),
..Default::default()
};
// Reference C implementation test uses cstrings for payload. That is why we need '\0' at
// the end of the string.
let expected_entries = get_test_entries_creference();
let mut buffer = ZbiAligned::default();
buffer.0[..ref_buffer.len()].clone_from_slice(&ref_buffer);
let container = ZbiContainer::parse(&buffer.0[..ref_buffer.len()]).unwrap();
assert_eq!(*container.header, expected_container_hdr);
check_container_made_of(&container, &expected_entries);
}
#[test]
fn test_zbi_container_new_entry_iterate() {
let mut buffer = ZbiAligned::default();
let new_entry = get_test_entry_nonempty_payload();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let (e, payload) = new_entry;
container
.create_entry_with_payload(e.type_.try_into().unwrap(), e.extra, e.get_flags(), payload)
.unwrap();
assert_eq!(container.iter().count(), 1);
let mut it = container.iter();
let item = it.next().unwrap();
assert_eq!(*item.header, e);
assert_eq!(&item.payload[..], payload);
assert!(it.next().is_none());
}
#[test]
fn test_zbi_container_new_entry_mut_iterate() {
let mut buffer = ZbiAligned::default();
let new_entry = get_test_entry_nonempty_payload();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let (e, payload) = new_entry;
container
.create_entry_with_payload(e.type_.try_into().unwrap(), e.extra, e.get_flags(), payload)
.unwrap();
{
let mut item = container.iter_mut().next().unwrap();
assert_ne!(item.header.type_, ZbiType::DebugData.into());
item.header.type_ = ZbiType::DebugData.into();
}
{
let item = container.iter().next().unwrap();
assert_eq!(item.header.type_, ZbiType::DebugData.into());
}
}
#[test]
fn test_zbi_container_parse_new_entry_mut_iterate() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(&[1, 2])
.align()
.update_container_length()
.build();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
let new_entry = get_test_entry_nonempty_payload();
let (e, payload) = new_entry;
container
.create_entry_with_payload(e.type_.try_into().unwrap(), e.extra, e.get_flags(), payload)
.unwrap();
assert_eq!(container.iter().count(), 2);
for mut item in container.iter_mut() {
assert_ne!(item.header.type_, ZbiType::DebugData.into());
item.header.type_ = ZbiType::DebugData.into();
}
for item in container.iter() {
assert_eq!(item.header.type_, ZbiType::DebugData.into());
}
}
#[test]
fn test_zbi_container_iterate_empty() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0).build();
assert_eq!(ZbiContainer::parse(&buffer.0[..]).unwrap().iter().count(), 0);
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
assert_eq!(container.iter().count(), 0);
assert_eq!(container.iter_mut().count(), 0);
}
fn byteslice_cmp(byteslice: impl ByteSlice, slice: &[u8]) -> bool {
byteslice.len() == slice.len() && byteslice.iter().zip(slice.iter()).all(|(a, b)| a == b)
}
#[test]
fn test_zbi_container_iterate_ref() {
let mut buffer = get_test_creference_buffer();
let container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
assert_eq!(container.iter().count(), 4);
assert!(container.iter().zip(get_test_entries_creference().iter()).all(
|(it, (entry, payload))| { *it.header == *entry && byteslice_cmp(it.payload, payload) }
));
}
#[test]
fn test_zbi_container_iterate_modify() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item_default(b"A")
.align()
.item_default(b"BB")
.align()
.item_default(b"CCC")
.align()
.update_container_length()
.build();
let mut container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
container.iter_mut().for_each(|mut item| item.payload[0] = b'D');
assert!(container.iter().all(|b| b.payload[0] == b'D'));
}
#[test]
fn test_zbi_bad_type() {
assert_eq!(ZbiType::try_from(0), Err(ZbiError::BadType));
}
fn get_all_zbi_type_values() -> Vec<ZbiType> {
// strum and enum-iterator crates are not available at the moment, so just hard coding
// values
vec![
ZbiType::KernelX64,
ZbiType::KernelArm64,
ZbiType::KernelRiscv64,
ZbiType::Container,
ZbiType::Discard,
ZbiType::StorageRamdisk,
ZbiType::StorageBootFs,
ZbiType::StorageKernel,
ZbiType::StorageBootFsFactory,
ZbiType::CmdLine,
ZbiType::CrashLog,
ZbiType::Nvram,
ZbiType::PlatformId,
ZbiType::DrvBoardInfo,
ZbiType::CpuTopology,
ZbiType::MemConfig,
ZbiType::KernelDriver,
ZbiType::AcpiRsdp,
ZbiType::Smbios,
ZbiType::EfiSystemTable,
ZbiType::EfiMemoryAttributesTable,
ZbiType::FrameBuffer,
ZbiType::ImageArgs,
ZbiType::BootVersion,
ZbiType::DrvMacAddress,
ZbiType::DrvPartitionMap,
ZbiType::DrvBoardPrivate,
ZbiType::HwRebootReason,
ZbiType::SerialNumber,
ZbiType::BootloaderFile,
ZbiType::DeviceTree,
ZbiType::SecureEntropy,
ZbiType::DebugData,
]
}
fn get_kernel_zbi_types() -> Vec<ZbiType> {
vec![ZbiType::KernelRiscv64, ZbiType::KernelX64, ZbiType::KernelArm64]
}
fn get_metadata_zbi_types() -> Vec<ZbiType> {
vec![
ZbiType::DrvBoardInfo,
ZbiType::DrvMacAddress,
ZbiType::DrvPartitionMap,
ZbiType::DrvBoardPrivate,
]
}
#[test]
fn test_zbi_type_is_kernel() {
assert!(get_kernel_zbi_types().iter().all(|t| t.is_kernel()))
}
#[test]
fn test_zbi_type_is_not_kernel() {
assert!(get_all_zbi_type_values()
.iter()
.filter(|v| !get_kernel_zbi_types().contains(v))
.all(|v| !v.is_kernel()));
}
#[test]
fn test_zbi_type_is_driver_metadata() {
assert!(get_metadata_zbi_types().iter().all(|t| t.is_driver_metadata()));
}
#[test]
fn test_zbi_type_is_not_driver_metadata() {
assert!(get_all_zbi_type_values()
.iter()
.filter(|v| !get_metadata_zbi_types().contains(v))
.all(|v| !v.is_driver_metadata()));
}
#[test]
fn test_zbi_default_type_has_version() {
assert!(ZbiFlags::default().contains(ZbiFlags::VERSION));
}
#[test]
fn test_zbi_is_bootable() {
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
container
.create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &[])
.unwrap();
assert!(container.is_bootable().is_ok());
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_zbi_is_bootable_reference() {
let ref_buffer = get_test_creference_buffer_vec();
let mut buffer = ZbiAligned::default();
buffer.0[..ref_buffer.len()].clone_from_slice(&ref_buffer);
let container = ZbiContainer::parse(&buffer.0[..]).unwrap();
assert!(container.is_bootable().is_ok());
}
#[test]
fn test_zbi_is_bootable_empty_container() {
let mut buffer = ZbiAligned::default();
let container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
assert_eq!(container.is_bootable(), Err(ZbiError::Truncated));
}
#[test]
fn test_zbi_is_bootable_wrong_arch() {
let mut buffer = ZbiAligned::default();
let _ = TestZbiBuilder::new(&mut buffer.0[..])
.container_hdr(0)
.align()
.item(ZbiHeader { type_: 0, ..TestZbiBuilder::get_header_default() }, &[])
.align()
.update_container_length()
.build();
let container = ZbiContainer::parse(&mut buffer.0[..]).unwrap();
assert_eq!(container.is_bootable(), Err(ZbiError::IncompleteKernel));
}
#[test]
fn test_zbi_is_bootable_not_first_item_fail() {
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
container
.create_entry_with_payload(ZbiType::DebugData, 0, ZbiFlags::default(), &[])
.unwrap();
container
.create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &[])
.unwrap();
assert_eq!(container.is_bootable(), Err(ZbiError::IncompleteKernel));
}
#[test]
fn test_zbi_get_kernel_entry_and_reserved_memory_size() {
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let bytes = [1u64.to_le_bytes(), 2u64.to_le_bytes()].concat();
container
.create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &bytes)
.unwrap();
assert_eq!(container.get_kernel_entry_and_reserved_memory_size().unwrap(), (1, 2));
}
#[test]
fn test_zbi_get_kernel_entry_and_reserved_memory_size_truncated() {
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
container
.create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &[])
.unwrap();
assert!(container.get_kernel_entry_and_reserved_memory_size().is_err());
}
#[test]
fn zbi_get_buffer_size_for_kernel_relocation() {
let mut buffer = ZbiAligned::default();
let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let bytes = [0u64.to_le_bytes(), 1024u64.to_le_bytes()].concat();
container
.create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &bytes)
.unwrap();
assert_eq!(
container.get_buffer_size_for_kernel_relocation().unwrap(),
container.container_size() + 1024
);
}
#[test]
fn test_zbi_header_alignment() {
assert_eq!(core::mem::size_of::<ZbiHeader>() & ZBI_ALIGNMENT_USIZE, 0);
}
fn get_test_payloads_all() -> Vec<&'static [u8]> {
vec![
&[],
&[1],
&[1, 2],
&[1, 2, 3, 4, 5],
// This 4 elements are for C reference binary testing
b"4567\0",
b"0123\0",
b"0123456789\0",
b"abcdefghijklmnopqrs\0",
]
}
fn get_test_zbi_headers_all() -> Vec<ZbiHeader> {
let test_payloads = get_test_payloads_all();
vec![
ZbiHeader {
type_: ZBI_TYPE_KERNEL_RISCV64,
length: test_payloads[0].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_KERNEL_ARM64,
length: test_payloads[1].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_KERNEL_RISCV64,
length: test_payloads[2].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_KERNEL_X64,
length: test_payloads[3].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_KERNEL_X64,
length: test_payloads[4].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_CMDLINE,
length: test_payloads[5].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_STORAGE_RAMDISK,
length: test_payloads[6].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
ZbiHeader {
type_: ZBI_TYPE_STORAGE_BOOTFS,
length: test_payloads[7].len().try_into().unwrap(),
extra: 0,
flags: ZbiFlags::default().bits(),
magic: ZBI_ITEM_MAGIC,
crc32: ZBI_ITEM_NO_CRC32,
..Default::default()
},
]
}
fn get_test_zbi_headers(num: usize) -> Vec<ZbiHeader> {
get_test_zbi_headers_all()[..num].to_vec()
}
fn get_test_entries_all() -> Vec<(ZbiHeader, &'static [u8])> {
let headers = get_test_zbi_headers_all();
let payloads = get_test_payloads_all();
assert_eq!(headers.len(), payloads.len());
headers.iter().cloned().zip(payloads.iter().cloned()).collect()
}
fn get_test_entries(num: usize) -> Vec<(ZbiHeader, &'static [u8])> {
get_test_entries_all()[..num].to_vec()
}
fn get_test_entry_empty_payload() -> (ZbiHeader, &'static [u8]) {
get_test_entries(1)[0]
}
fn get_test_entry_nonempty_payload() -> (ZbiHeader, &'static [u8]) {
get_test_entries(2)[1]
}
fn get_test_entries_creference() -> Vec<(ZbiHeader, &'static [u8])> {
let entries = get_test_entries_all();
entries[entries.len() - 4..].to_vec()
}
fn get_test_creference_buffer() -> ZbiAligned {
let entries = get_test_entries_creference();
let mut buffer = ZbiAligned::default();
let mut builder = TestZbiBuilder::new(&mut buffer.0[..]).container_hdr(0);
for entry in entries {
builder = builder.item(entry.0, entry.1).align();
}
let _ = builder.update_container_length().padding(0xab_u8, 10).build();
buffer
}
fn get_test_creference_buffer_vec() -> Vec<u8> {
hex::decode(
"424f4f54b8000000e6f78c8600000100\
0000000000000000291778b5d6e8874a\
4b524e4c050000000000000000000100\
0000000000000000291778b5d6e8874a\
3435363700000000434d444c05000000\
00000000000001000000000000000000\
291778b5d6e8874a3031323300000000\
5244534b0b0000000000000000000100\
0000000000000000291778b5d6e8874a\
30313233343536373839000000000000\
42465342140000000000000000000100\
0000000000000000291778b5d6e8874a\
6162636465666768696a6b6c6d6e6f70\
7172730000000000abababababababab\
abab",
)
.unwrap()
}
#[test]
fn test_creference_buffer_generation() {
let ref_buffer = get_test_creference_buffer_vec();
let buffer = get_test_creference_buffer();
assert_eq!(&ref_buffer[..ref_buffer.len()], &buffer.0[..ref_buffer.len()]);
}
#[test]
fn test_zbi_zbi_error() {
let e = ZbiError::Error;
println!("{e}");
println!("{e:?}");
println!("{e:#?}");
}
#[test]
fn test_zbi_container_align_buffer() {
let buffer = ZbiAligned::default();
let original_len = buffer.0.len();
let buffer = align_buffer(&buffer.0[1..]).unwrap();
assert_eq!(buffer.as_ptr() as usize % ZBI_ALIGNMENT_USIZE, 0);
assert_eq!(buffer.len(), original_len - ZBI_ALIGNMENT_USIZE);
}
#[test]
fn test_zbi_container_align_buffer_empty() {
let buffer = ZbiAligned::default();
let buffer = align_buffer(&buffer.0[..0]).unwrap();
assert_eq!(buffer.as_ptr() as usize % ZBI_ALIGNMENT_USIZE, 0);
assert_eq!(buffer.len(), 0);
}
#[test]
fn test_zbi_container_align_buffer_too_short() {
let buffer = ZbiAligned::default();
assert_eq!(align_buffer(&buffer.0[1..ZBI_ALIGNMENT_USIZE - 1]), Err(ZbiError::TooBig));
}
#[test]
fn test_zbi_container_align_buffer_just_enough() {
let buffer = ZbiAligned::default();
let buffer = align_buffer(&buffer.0[1..ZBI_ALIGNMENT_USIZE]).unwrap();
assert_eq!(buffer.as_ptr() as usize % ZBI_ALIGNMENT_USIZE, 0);
assert_eq!(buffer.len(), 0);
}
#[test]
fn test_merge_within() {
let mut buffer = vec![0u8; 1024];
let buffer = align_buffer(&mut buffer[..]).unwrap();
let mut container_0 = ZbiContainer::new(&mut buffer[..]).unwrap();
container_0
.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), b"0")
.unwrap();
let container_size_0 = container_0.container_size();
let mut container_1 = ZbiContainer::new(&mut buffer[container_size_0..]).unwrap();
container_1
.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), b"1")
.unwrap();
// Makes a copy of the buffer for performing the merge.
let mut copy = buffer.to_vec();
let merged = merge_within(&mut copy[..], container_size_0).unwrap();
let (buffer_0, buffer_1) = buffer.split_at_mut(container_size_0);
let container_0 = ZbiContainer::parse(buffer_0).unwrap();
let container_1 = ZbiContainer::parse(buffer_1).unwrap();
let mut it = merged.iter();
assert_eq!(it.next().unwrap(), container_0.iter().next().unwrap());
assert_eq!(it.next().unwrap(), container_1.iter().next().unwrap());
assert!(it.next().is_none());
}
#[test]
fn test_merge_within_invalid_second_start() {
let mut buffer = ZbiAligned::default();
ZbiContainer::new(&mut buffer.0[..]).unwrap();
assert!(merge_within(&mut buffer.0[..], 0).is_err());
}
#[test]
fn test_merge_within_invalid_first_container() {
let mut buffer = ZbiAligned::default();
ZbiContainer::new(&mut buffer.0[2 * ZBI_ALIGNMENT_USIZE..]).unwrap();
assert!(merge_within(&mut buffer.0[..], 2 * ZBI_ALIGNMENT_USIZE).is_err());
}
#[test]
fn test_merge_within_invalid_second_container() {
let mut buffer = ZbiAligned::default();
let first = ZbiContainer::new(&mut buffer.0[..]).unwrap();
let first_sz = first.container_size();
assert!(merge_within(&mut buffer.0[..], first_sz).is_err());
}
}