blob: a059d23fc5835125d36f52400c3a40efa8430472 [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use crate::structures::VariableSized;
// Light-weight class for decoding structs in a safe manner.
//
// Each operation returns a pointer to a valid struct or None indicating
// that the read would return an invalid structure, such as a structure out of
// bounds of the original input buffer.
//
// BinaryReader supports a common requirement in ACPI of variable-length
// structures, where a struct consists of a header followed by a payload.
// To support such structures, we require a |size| method returning the
// size of the header + payload.
//
// Successful reads consume bytes from the buffer, while failed reads don't
// modify internal state.
pub struct BinaryReader<'a> {
buffer: &'a [u8],
}
impl<'a> BinaryReader<'a> {
// Construct a BinaryReader from the given slice.
pub fn new(buffer: &'a [u8]) -> Self {
Self { buffer }
}
/// # Safety
/// The caller must ensure that `ptr` points to a valid block of memory
/// of at least `size` bytes, and that the memory remains valid for the
/// lifetime `'a`.
pub unsafe fn from_ptr(ptr: *const u8, size: usize) -> Self {
// SAFETY: The caller guarantees `ptr` and `size` are valid.
Self { buffer: unsafe { core::slice::from_raw_parts(ptr, size) } }
}
// Construct a BinaryReader from a valid structure with a size() method.
pub fn from_variable_sized<T>(header: &'a T) -> Self
where
T: VariableSized,
{
let size = header.size();
let ptr = header as *const T as *const u8;
// SAFETY: `header` is a valid reference, and `T::size()` is assumed
// to return the size of the memory backing it.
unsafe { Self::from_ptr(ptr, size) }
}
// Construct a BinaryReader from a class with a size() method, skipping the header T.
pub fn from_payload_of_struct<T>(header: &'a T) -> Self
where
T: VariableSized,
{
let size = header.size();
let struct_size = core::mem::size_of::<T>();
if size < struct_size {
return Self { buffer: &[] };
}
let ptr = header as *const T as *const u8;
// SAFETY: `header` is a valid reference, and `T::size()` is assumed
// to return the size of the memory backing it. We offset by `struct_size`
// which is safe because `size >= struct_size`.
unsafe { Self::from_ptr(ptr.add(struct_size), size - struct_size) }
}
// Read a fixed-length structure.
pub fn read_fixed_length<T>(&mut self) -> Option<&'a T>
where
T: zerocopy::FromBytes + zerocopy::Unaligned + zerocopy::Immutable + zerocopy::KnownLayout,
{
let size = core::mem::size_of::<T>();
if self.buffer.len() < size {
return None;
}
let (prefix, suffix) = self.buffer.split_at(size);
let r = zerocopy::Ref::<_, T>::from_bytes(prefix).ok()?;
self.buffer = suffix;
Some(zerocopy::Ref::into_ref(r))
}
// Read a variable length structure, where the size is determined by T::size().
pub fn read<T>(&mut self) -> Option<&'a T>
where
T: VariableSized
+ zerocopy::FromBytes
+ zerocopy::Unaligned
+ zerocopy::Immutable
+ zerocopy::KnownLayout,
{
let size = core::mem::size_of::<T>();
if self.buffer.len() < size {
return None;
}
let prefix = &self.buffer[..size];
let r = zerocopy::Ref::<_, T>::from_bytes(prefix).ok()?;
let val = zerocopy::Ref::into_ref(r);
let desired_size = val.size();
if desired_size < size || desired_size > self.buffer.len() {
return None;
}
self.buffer = &self.buffer[desired_size..];
Some(val)
}
// Discard the given number of bytes.
//
// Return true if the bytes could be discarded, or false if there are insufficient bytes.
pub fn skip_bytes(&mut self, bytes: usize) -> bool {
if self.buffer.len() < bytes {
return false;
}
self.buffer = &self.buffer[bytes..];
true
}
// Return true if all the bytes of the reader have been consumed.
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
}
// Convert a pointer to type |Src| to a pointer of type |Dest|, ensuring that the size of |Src|
// is valid.
//
// We require that the type |Dest| has a field |header| at offset 0 of type |Src|.
pub trait DowncastFrom<Src> {
/// # Safety
///
/// The caller must ensure that `src` actually points to a structure of type `Self`.
unsafe fn downcast_from(src: &Src) -> Option<&Self>;
}
#[derive(Copy, Clone, zerocopy::FromBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
#[repr(C, packed)]
// A "packed" type wraps a plain type, but instructs the compiler to treat it as unaligned data.
pub struct Unaligned<T>(pub T);
unsafe impl<T> zerocopy::Unaligned for Unaligned<T> {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
macro_rules! impl_downcast_from {
($Src:ty => $($Target:ty),+ $(,)?) => {
$(
impl $crate::binary_reader::DowncastFrom<$Src> for $Target {
unsafe fn downcast_from(src: &$Src) -> Option<&Self> {
if $crate::structures::VariableSized::size(src) < core::mem::size_of::<Self>() {
return None;
}
// SAFETY: The caller must ensure that `src` points to a block of memory
// of at least `src.size()` bytes.
let bytes = unsafe {
core::slice::from_raw_parts(
src as *const $Src as *const u8,
$crate::structures::VariableSized::size(src),
)
};
let bytes = &bytes[..core::mem::size_of::<Self>()];
let r = zerocopy::Ref::<_, Self>::from_bytes(bytes).ok()?;
Some(zerocopy::Ref::into_ref(r))
}
}
)+
};
}