Skip to main content

n3_lib/
binary.rs

1//! Bounded little-endian binary primitives.
2
3use std::fmt;
4
5/// A malformed record or an invalid edited value, with its byte offset.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Error {
8    /// Byte position where the failure was detected.
9    pub offset: usize,
10    /// Human-readable field or validation context.
11    pub message: String,
12}
13
14impl fmt::Display for Error {
15    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(formatter, "{} at byte {}", self.message, self.offset)
17    }
18}
19impl std::error::Error for Error {}
20
21/// Result returned by bounded parsing and writing operations.
22pub type Result<T> = std::result::Result<T, Error>;
23
24/// Sequential reader borrowing a byte slice.
25pub struct Reader<'a> {
26    bytes: &'a [u8],
27    /// Current byte offset.
28    pub offset: usize,
29}
30
31impl<'a> Reader<'a> {
32    /// Borrow input without copying or mutating it.
33    pub fn new(bytes: &'a [u8]) -> Self {
34        Self { bytes, offset: 0 }
35    }
36    /// Unconsumed byte count.
37    pub fn remaining(&self) -> usize {
38        self.bytes.len() - self.offset
39    }
40    /// Read a bounded range, returning an error rather than panicking.
41    pub fn raw(&mut self, size: usize) -> Result<&'a [u8]> {
42        if size > self.remaining() {
43            return self.fail("Truncated record");
44        }
45        let start = self.offset;
46        self.offset += size;
47        Ok(&self.bytes[start..self.offset])
48    }
49    /// Construct a diagnostic at the current position.
50    pub fn fail<T>(&self, message: impl Into<String>) -> Result<T> {
51        Err(Error {
52            offset: self.offset,
53            message: message.into(),
54        })
55    }
56    /// Read an unsigned byte.
57    pub fn u8(&mut self) -> Result<u8> {
58        Ok(self.raw(1)?[0])
59    }
60    /// Read a signed byte.
61    pub fn i8(&mut self) -> Result<i8> {
62        Ok(self.u8()? as i8)
63    }
64    /// Read an unsigned 16-bit word.
65    pub fn u16(&mut self) -> Result<u16> {
66        let bytes = self.raw(2)?;
67        Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
68    }
69    /// Read a signed 16-bit word.
70    pub fn i16(&mut self) -> Result<i16> {
71        Ok(self.u16()? as i16)
72    }
73    /// Read an unsigned word, also usable for exact f32 storage bits.
74    pub fn u32(&mut self) -> Result<u32> {
75        let bytes = self.raw(4)?;
76        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
77    }
78    /// Read a signed 32-bit word.
79    pub fn i32(&mut self) -> Result<i32> {
80        Ok(self.u32()? as i32)
81    }
82    /// Read an IEEE-754 single-precision value.
83    pub fn f32(&mut self) -> Result<f32> {
84        Ok(f32::from_bits(self.u32()?))
85    }
86    /// Read a nonnegative count bounded by both policy and available storage.
87    pub fn count(&mut self, maximum: usize, stride: usize) -> Result<usize> {
88        let value = self.i32()?;
89        if value < 0 {
90            return self.fail("Negative count");
91        }
92        let count = value as usize;
93        if count > maximum
94            || count
95                .checked_mul(stride)
96                .is_none_or(|size| size > self.remaining())
97        {
98            return self.fail("Invalid count or truncated array");
99        }
100        Ok(count)
101    }
102    /// Read an original length-prefixed byte string.
103    pub fn string(&mut self, maximum: usize) -> Result<Vec<u8>> {
104        let size = self.count(maximum, 1)?;
105        Ok(self.raw(size)?.to_vec())
106    }
107    /// Require exact record consumption.
108    pub fn end(&self) -> Result<()> {
109        if self.remaining() == 0 {
110            Ok(())
111        } else {
112            self.fail("Trailing bytes")
113        }
114    }
115}
116
117/// Output builder with a byte budget; integer ranges are enforced by Rust types.
118pub struct Writer {
119    bytes: Vec<u8>,
120    maximum: usize,
121}
122
123impl Default for Writer {
124    fn default() -> Self {
125        Self::new(256 * 1024 * 1024)
126    }
127}
128
129impl Writer {
130    /// Construct a writer with a maximum encoded length.
131    pub fn new(maximum: usize) -> Self {
132        Self {
133            bytes: Vec::new(),
134            maximum,
135        }
136    }
137    /// Append bytes without exceeding the configured budget.
138    pub fn raw(&mut self, bytes: &[u8]) -> Result<()> {
139        if bytes.len() > self.maximum.saturating_sub(self.bytes.len()) {
140            return Err(Error {
141                offset: self.bytes.len(),
142                message: "Output byte budget exceeded".into(),
143            });
144        }
145        self.bytes.try_reserve(bytes.len()).map_err(|_| Error {
146            offset: self.bytes.len(),
147            message: "Allocation failed".into(),
148        })?;
149        self.bytes.extend_from_slice(bytes);
150        Ok(())
151    }
152    /// Write an unsigned byte.
153    pub fn u8(&mut self, value: u8) -> Result<()> {
154        self.raw(&[value])
155    }
156    /// Write a signed byte.
157    pub fn i8(&mut self, value: i8) -> Result<()> {
158        self.u8(value as u8)
159    }
160    /// Write an unsigned 16-bit word.
161    pub fn u16(&mut self, value: u16) -> Result<()> {
162        self.raw(&value.to_le_bytes())
163    }
164    /// Write a signed 16-bit word.
165    pub fn i16(&mut self, value: i16) -> Result<()> {
166        self.raw(&value.to_le_bytes())
167    }
168    /// Write an unsigned word or original f32 bits.
169    pub fn u32(&mut self, value: u32) -> Result<()> {
170        self.raw(&value.to_le_bytes())
171    }
172    /// Write a signed 32-bit word.
173    pub fn i32(&mut self, value: i32) -> Result<()> {
174        self.raw(&value.to_le_bytes())
175    }
176    /// Write an IEEE-754 single-precision value.
177    pub fn f32(&mut self, value: f32) -> Result<()> {
178        self.u32(value.to_bits())
179    }
180    /// Write a length-prefixed original or newly encoded string.
181    pub fn string(&mut self, bytes: &[u8]) -> Result<()> {
182        let length = i32::try_from(bytes.len()).map_err(|_| Error {
183            offset: self.bytes.len(),
184            message: "String too long".into(),
185        })?;
186        self.i32(length)?;
187        self.raw(bytes)
188    }
189    /// Consume the writer and return its bytes.
190    pub fn finish(self) -> Vec<u8> {
191        self.bytes
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn bit_exact_and_bounded() -> Result<()> {
201        let mut writer = Writer::default();
202        writer.i32(-17)?;
203        writer.u32(0x7fa12345)?;
204        writer.string(&[0, 255, 128])?;
205        let bytes = writer.finish();
206        let mut reader = Reader::new(&bytes);
207        assert_eq!(reader.i32()?, -17);
208        assert_eq!(reader.u32()?, 0x7fa12345);
209        assert_eq!(reader.string(100)?, [0, 255, 128]);
210        reader.end()?;
211        assert!(reader.u8().is_err());
212        assert!(Reader::new(&[255; 4]).count(100, 1).is_err());
213        assert!(Writer::new(2).u32(1).is_err());
214        Ok(())
215    }
216}