use crate::error::{FasError, Result};
pub fn u8_at(data: &[u8], off: usize) -> Result<u8> {
data.get(off).copied().ok_or(FasError::Truncated("u8"))
}
pub fn u16_at(data: &[u8], off: usize) -> Result<u16> {
let b: [u8; 2] = data
.get(off..off + 2)
.ok_or(FasError::Truncated("u16"))?
.try_into()
.map_err(|_| FasError::Truncated("u16"))?;
Ok(u16::from_le_bytes(b))
}
pub fn u32_at(data: &[u8], off: usize) -> Result<u32> {
let b: [u8; 4] = data
.get(off..off + 4)
.ok_or(FasError::Truncated("u32"))?
.try_into()
.map_err(|_| FasError::Truncated("u32"))?;
Ok(u32::from_le_bytes(b))
}
pub fn u64_at(data: &[u8], off: usize) -> Result<u64> {
let b: [u8; 8] = data
.get(off..off + 8)
.ok_or(FasError::Truncated("u64"))?
.try_into()
.map_err(|_| FasError::Truncated("u64"))?;
Ok(u64::from_le_bytes(b))
}
pub fn slice_at(data: &[u8], off: u32, len: u32) -> Result<&[u8]> {
let start = off as usize;
let end = start
.checked_add(len as usize)
.ok_or(FasError::Truncated("slice overflow"))?;
data.get(start..end).ok_or(FasError::Truncated("slice"))
}
pub fn cstring_at(data: &[u8], off: usize) -> Result<&str> {
let tail = data.get(off..).ok_or(FasError::Truncated("cstring"))?;
let n = tail
.iter()
.position(|&b| b == 0)
.ok_or(FasError::BadString("unterminated"))?;
std::str::from_utf8(&tail[..n]).map_err(|_| FasError::BadString("utf8"))
}
pub fn pascal_at(data: &[u8], off: usize) -> Result<&str> {
let len = u8_at(data, off)? as usize;
let bytes = data
.get(off + 1..off + 1 + len)
.ok_or(FasError::Truncated("pascal"))?;
std::str::from_utf8(bytes).map_err(|_| FasError::BadString("pascal utf8"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_scalars() {
let d = [0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00];
assert_eq!(u8_at(&d, 0).unwrap(), 0x78);
assert_eq!(u16_at(&d, 0).unwrap(), 0x5678);
assert_eq!(u32_at(&d, 0).unwrap(), 0x12345678);
}
#[test]
fn truncated_is_err() {
assert!(u32_at(&[1, 2], 0).is_err());
assert!(cstring_at(b"ab", 0).is_err());
}
}