use crate::error::{Error, Result};
pub(crate) struct Reader<'a> {
data: &'a [u8],
position: usize,
context: &'static str,
}
impl<'a> Reader<'a> {
pub(crate) fn new(data: &'a [u8], context: &'static str) -> Self {
Self {
data,
position: 0,
context,
}
}
pub(crate) fn at(data: &'a [u8], position: usize, context: &'static str) -> Result<Self> {
if position > data.len() {
return Err(Error::Format(format!(
"{context}: offset 0x{position:x} is past the end of the record"
)));
}
Ok(Self {
data,
position,
context,
})
}
pub(crate) const fn position(&self) -> usize {
self.position
}
pub(crate) fn remaining(&self) -> usize {
self.data.len().saturating_sub(self.position)
}
pub(crate) fn remaining_bytes(&self) -> &'a [u8] {
&self.data[self.position..]
}
pub(crate) fn read_u8(&mut self, field: &'static str) -> Result<u8> {
Ok(self.read_array::<1>(field)?[0])
}
pub(crate) fn read_u16(&mut self, field: &'static str) -> Result<u16> {
Ok(u16::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_i16(&mut self, field: &'static str) -> Result<i16> {
Ok(i16::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_u32(&mut self, field: &'static str) -> Result<u32> {
Ok(u32::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_i32(&mut self, field: &'static str) -> Result<i32> {
Ok(i32::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_u64(&mut self, field: &'static str) -> Result<u64> {
Ok(u64::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_i64(&mut self, field: &'static str) -> Result<i64> {
Ok(i64::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_f32(&mut self, field: &'static str) -> Result<f32> {
Ok(f32::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_f64(&mut self, field: &'static str) -> Result<f64> {
Ok(f64::from_le_bytes(self.read_array(field)?))
}
pub(crate) fn read_bytes(&mut self, length: usize, field: &'static str) -> Result<&'a [u8]> {
let start = self.position;
let end = start.checked_add(length).ok_or_else(|| {
Error::Format(format!(
"{}: {field} length overflows at offset 0x{start:x}",
self.context
))
})?;
let bytes = self.data.get(start..end).ok_or_else(|| {
Error::Format(format!(
"{}: truncated {field} at offset 0x{start:x} (need {length} bytes, have {})",
self.context,
self.remaining()
))
})?;
self.position = end;
Ok(bytes)
}
pub(crate) fn skip(&mut self, length: usize, field: &'static str) -> Result<()> {
self.read_bytes(length, field).map(|_| ())
}
pub(crate) fn read_utf16_u16(&mut self, field: &'static str) -> Result<String> {
self.read_utf16_u16_with_limit(field, usize::MAX)
}
pub(crate) fn read_utf16_u16_with_limit(
&mut self,
field: &'static str,
max_units: usize,
) -> Result<String> {
self.read_nullable_utf16_u16(field, max_units)?
.ok_or_else(|| {
Error::Format(format!(
"{}: {field} uses the null string sentinel",
self.context
))
})
}
pub(crate) fn read_nullable_utf16_u16(
&mut self,
field: &'static str,
max_units: usize,
) -> Result<Option<String>> {
let unit_count = usize::from(self.read_u16(field)?);
if unit_count == usize::from(u16::MAX) {
return Ok(None);
}
self.read_utf16_units(unit_count, field, max_units)
.map(Some)
}
pub(crate) fn read_utf16_u16_without_null_sentinel(
&mut self,
field: &'static str,
max_units: usize,
) -> Result<String> {
let unit_count = usize::from(self.read_u16(field)?);
self.read_utf16_units(unit_count, field, max_units)
}
pub(crate) fn read_utf16_u32(
&mut self,
field: &'static str,
max_units: usize,
) -> Result<String> {
let unit_count = usize::try_from(self.read_u32(field)?)
.map_err(|_| Error::Format(format!("{}: {field} is too long", self.context)))?;
self.read_utf16_units(unit_count, field, max_units)
}
pub(crate) fn read_utf16_units(
&mut self,
unit_count: usize,
field: &'static str,
max_units: usize,
) -> Result<String> {
if unit_count > max_units {
return Err(Error::LimitExceeded {
resource: "text characters",
limit: u64::try_from(max_units).unwrap_or(u64::MAX),
actual: u64::try_from(unit_count).unwrap_or(u64::MAX),
});
}
let byte_count = unit_count
.checked_mul(2)
.ok_or_else(|| Error::Format(format!("{}: {field} length overflows", self.context)))?;
decode_utf16(self.read_bytes(byte_count, field)?, self.context, field)
}
pub(crate) fn read_utf8_u16(&mut self, field: &'static str) -> Result<String> {
let byte_count = usize::from(self.read_u16(field)?);
let bytes = self.read_bytes(byte_count, field)?;
std::str::from_utf8(bytes)
.map(str::to_owned)
.map_err(|_| Error::Format(format!("{}: invalid UTF-8 in {field}", self.context)))
}
fn read_array<const N: usize>(&mut self, field: &'static str) -> Result<[u8; N]> {
self.read_bytes(N, field)?
.try_into()
.map_err(|_| Error::Format(format!("{}: invalid {field}", self.context)))
}
}
fn decode_utf16(bytes: &[u8], context: &'static str, field: &'static str) -> Result<String> {
let units = bytes
.as_chunks::<2>()
.0
.iter()
.map(|pair| u16::from_le_bytes(*pair))
.collect::<Vec<_>>();
String::from_utf16(&units)
.map_err(|_| Error::Format(format!("{context}: invalid UTF-16 in {field}")))
}
#[cfg(test)]
mod tests {
use super::Reader;
#[test]
fn reports_the_field_and_offset_for_truncated_data() {
let mut reader = Reader::new(&[0x01, 0x02], "test record");
let error = reader.read_u32("declared size").unwrap_err();
assert_eq!(
error.to_string(),
"format error: test record: truncated declared size at offset 0x0 (need 4 bytes, have 2)"
);
}
#[test]
fn decodes_length_prefixed_utf16() {
let mut reader = Reader::new(&[2, 0, b'A', 0, 0x3d, 0xd8], "test record");
let error = reader.read_utf16_u16("name").unwrap_err();
assert_eq!(
error.to_string(),
"format error: test record: invalid UTF-16 in name"
);
}
}