Skip to main content

graph_r/
bytesio.rs

1// Author: spicyzhug@gmail.com
2// Copyright: lituus-io, all rights reserved
3//
4//! Little-endian byte codec shared by the base and WAL formats. All readers
5//! return [`Error::Format`] rather than panicking on truncated or hostile
6//! input, and every length-driven preallocation is capped by
7//! [`Reader::remaining`] — the two rules the fuzz targets enforce.
8
9use crate::error::{Error, Result};
10
11/// Round `n` up to the next multiple of `align` (power of two).
12#[must_use]
13#[allow(clippy::manual_div_ceil)] // alignment idiom, kept explicit
14pub fn align_up(n: usize, align: usize) -> usize {
15    (n + align - 1) & !(align - 1)
16}
17
18/// Append helpers (writers never fail).
19pub fn put_u8(out: &mut Vec<u8>, v: u8) {
20    out.push(v);
21}
22/// Append a `u16` LE.
23pub fn put_u16(out: &mut Vec<u8>, v: u16) {
24    out.extend_from_slice(&v.to_le_bytes());
25}
26/// Append a `u32` LE.
27pub fn put_u32(out: &mut Vec<u8>, v: u32) {
28    out.extend_from_slice(&v.to_le_bytes());
29}
30/// Append a `u64` LE.
31pub fn put_u64(out: &mut Vec<u8>, v: u64) {
32    out.extend_from_slice(&v.to_le_bytes());
33}
34/// Append a `u16`-length-prefixed UTF-8 string (caller guarantees `<= u16::MAX`).
35pub fn put_str(out: &mut Vec<u8>, s: &str) {
36    debug_assert!(u16::try_from(s.len()).is_ok());
37    put_u16(out, s.len() as u16);
38    out.extend_from_slice(s.as_bytes());
39}
40/// Append a `u32`-length-prefixed UTF-8 string (large values: SQL, text).
41pub fn put_blob(out: &mut Vec<u8>, s: &str) {
42    put_len_prefixed(out, s.as_bytes());
43}
44/// Append `u32`-length-prefixed bytes.
45pub fn put_len_prefixed(out: &mut Vec<u8>, bytes: &[u8]) {
46    put_u32(out, u32::try_from(bytes.len()).unwrap_or(u32::MAX));
47    out.extend_from_slice(bytes);
48}
49/// Append an `f32` LE.
50pub fn put_f32(out: &mut Vec<u8>, v: f32) {
51    put_u32(out, v.to_bits());
52}
53/// Append an unsigned LEB128 varint.
54pub fn put_varint(out: &mut Vec<u8>, mut v: u64) {
55    loop {
56        let byte = (v & 0x7f) as u8;
57        v >>= 7;
58        if v == 0 {
59            out.push(byte);
60            return;
61        }
62        out.push(byte | 0x80);
63    }
64}
65
66/// Read a `u16` at a known-good offset (caller has already bounds-checked).
67#[must_use]
68pub fn get_u16(bytes: &[u8], off: usize) -> u16 {
69    u16::from_le_bytes([bytes[off], bytes[off + 1]])
70}
71/// Read a `u32` at a known-good offset.
72#[must_use]
73pub fn get_u32(bytes: &[u8], off: usize) -> u32 {
74    u32::from_le_bytes([bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]])
75}
76/// Read a `u64` at a known-good offset.
77#[must_use]
78pub fn get_u64(bytes: &[u8], off: usize) -> u64 {
79    let mut b = [0u8; 8];
80    b.copy_from_slice(&bytes[off..off + 8]);
81    u64::from_le_bytes(b)
82}
83
84/// A bounds-checked forward reader over untrusted bytes.
85#[derive(Debug)]
86pub struct Reader<'a> {
87    bytes: &'a [u8],
88    pos: usize,
89}
90
91impl<'a> Reader<'a> {
92    /// Start reading at the beginning of `bytes`.
93    #[must_use]
94    pub fn new(bytes: &'a [u8]) -> Self {
95        Self { bytes, pos: 0 }
96    }
97
98    /// Bytes not yet consumed. Decoders cap every `Vec::with_capacity` by
99    /// this value so a hostile length field cannot force an allocation
100    /// larger than the input itself.
101    #[must_use]
102    pub fn remaining(&self) -> usize {
103        self.bytes.len() - self.pos
104    }
105
106    fn take(&mut self, n: usize) -> Result<&'a [u8]> {
107        let end =
108            self.pos.checked_add(n).filter(|&e| e <= self.bytes.len()).ok_or_else(|| {
109                Error::format(format!("truncated: need {n} bytes at {}", self.pos))
110            })?;
111        let out = &self.bytes[self.pos..end];
112        self.pos = end;
113        Ok(out)
114    }
115
116    /// Read one byte.
117    pub fn u8(&mut self) -> Result<u8> {
118        Ok(self.take(1)?[0])
119    }
120    /// Read a `u16` LE.
121    pub fn u16(&mut self) -> Result<u16> {
122        let b = self.take(2)?;
123        Ok(u16::from_le_bytes([b[0], b[1]]))
124    }
125    /// Read a `u32` LE.
126    pub fn u32(&mut self) -> Result<u32> {
127        let b = self.take(4)?;
128        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
129    }
130    /// Read a `u64` LE.
131    pub fn u64(&mut self) -> Result<u64> {
132        let b = self.take(8)?;
133        let mut a = [0u8; 8];
134        a.copy_from_slice(b);
135        Ok(u64::from_le_bytes(a))
136    }
137    /// Read an `f32` LE.
138    pub fn f32(&mut self) -> Result<f32> {
139        Ok(f32::from_bits(self.u32()?))
140    }
141    /// Read `u32`-length-prefixed bytes (borrowed).
142    pub fn len_prefixed(&mut self) -> Result<&'a [u8]> {
143        let len = self.u32()? as usize;
144        self.take(len)
145    }
146    /// Read `n` raw bytes.
147    pub fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
148        self.take(n)
149    }
150    /// Read a `u16`-length-prefixed UTF-8 string.
151    pub fn str(&mut self) -> Result<&'a str> {
152        let len = usize::from(self.u16()?);
153        let raw = self.take(len)?;
154        std::str::from_utf8(raw).map_err(|_| Error::format("invalid utf-8 in string"))
155    }
156    /// Read a `u32`-length-prefixed UTF-8 string.
157    pub fn blob(&mut self) -> Result<&'a str> {
158        let raw = self.len_prefixed()?;
159        std::str::from_utf8(raw).map_err(|_| Error::format("invalid utf-8 in blob"))
160    }
161    /// Read an unsigned LEB128 varint (max 10 bytes).
162    pub fn varint(&mut self) -> Result<u64> {
163        let mut v: u64 = 0;
164        for shift in (0..64).step_by(7) {
165            let byte = self.u8()?;
166            v |= u64::from(byte & 0x7f) << shift;
167            if byte & 0x80 == 0 {
168                return Ok(v);
169            }
170        }
171        Err(Error::format("varint overlong"))
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn round_trips_all_widths() {
181        let mut out = Vec::new();
182        put_u8(&mut out, 7);
183        put_u16(&mut out, 300);
184        put_u32(&mut out, 70_000);
185        put_u64(&mut out, u64::MAX - 1);
186        put_str(&mut out, "héllo");
187        put_varint(&mut out, 0);
188        put_varint(&mut out, 127);
189        put_varint(&mut out, 128);
190        put_varint(&mut out, u64::MAX);
191
192        let mut r = Reader::new(&out);
193        assert_eq!(r.u8().unwrap(), 7);
194        assert_eq!(r.u16().unwrap(), 300);
195        assert_eq!(r.u32().unwrap(), 70_000);
196        assert_eq!(r.u64().unwrap(), u64::MAX - 1);
197        assert_eq!(r.str().unwrap(), "héllo");
198        assert_eq!(r.varint().unwrap(), 0);
199        assert_eq!(r.varint().unwrap(), 127);
200        assert_eq!(r.varint().unwrap(), 128);
201        assert_eq!(r.varint().unwrap(), u64::MAX);
202        assert_eq!(r.remaining(), 0);
203    }
204
205    #[test]
206    fn truncation_is_an_error_never_a_panic() {
207        let mut out = Vec::new();
208        put_str(&mut out, "abcdef");
209        for cut in 0..out.len() {
210            let mut r = Reader::new(&out[..cut]);
211            assert!(r.str().is_err() || cut >= out.len());
212        }
213        let mut r = Reader::new(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
214        assert!(r.varint().is_err(), "overlong varint rejected");
215    }
216
217    #[test]
218    fn invalid_utf8_is_a_format_error() {
219        let mut out = Vec::new();
220        put_u16(&mut out, 2);
221        out.extend_from_slice(&[0xff, 0xfe]);
222        assert!(Reader::new(&out).str().is_err());
223    }
224
225    #[test]
226    fn align_up_works() {
227        assert_eq!(align_up(0, 64), 0);
228        assert_eq!(align_up(1, 64), 64);
229        assert_eq!(align_up(64, 64), 64);
230        assert_eq!(align_up(65, 64), 128);
231    }
232}