use crate::abi;
use crate::parse::ParseError;
macro_rules! safe_from {
( $self:ident, $typ:ty, $off:ident, $data:ident) => {{
const SIZE: usize = core::mem::size_of::<$typ>();
let end = (*$off)
.checked_add(SIZE)
.ok_or(ParseError::IntegerOverflow)?;
let buf: [u8; SIZE] = $data
.get(*$off..end)
.ok_or(ParseError::SliceReadError((*$off, end)))?
.try_into()?;
*$off = end;
if $self.is_little() {
Ok(<$typ>::from_le_bytes(buf))
} else {
Ok(<$typ>::from_be_bytes(buf))
}
}};
}
pub trait EndianParse: Clone + Copy + Default + PartialEq + Eq {
fn parse_u8_at(self, offset: &mut usize, data: &[u8]) -> Result<u8, ParseError> {
safe_from!(self, u8, offset, data)
}
fn parse_u16_at(self, offset: &mut usize, data: &[u8]) -> Result<u16, ParseError> {
safe_from!(self, u16, offset, data)
}
fn parse_u32_at(self, offset: &mut usize, data: &[u8]) -> Result<u32, ParseError> {
safe_from!(self, u32, offset, data)
}
fn parse_u64_at(self, offset: &mut usize, data: &[u8]) -> Result<u64, ParseError> {
safe_from!(self, u64, offset, data)
}
fn parse_i32_at(self, offset: &mut usize, data: &[u8]) -> Result<i32, ParseError> {
safe_from!(self, i32, offset, data)
}
fn parse_i64_at(self, offset: &mut usize, data: &[u8]) -> Result<i64, ParseError> {
safe_from!(self, i64, offset, data)
}
fn from_ei_data(ei_data: u8) -> Result<Self, ParseError>;
fn is_little(self) -> bool;
#[inline(always)]
fn is_big(self) -> bool {
!self.is_little()
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum AnyEndian {
#[default]
Little,
Big,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct LittleEndian;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct BigEndian;
#[cfg(target_endian = "little")]
pub type NativeEndian = LittleEndian;
#[cfg(target_endian = "little")]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
pub const NativeEndian: LittleEndian = LittleEndian;
#[cfg(target_endian = "big")]
pub type NativeEndian = BigEndian;
#[cfg(target_endian = "big")]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
pub const NativeEndian: BigEndian = BigEndian;
impl EndianParse for LittleEndian {
fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
match ei_data {
abi::ELFDATA2LSB => Ok(LittleEndian),
_ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
}
}
#[inline(always)]
fn is_little(self) -> bool {
true
}
}
impl EndianParse for BigEndian {
fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
match ei_data {
abi::ELFDATA2MSB => Ok(BigEndian),
_ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
}
}
#[inline(always)]
fn is_little(self) -> bool {
false
}
}
impl EndianParse for AnyEndian {
fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
match ei_data {
abi::ELFDATA2LSB => Ok(AnyEndian::Little),
abi::ELFDATA2MSB => Ok(AnyEndian::Big),
_ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
}
}
#[inline(always)]
fn is_little(self) -> bool {
match self {
AnyEndian::Little => true,
AnyEndian::Big => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! parse_test {
( $endian:expr, $res_typ:ty, $method:ident, $expect:expr) => {{
let bytes = [
0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8,
];
let mut offset = 0;
let result = $endian.$method(&mut offset, &bytes).unwrap();
assert_eq!(result, $expect);
assert_eq!(offset, core::mem::size_of::<$res_typ>());
}};
}
macro_rules! fuzz_too_short_test {
( $endian:expr, $res_typ:ty, $method:ident) => {{
let bytes = [
0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8,
];
let size = core::mem::size_of::<$res_typ>();
for n in 0..size {
let buf = bytes.split_at(n).0.as_ref();
let mut offset: usize = 0;
let error = $endian
.$method(&mut offset, buf)
.expect_err("Expected an error, but parsed: ");
assert!(
matches!(error, ParseError::SliceReadError(_)),
"Unexpected Error type found: {error}"
);
}
}};
}
#[test]
fn parse_u8_at() {
parse_test!(LittleEndian, u8, parse_u8_at, 0x01u8);
parse_test!(BigEndian, u8, parse_u8_at, 0x01u8);
parse_test!(AnyEndian::Little, u8, parse_u8_at, 0x01u8);
parse_test!(AnyEndian::Big, u8, parse_u8_at, 0x01u8);
}
#[test]
fn parse_u16_at() {
parse_test!(LittleEndian, u16, parse_u16_at, 0x0201u16);
parse_test!(BigEndian, u16, parse_u16_at, 0x0102u16);
parse_test!(AnyEndian::Little, u16, parse_u16_at, 0x0201u16);
parse_test!(AnyEndian::Big, u16, parse_u16_at, 0x0102u16);
}
#[test]
fn parse_u32_at() {
parse_test!(LittleEndian, u32, parse_u32_at, 0x04030201u32);
parse_test!(BigEndian, u32, parse_u32_at, 0x01020304u32);
parse_test!(AnyEndian::Little, u32, parse_u32_at, 0x04030201u32);
parse_test!(AnyEndian::Big, u32, parse_u32_at, 0x01020304u32);
}
#[test]
fn parse_u64_at() {
parse_test!(LittleEndian, u64, parse_u64_at, 0x0807060504030201u64);
parse_test!(BigEndian, u64, parse_u64_at, 0x0102030405060708u64);
parse_test!(AnyEndian::Little, u64, parse_u64_at, 0x0807060504030201u64);
parse_test!(AnyEndian::Big, u64, parse_u64_at, 0x0102030405060708u64);
}
#[test]
fn parse_i32_at() {
parse_test!(LittleEndian, i32, parse_i32_at, 0x04030201i32);
parse_test!(BigEndian, i32, parse_i32_at, 0x01020304i32);
parse_test!(AnyEndian::Little, i32, parse_i32_at, 0x04030201i32);
parse_test!(AnyEndian::Big, i32, parse_i32_at, 0x01020304i32);
}
#[test]
fn parse_i64_at() {
parse_test!(LittleEndian, i64, parse_i64_at, 0x0807060504030201i64);
parse_test!(BigEndian, i64, parse_i64_at, 0x0102030405060708i64);
parse_test!(AnyEndian::Little, i64, parse_i64_at, 0x0807060504030201i64);
parse_test!(AnyEndian::Big, i64, parse_i64_at, 0x0102030405060708i64);
}
#[test]
fn fuzz_u8_too_short() {
fuzz_too_short_test!(LittleEndian, u8, parse_u8_at);
fuzz_too_short_test!(BigEndian, u8, parse_u8_at);
fuzz_too_short_test!(AnyEndian::Little, u8, parse_u8_at);
fuzz_too_short_test!(AnyEndian::Big, u8, parse_u8_at);
}
#[test]
fn fuzz_u16_too_short() {
fuzz_too_short_test!(LittleEndian, u16, parse_u16_at);
fuzz_too_short_test!(BigEndian, u16, parse_u16_at);
fuzz_too_short_test!(AnyEndian::Little, u16, parse_u16_at);
fuzz_too_short_test!(AnyEndian::Big, u16, parse_u16_at);
}
#[test]
fn fuzz_u32_too_short() {
fuzz_too_short_test!(LittleEndian, u32, parse_u32_at);
fuzz_too_short_test!(BigEndian, u32, parse_u32_at);
fuzz_too_short_test!(AnyEndian::Little, u32, parse_u32_at);
fuzz_too_short_test!(AnyEndian::Big, u32, parse_u32_at);
}
#[test]
fn fuzz_i32_too_short() {
fuzz_too_short_test!(LittleEndian, i32, parse_i32_at);
fuzz_too_short_test!(BigEndian, i32, parse_i32_at);
fuzz_too_short_test!(AnyEndian::Little, i32, parse_i32_at);
fuzz_too_short_test!(AnyEndian::Big, i32, parse_i32_at);
}
#[test]
fn fuzz_u64_too_short() {
fuzz_too_short_test!(LittleEndian, u64, parse_u64_at);
fuzz_too_short_test!(BigEndian, u64, parse_u64_at);
fuzz_too_short_test!(AnyEndian::Little, u64, parse_u64_at);
fuzz_too_short_test!(AnyEndian::Big, u64, parse_u64_at);
}
#[test]
fn fuzz_i64_too_short() {
fuzz_too_short_test!(LittleEndian, i64, parse_i64_at);
fuzz_too_short_test!(BigEndian, i64, parse_i64_at);
fuzz_too_short_test!(AnyEndian::Little, i64, parse_i64_at);
fuzz_too_short_test!(AnyEndian::Big, i64, parse_i64_at);
}
}