use crate::error::Error;
#[inline]
pub(crate) fn read_u16_le(data: &[u8], offset: usize) -> Result<u16, Error> {
let bytes = read_array::<2>(data, offset)?;
Ok(u16::from_le_bytes(bytes))
}
#[inline]
pub(crate) fn read_u32_le(data: &[u8], offset: usize) -> Result<u32, Error> {
let bytes = read_array::<4>(data, offset)?;
Ok(u32::from_le_bytes(bytes))
}
#[inline]
pub(crate) fn read_i16_le(data: &[u8], offset: usize) -> Result<i16, Error> {
let bytes = read_array::<2>(data, offset)?;
Ok(i16::from_le_bytes(bytes))
}
#[inline]
pub(crate) fn read_i32_le(data: &[u8], offset: usize) -> Result<i32, Error> {
let bytes = read_array::<4>(data, offset)?;
Ok(i32::from_le_bytes(bytes))
}
#[inline]
fn read_array<const N: usize>(data: &[u8], offset: usize) -> Result<[u8; N], Error> {
let end = offset.checked_add(N).ok_or(Error::ArithmeticOverflow {
context: "read_array offset",
})?;
let slice = data.get(offset..end).ok_or(Error::Truncated {
needed: N,
available: data.len().saturating_sub(offset),
})?;
<[u8; N]>::try_from(slice).map_err(|_| Error::Truncated {
needed: N,
available: slice.len(),
})
}
pub(crate) fn read_cstr(data: &[u8], offset: usize) -> Result<&[u8], Error> {
let rest = data.get(offset..).ok_or(Error::Truncated {
needed: 0,
available: data.len().saturating_sub(offset),
})?;
let cstr_len = rest.iter().position(|&b| b == 0).unwrap_or(rest.len());
rest.get(..cstr_len).ok_or(Error::Truncated {
needed: cstr_len,
available: rest.len(),
})
}
#[inline]
pub(crate) fn read_fixed(data: &[u8], offset: usize, len: usize) -> Result<&[u8], Error> {
let end = offset.checked_add(len).ok_or(Error::ArithmeticOverflow {
context: "read_fixed offset",
})?;
data.get(offset..end).ok_or(Error::Truncated {
needed: len,
available: data.len().saturating_sub(offset),
})
}
pub(crate) fn read_fixed_cstr(data: &[u8], offset: usize, len: usize) -> Result<&[u8], Error> {
let region = read_fixed(data, offset, len)?;
let cstr_len = region.iter().position(|&b| b == 0).unwrap_or(region.len());
region.get(..cstr_len).ok_or(Error::Truncated {
needed: cstr_len,
available: region.len(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_u16_le() {
let data = [0x34, 0x12];
assert_eq!(read_u16_le(&data, 0).unwrap(), 0x1234);
}
#[test]
fn test_read_u16_le_offset() {
let data = [0xFF, 0x34, 0x12, 0xFF];
assert_eq!(read_u16_le(&data, 1).unwrap(), 0x1234);
}
#[test]
fn test_read_u32_le() {
let data = [0x78, 0x56, 0x34, 0x12];
assert_eq!(read_u32_le(&data, 0).unwrap(), 0x1234_5678);
}
#[test]
fn test_read_u32_le_offset() {
let data = [0xFF, 0x78, 0x56, 0x34, 0x12];
assert_eq!(read_u32_le(&data, 1).unwrap(), 0x1234_5678);
}
#[test]
fn test_read_i16_le_positive() {
let data = [0x05, 0x00];
assert_eq!(read_i16_le(&data, 0).unwrap(), 5);
}
#[test]
fn test_read_i16_le_negative() {
let data = [0x70, 0xFF];
assert_eq!(read_i16_le(&data, 0).unwrap(), -144);
}
#[test]
fn test_read_i32_le() {
let data = [0xFE, 0xFF, 0xFF, 0xFF];
assert_eq!(read_i32_le(&data, 0).unwrap(), -2);
}
#[test]
fn test_read_cstr() {
let data = b"hello\x00world";
assert_eq!(read_cstr(data, 0).unwrap(), b"hello");
}
#[test]
fn test_read_cstr_no_null() {
let data = b"hello";
assert_eq!(read_cstr(data, 0).unwrap(), b"hello");
}
#[test]
fn test_read_cstr_offset() {
let data = b"\x00\x00hello\x00";
assert_eq!(read_cstr(data, 2).unwrap(), b"hello");
}
#[test]
fn test_read_cstr_empty() {
let data = b"\x00rest";
assert_eq!(read_cstr(data, 0).unwrap(), b"");
}
#[test]
fn test_read_fixed() {
let data = b"abcdef";
assert_eq!(read_fixed(data, 1, 3).unwrap(), b"bcd");
}
#[test]
fn test_read_fixed_cstr() {
let data = b"hi\x00\x00\x00rest";
assert_eq!(read_fixed_cstr(data, 0, 5).unwrap(), b"hi");
}
#[test]
fn test_read_fixed_cstr_full() {
let data = b"hello";
assert_eq!(read_fixed_cstr(data, 0, 5).unwrap(), b"hello");
}
#[test]
fn test_read_u16_le_out_of_bounds() {
let data = [0x01];
assert!(matches!(
read_u16_le(&data, 0),
Err(Error::Truncated {
needed: 2,
available: 1
})
));
}
#[test]
fn test_read_u32_le_out_of_bounds() {
let data = [0x01, 0x02, 0x03];
assert!(matches!(
read_u32_le(&data, 0),
Err(Error::Truncated {
needed: 4,
available: 3
})
));
}
#[test]
fn test_read_u32_le_offset_overflow() {
let data = [0x01, 0x02, 0x03, 0x04];
assert!(matches!(
read_u32_le(&data, usize::MAX - 1),
Err(Error::ArithmeticOverflow { .. })
));
}
#[test]
fn test_read_cstr_offset_past_end() {
let data = b"hi";
assert!(read_cstr(data, 5).is_err());
}
#[test]
fn test_read_fixed_overflow() {
let data = b"abc";
assert!(matches!(
read_fixed(data, 1, usize::MAX),
Err(Error::ArithmeticOverflow { .. })
));
}
}