use crate::convert::is_undefined_addr;
use crate::error::FormatError;
#[inline]
pub(crate) fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
match offset.checked_add(needed) {
Some(end) if end <= data.len() => Ok(()),
_ => Err(FormatError::UnexpectedEof {
expected: offset.saturating_add(needed),
available: data.len(),
}),
}
}
#[inline]
fn read_le(data: &[u8], pos: usize, width: u8) -> u64 {
match width {
1 => u64::from(data[pos]),
2 => u64::from(u16::from_le_bytes([data[pos], data[pos + 1]])),
4 => u64::from(u32::from_le_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
])),
8 => u64::from_le_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
data[pos + 4],
data[pos + 5],
data[pos + 6],
data[pos + 7],
]),
_ => {
debug_assert!(false, "width validated by the caller");
0
}
}
}
#[inline]
pub(crate) fn read_offset(data: &[u8], pos: usize, offset_size: u8) -> Result<u64, FormatError> {
if !matches!(offset_size, 2 | 4 | 8) {
return Err(FormatError::InvalidOffsetSize(offset_size));
}
ensure_len(data, pos, offset_size as usize)?;
Ok(read_le(data, pos, offset_size))
}
#[inline]
pub(crate) fn read_length(data: &[u8], pos: usize, length_size: u8) -> Result<u64, FormatError> {
if !matches!(length_size, 2 | 4 | 8) {
return Err(FormatError::InvalidLengthSize(length_size));
}
ensure_len(data, pos, length_size as usize)?;
Ok(read_le(data, pos, length_size))
}
#[inline]
pub(crate) fn read_uint_width(data: &[u8], pos: usize, width: u8) -> Result<u64, FormatError> {
if !matches!(width, 1 | 2 | 4 | 8) {
return Err(FormatError::InvalidOffsetSize(width));
}
ensure_len(data, pos, width as usize)?;
Ok(read_le(data, pos, width))
}
#[inline]
pub(crate) fn read_optional_offset(
data: &[u8],
pos: usize,
offset_size: u8,
) -> Result<Option<u64>, FormatError> {
let addr = read_offset(data, pos, offset_size)?;
if is_undefined_addr(addr, offset_size) {
Ok(None)
} else {
Ok(Some(addr))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_len_accepts_an_exact_fit_and_rejects_one_byte_more() {
let data = [0u8; 8];
assert!(ensure_len(&data, 4, 4).is_ok());
assert!(ensure_len(&data, 4, 5).is_err());
assert!(ensure_len(&data, 8, 0).is_ok());
}
#[test]
fn ensure_len_reports_a_wrapping_offset_instead_of_admitting_it() {
let data = [0u8; 16];
let err = ensure_len(&data, usize::MAX - 1, 8).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
}
#[test]
fn the_eof_payload_reports_what_was_wanted_and_what_was_there() {
let data = [0u8; 8];
assert_eq!(
ensure_len(&data, 6, 4).unwrap_err(),
FormatError::UnexpectedEof {
expected: 10,
available: 8,
}
);
assert_eq!(
ensure_len(&data, usize::MAX - 1, 8).unwrap_err(),
FormatError::UnexpectedEof {
expected: usize::MAX,
available: 8,
}
);
assert_eq!(
read_offset(&data, 6, 4).unwrap_err(),
FormatError::UnexpectedEof {
expected: 10,
available: 8,
}
);
}
#[test]
fn ensure_len_of_nothing_past_the_end_is_still_out_of_range() {
let data = [0u8; 4];
assert!(ensure_len(&data, 4, 0).is_ok());
assert!(ensure_len(&data, 5, 0).is_err());
}
#[test]
fn every_helper_reads_every_width_it_accepts() {
let data = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
for (width, expected) in [
(2u8, 0x2211u64),
(4, 0x4433_2211),
(8, 0x8877_6655_4433_2211),
] {
assert_eq!(read_offset(&data, 0, width).unwrap(), expected);
assert_eq!(read_length(&data, 0, width).unwrap(), expected);
assert_eq!(read_uint_width(&data, 0, width).unwrap(), expected);
}
assert_eq!(read_uint_width(&data, 0, 1).unwrap(), 0x11);
}
#[test]
fn each_supported_width_reads_little_endian() {
let data = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
assert_eq!(read_offset(&data, 0, 2).unwrap(), 0x2211);
assert_eq!(read_offset(&data, 0, 4).unwrap(), 0x4433_2211);
assert_eq!(read_offset(&data, 0, 8).unwrap(), 0x8877_6655_4433_2211);
assert_eq!(read_length(&data, 0, 4).unwrap(), 0x4433_2211);
assert_eq!(read_uint_width(&data, 0, 1).unwrap(), 0x11);
assert_eq!(read_offset(&data, 2, 2).unwrap(), 0x4433);
}
#[test]
fn a_file_address_of_width_one_is_refused_where_a_flag_width_is_not() {
let data = [0xABu8; 8];
assert_eq!(
read_offset(&data, 0, 1).unwrap_err(),
FormatError::InvalidOffsetSize(1)
);
assert_eq!(read_uint_width(&data, 0, 1).unwrap(), 0xAB);
}
#[test]
fn an_unsupported_width_names_the_field_it_came_from() {
let data = [0u8; 8];
assert_eq!(
read_offset(&data, 0, 3).unwrap_err(),
FormatError::InvalidOffsetSize(3)
);
assert_eq!(
read_length(&data, 0, 3).unwrap_err(),
FormatError::InvalidLengthSize(3)
);
assert_eq!(
read_uint_width(&data, 0, 3).unwrap_err(),
FormatError::InvalidOffsetSize(3)
);
}
#[test]
fn width_is_validated_before_the_bound_is_checked() {
let err = read_offset(&[], 0, 3).unwrap_err();
assert_eq!(err, FormatError::InvalidOffsetSize(3));
}
#[test]
fn the_all_ones_sentinel_reads_as_none_at_each_width() {
let ones = [0xFFu8; 8];
assert_eq!(read_optional_offset(&ones, 0, 2).unwrap(), None);
assert_eq!(read_optional_offset(&ones, 0, 4).unwrap(), None);
assert_eq!(read_optional_offset(&ones, 0, 8).unwrap(), None);
let mut nearly = [0xFFu8; 8];
nearly[0] = 0xFE;
assert_eq!(
read_optional_offset(&nearly, 0, 8).unwrap(),
Some(0xFFFF_FFFF_FFFF_FFFE)
);
}
#[test]
fn the_sentinel_is_the_width_the_caller_asked_for() {
let mut data = [0u8; 8];
data[0] = 0xFF;
data[1] = 0xFF;
assert_eq!(read_optional_offset(&data, 0, 2).unwrap(), None);
assert_eq!(read_optional_offset(&data, 0, 8).unwrap(), Some(0xFFFF));
}
#[test]
fn an_unreadable_address_is_an_error_rather_than_a_none() {
let ones = [0xFFu8; 8];
assert!(matches!(
read_optional_offset(&ones, 4, 8),
Err(FormatError::UnexpectedEof { .. })
));
assert_eq!(
read_optional_offset(&ones, 0, 3).unwrap_err(),
FormatError::InvalidOffsetSize(3)
);
}
}