Skip to main content

flashraw_core/
bytes.rs

1//! Bounds-checked, zero-copy byte access.
2//!
3//! Every container parser works on `&[u8]` slices of the (usually memory-mapped)
4//! input. All offset arithmetic is checked: a malformed offset yields
5//! [`Error::CorruptFile`] instead of a panic or out-of-bounds read.
6
7use crate::{Error, Result};
8
9/// Byte order of multi-byte integers.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum ByteOrder {
13    Little,
14    Big,
15}
16
17impl ByteOrder {
18    #[inline(always)]
19    pub fn u16(self, b: [u8; 2]) -> u16 {
20        match self {
21            ByteOrder::Little => u16::from_le_bytes(b),
22            ByteOrder::Big => u16::from_be_bytes(b),
23        }
24    }
25    #[inline(always)]
26    pub fn u32(self, b: [u8; 4]) -> u32 {
27        match self {
28            ByteOrder::Little => u32::from_le_bytes(b),
29            ByteOrder::Big => u32::from_be_bytes(b),
30        }
31    }
32    #[inline(always)]
33    pub fn u64(self, b: [u8; 8]) -> u64 {
34        match self {
35            ByteOrder::Little => u64::from_le_bytes(b),
36            ByteOrder::Big => u64::from_be_bytes(b),
37        }
38    }
39}
40
41/// Return `data[offset..offset+len]` or a corruption error.
42#[inline]
43pub fn slice_at(data: &[u8], offset: usize, len: usize) -> Result<&[u8]> {
44    offset.checked_add(len).and_then(|end| data.get(offset..end)).ok_or_else(|| {
45        Error::corrupt_at(format!("range of {len} bytes exceeds file size {}", data.len()), offset as u64)
46    })
47}
48
49/// Return `data[offset..]` clamped to at most `len` bytes. Used for payloads where
50/// vendors routinely declare slightly more bytes than the file contains (truncated
51/// final strips are a common real-world quirk).
52#[inline]
53pub fn slice_at_lenient(data: &[u8], offset: usize, len: usize) -> Result<&[u8]> {
54    if offset >= data.len() {
55        return Err(Error::corrupt_at("payload offset beyond end of file", offset as u64));
56    }
57    let end = offset.saturating_add(len).min(data.len());
58    Ok(&data[offset..end])
59}
60
61#[inline(always)]
62fn arr<const N: usize>(data: &[u8], offset: usize) -> Result<[u8; N]> {
63    match offset.checked_add(N).and_then(|end| data.get(offset..end)) {
64        Some(s) => {
65            let mut a = [0u8; N];
66            a.copy_from_slice(s);
67            Ok(a)
68        }
69        None => Err(Error::corrupt_at("read beyond end of data", offset as u64)),
70    }
71}
72
73/// Random-access endian-aware reads on a byte slice.
74pub trait ReadAt {
75    fn u8_at(&self, offset: usize) -> Result<u8>;
76    fn u16_at(&self, order: ByteOrder, offset: usize) -> Result<u16>;
77    fn u32_at(&self, order: ByteOrder, offset: usize) -> Result<u32>;
78    fn u64_at(&self, order: ByteOrder, offset: usize) -> Result<u64>;
79    fn u16le(&self, offset: usize) -> Result<u16> {
80        self.u16_at(ByteOrder::Little, offset)
81    }
82    fn u16be(&self, offset: usize) -> Result<u16> {
83        self.u16_at(ByteOrder::Big, offset)
84    }
85    fn u32le(&self, offset: usize) -> Result<u32> {
86        self.u32_at(ByteOrder::Little, offset)
87    }
88    fn u32be(&self, offset: usize) -> Result<u32> {
89        self.u32_at(ByteOrder::Big, offset)
90    }
91}
92
93impl ReadAt for [u8] {
94    #[inline]
95    fn u8_at(&self, offset: usize) -> Result<u8> {
96        self.get(offset).copied().ok_or_else(|| Error::corrupt_at("read beyond end of data", offset as u64))
97    }
98    #[inline]
99    fn u16_at(&self, order: ByteOrder, offset: usize) -> Result<u16> {
100        Ok(order.u16(arr(self, offset)?))
101    }
102    #[inline]
103    fn u32_at(&self, order: ByteOrder, offset: usize) -> Result<u32> {
104        Ok(order.u32(arr(self, offset)?))
105    }
106    #[inline]
107    fn u64_at(&self, order: ByteOrder, offset: usize) -> Result<u64> {
108        Ok(order.u64(arr(self, offset)?))
109    }
110}
111
112/// A sequential cursor over a byte slice.
113#[derive(Debug, Clone)]
114pub struct Cursor<'a> {
115    data: &'a [u8],
116    pos: usize,
117    order: ByteOrder,
118}
119
120impl<'a> Cursor<'a> {
121    pub fn new(data: &'a [u8], order: ByteOrder) -> Self {
122        Cursor { data, pos: 0, order }
123    }
124    pub fn at(data: &'a [u8], pos: usize, order: ByteOrder) -> Self {
125        Cursor { data, pos, order }
126    }
127    #[inline]
128    pub fn data(&self) -> &'a [u8] {
129        self.data
130    }
131    #[inline]
132    pub fn pos(&self) -> usize {
133        self.pos
134    }
135    #[inline]
136    pub fn order(&self) -> ByteOrder {
137        self.order
138    }
139    pub fn set_order(&mut self, order: ByteOrder) {
140        self.order = order;
141    }
142    pub fn seek(&mut self, pos: usize) {
143        self.pos = pos;
144    }
145    #[inline]
146    pub fn remaining(&self) -> usize {
147        self.data.len().saturating_sub(self.pos)
148    }
149    pub fn skip(&mut self, n: usize) -> Result<()> {
150        let np = self.pos.checked_add(n).ok_or_else(|| Error::corrupt("cursor overflow"))?;
151        if np > self.data.len() {
152            return Err(Error::corrupt_at("skip beyond end of data", self.pos as u64));
153        }
154        self.pos = np;
155        Ok(())
156    }
157    #[inline]
158    pub fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
159        let s = slice_at(self.data, self.pos, n)?;
160        self.pos += n;
161        Ok(s)
162    }
163    #[inline]
164    pub fn u8(&mut self) -> Result<u8> {
165        let v = self.data.u8_at(self.pos)?;
166        self.pos += 1;
167        Ok(v)
168    }
169    #[inline]
170    pub fn u16(&mut self) -> Result<u16> {
171        let v = self.data.u16_at(self.order, self.pos)?;
172        self.pos += 2;
173        Ok(v)
174    }
175    #[inline]
176    pub fn u32(&mut self) -> Result<u32> {
177        let v = self.data.u32_at(self.order, self.pos)?;
178        self.pos += 4;
179        Ok(v)
180    }
181    #[inline]
182    pub fn u64(&mut self) -> Result<u64> {
183        let v = self.data.u64_at(self.order, self.pos)?;
184        self.pos += 8;
185        Ok(v)
186    }
187    #[inline]
188    pub fn i16(&mut self) -> Result<i16> {
189        Ok(self.u16()? as i16)
190    }
191    #[inline]
192    pub fn i32(&mut self) -> Result<i32> {
193        Ok(self.u32()? as i32)
194    }
195    pub fn u16be(&mut self) -> Result<u16> {
196        let v = self.data.u16be(self.pos)?;
197        self.pos += 2;
198        Ok(v)
199    }
200    pub fn u32be(&mut self) -> Result<u32> {
201        let v = self.data.u32be(self.pos)?;
202        self.pos += 4;
203        Ok(v)
204    }
205}
206
207/// Decode a fixed-length, NUL-padded ASCII field into a trimmed `String`.
208pub fn ascii_field(bytes: &[u8]) -> String {
209    let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
210    let s: String = bytes[..end].iter().map(|&b| if b.is_ascii() && b >= 0x20 { b as char } else { ' ' }).collect();
211    s.trim().to_string()
212}
213
214/// Find the first occurrence of `needle` in `hay` (simple, used for marker scans in headers only).
215pub fn find(hay: &[u8], needle: &[u8]) -> Option<usize> {
216    if needle.is_empty() || hay.len() < needle.len() {
217        return None;
218    }
219    hay.windows(needle.len()).position(|w| w == needle)
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn checked_reads() {
228        let d = [1u8, 2, 3, 4];
229        assert_eq!(d.u16le(0).unwrap(), 0x0201);
230        assert_eq!(d.u16be(2).unwrap(), 0x0304);
231        assert_eq!(d.u32be(0).unwrap(), 0x01020304);
232        assert!(d.u32le(1).is_err());
233        assert!(d.u16le(usize::MAX).is_err());
234        assert!(slice_at(&d, usize::MAX - 1, 4).is_err());
235        assert_eq!(slice_at_lenient(&d, 2, 100).unwrap(), &[3, 4]);
236    }
237
238    #[test]
239    fn cursor() {
240        let d = [0u8, 1, 0, 0, 0, 2];
241        let mut c = Cursor::new(&d, ByteOrder::Big);
242        assert_eq!(c.u16().unwrap(), 1);
243        assert_eq!(c.u32().unwrap(), 2);
244        assert!(c.u8().is_err());
245    }
246
247    #[test]
248    fn ascii() {
249        assert_eq!(ascii_field(b"NIKON \0garbage"), "NIKON");
250    }
251}