Skip to main content

cyclone_runtime_rust/
reader.rs

1//! Decoding side of the runtime.
2
3use crate::error::DecodeError;
4
5/// Allocation guards applied while decoding.
6///
7/// A `u32` length field can claim up to 4 GiB, so a decoder that allocates
8/// straight from an untrusted length is a denial-of-service target. These
9/// limits are **not part of the wire format** (RFC-0002 §12): two peers with
10/// different configurations may disagree on whether a byte stream is
11/// acceptable, and neither is wrong.
12///
13/// They are additive, never a replacement for the normative check that a length
14/// may not exceed the bytes actually remaining. Both are enforced, so the
15/// effective ceiling is `min(remaining_bytes, configured_limit)`.
16///
17/// The default is `u32::MAX` for every field - i.e. no restriction beyond what
18/// the wire format itself imposes. Applications accepting bytes from the
19/// network are expected to lower them:
20///
21/// ```
22/// use cyclone_runtime::{Limits, Reader};
23///
24/// let limits = Limits {
25///     max_string_len: 1024 * 1024,
26///     max_bytes_len: 1024 * 1024,
27///     max_array_count: 100_000,
28/// };
29/// let bytes = [0u8; 0];
30/// let reader = Reader::with_limits(&bytes, limits);
31/// assert!(reader.is_empty());
32/// ```
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Limits {
35    /// Largest accepted UTF-8 byte length of a `String`.
36    pub max_string_len: usize,
37    /// Largest accepted byte length of a `Bytes` blob.
38    pub max_bytes_len: usize,
39    /// Largest accepted element count of an `Array`.
40    pub max_array_count: usize,
41}
42
43impl Limits {
44    /// The permissive default: `u32::MAX` for every field.
45    pub const UNLIMITED: Limits = Limits {
46        max_string_len: u32::MAX as usize,
47        max_bytes_len: u32::MAX as usize,
48        max_array_count: u32::MAX as usize,
49    };
50}
51
52impl Default for Limits {
53    #[inline]
54    fn default() -> Self {
55        Limits::UNLIMITED
56    }
57}
58
59/// Reads Cyclone-encoded values from a borrowed byte buffer.
60///
61/// The reader holds a cursor into `buf` and advances it by exactly the bytes
62/// each value occupies. It borrows rather than owns, so decoding a message
63/// costs no copy beyond the `String` and `Bytes` values that must be owned.
64///
65/// Malformed input is always reported as [`DecodeError`]; the reader never
66/// panics on it, and the cursor is left untouched when a read fails, so an
67/// error cannot desynchronize a caller that chooses to continue.
68///
69/// ```
70/// use cyclone_runtime::Reader;
71///
72/// let bytes = [0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, b'a', b'b', b'c'];
73/// let mut r = Reader::new(&bytes);
74/// assert_eq!(r.read_u32()?, 42);
75/// assert_eq!(r.read_string()?, "abc");
76/// assert!(r.is_empty());
77/// # Ok::<(), cyclone_runtime::DecodeError>(())
78/// ```
79#[derive(Debug, Clone)]
80pub struct Reader<'a> {
81    buf: &'a [u8],
82    pos: usize,
83    limits: Limits,
84}
85
86impl<'a> Reader<'a> {
87    /// Creates a reader over `buf` with [`Limits::UNLIMITED`].
88    #[inline]
89    pub fn new(buf: &'a [u8]) -> Self {
90        Reader { buf, pos: 0, limits: Limits::UNLIMITED }
91    }
92
93    /// Creates a reader over `buf` with explicit allocation guards.
94    #[inline]
95    pub fn with_limits(buf: &'a [u8], limits: Limits) -> Self {
96        Reader { buf, pos: 0, limits }
97    }
98
99    /// Returns the cursor position, in bytes from the start of the buffer.
100    #[inline]
101    pub fn position(&self) -> usize {
102        self.pos
103    }
104
105    /// Returns the number of bytes left to read.
106    #[inline]
107    pub fn remaining(&self) -> usize {
108        self.buf.len() - self.pos
109    }
110
111    /// Returns `true` when the cursor has reached the end of the buffer.
112    ///
113    /// After decoding a complete message this MUST be true; trailing bytes mean
114    /// the sender and receiver disagree about the schema.
115    #[inline]
116    pub fn is_empty(&self) -> bool {
117        self.remaining() == 0
118    }
119
120    /// Returns the limits this reader enforces.
121    #[inline]
122    pub fn limits(&self) -> Limits {
123        self.limits
124    }
125
126    // ---------------------------------------------------------------- bool
127
128    /// Reads a `bool` from 1 byte.
129    ///
130    /// # Errors
131    ///
132    /// [`DecodeError::InvalidBool`] if the byte is neither `0x00` nor `0x01` -
133    /// "non-zero means true" is not permitted (RFC-0002 §2.4).
134    /// [`DecodeError::UnexpectedEof`] if the buffer is exhausted.
135    #[inline]
136    pub fn read_bool(&mut self) -> Result<bool, DecodeError> {
137        let byte = self.read_u8()?;
138        match byte {
139            0x00 => Ok(false),
140            0x01 => Ok(true),
141            other => {
142                // Rewind: the byte was not a valid bool, so it was not consumed.
143                self.pos -= 1;
144                Err(DecodeError::InvalidBool(other))
145            }
146        }
147    }
148
149    // ------------------------------------------------------------ integers
150
151    /// Reads an `i8` from 1 byte.
152    ///
153    /// # Errors
154    ///
155    /// [`DecodeError::UnexpectedEof`] if fewer than 1 byte remains.
156    #[inline]
157    pub fn read_i8(&mut self) -> Result<i8, DecodeError> {
158        Ok(i8::from_le_bytes(*self.take::<1>()?))
159    }
160
161    /// Reads a `u8` from 1 byte.
162    ///
163    /// # Errors
164    ///
165    /// [`DecodeError::UnexpectedEof`] if fewer than 1 byte remains.
166    #[inline]
167    pub fn read_u8(&mut self) -> Result<u8, DecodeError> {
168        Ok(u8::from_le_bytes(*self.take::<1>()?))
169    }
170
171    /// Reads an `i16` from 2 bytes, Little Endian.
172    ///
173    /// # Errors
174    ///
175    /// [`DecodeError::UnexpectedEof`] if fewer than 2 bytes remain.
176    #[inline]
177    pub fn read_i16(&mut self) -> Result<i16, DecodeError> {
178        Ok(i16::from_le_bytes(*self.take::<2>()?))
179    }
180
181    /// Reads a `u16` from 2 bytes, Little Endian.
182    ///
183    /// # Errors
184    ///
185    /// [`DecodeError::UnexpectedEof`] if fewer than 2 bytes remain.
186    #[inline]
187    pub fn read_u16(&mut self) -> Result<u16, DecodeError> {
188        Ok(u16::from_le_bytes(*self.take::<2>()?))
189    }
190
191    /// Reads an `i32` from 4 bytes, Little Endian.
192    ///
193    /// # Errors
194    ///
195    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
196    #[inline]
197    pub fn read_i32(&mut self) -> Result<i32, DecodeError> {
198        Ok(i32::from_le_bytes(*self.take::<4>()?))
199    }
200
201    /// Reads a `u32` from 4 bytes, Little Endian.
202    ///
203    /// # Errors
204    ///
205    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
206    #[inline]
207    pub fn read_u32(&mut self) -> Result<u32, DecodeError> {
208        Ok(u32::from_le_bytes(*self.take::<4>()?))
209    }
210
211    /// Reads an `i64` from 8 bytes, Little Endian.
212    ///
213    /// # Errors
214    ///
215    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
216    #[inline]
217    pub fn read_i64(&mut self) -> Result<i64, DecodeError> {
218        Ok(i64::from_le_bytes(*self.take::<8>()?))
219    }
220
221    /// Reads a `u64` from 8 bytes, Little Endian.
222    ///
223    /// # Errors
224    ///
225    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
226    #[inline]
227    pub fn read_u64(&mut self) -> Result<u64, DecodeError> {
228        Ok(u64::from_le_bytes(*self.take::<8>()?))
229    }
230
231    // -------------------------------------------------------------- floats
232
233    /// Reads an `f32` from its raw 4-byte IEEE 754 bit pattern.
234    ///
235    /// The bits are reinterpreted, not normalized: a signaling `NaN` stays
236    /// signaling, its payload survives, and `-0.0` does not collapse to `0.0`
237    /// (RFC-0002 §2.3).
238    ///
239    /// # Errors
240    ///
241    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
242    #[inline]
243    pub fn read_f32(&mut self) -> Result<f32, DecodeError> {
244        Ok(f32::from_bits(self.read_u32()?))
245    }
246
247    /// Reads an `f64` from its raw 8-byte IEEE 754 bit pattern.
248    ///
249    /// Same rule as [`read_f32`](Self::read_f32): bits are preserved exactly.
250    ///
251    /// # Errors
252    ///
253    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
254    #[inline]
255    pub fn read_f64(&mut self) -> Result<f64, DecodeError> {
256        Ok(f64::from_bits(self.read_u64()?))
257    }
258
259    // ------------------------------------------------------------- complex
260
261    /// Reads a string: a `u32` UTF-8 byte length followed by that many bytes.
262    ///
263    /// The length is checked against [`Limits::max_string_len`] and against the
264    /// bytes actually remaining **before** anything is allocated.
265    ///
266    /// # Errors
267    ///
268    /// [`DecodeError::LengthOverflow`] if the length exceeds the configured
269    /// limit, [`DecodeError::UnexpectedEof`] if it exceeds the remaining bytes,
270    /// and [`DecodeError::InvalidUtf8`] if the byte region is not valid UTF-8.
271    pub fn read_string(&mut self) -> Result<String, DecodeError> {
272        let start = self.pos;
273        let len = self.read_prefixed_len(self.limits.max_string_len)?;
274        let bytes = self.take_slice(len).inspect_err(|_| self.pos = start)?;
275
276        match core::str::from_utf8(bytes) {
277            Ok(text) => Ok(text.to_owned()),
278            Err(_) => {
279                self.pos = start;
280                Err(DecodeError::InvalidUtf8)
281            }
282        }
283    }
284
285    /// Reads a byte blob: a `u32` length followed by that many raw bytes.
286    ///
287    /// Identical to [`read_string`](Self::read_string) minus the UTF-8 check,
288    /// and guarded by [`Limits::max_bytes_len`].
289    ///
290    /// # Errors
291    ///
292    /// [`DecodeError::LengthOverflow`] if the length exceeds the configured
293    /// limit; [`DecodeError::UnexpectedEof`] if it exceeds the remaining bytes.
294    pub fn read_bytes(&mut self) -> Result<Vec<u8>, DecodeError> {
295        let start = self.pos;
296        let len = self.read_prefixed_len(self.limits.max_bytes_len)?;
297        let bytes = self.take_slice(len).inspect_err(|_| self.pos = start)?;
298        Ok(bytes.to_vec())
299    }
300
301    /// Reads the element count of an array as a `u32` (RFC-0002 §6).
302    ///
303    /// The elements themselves are read by the generated codec, which is the
304    /// only party that knows their type. The count is checked against
305    /// [`Limits::max_array_count`] so a codec never sizes a collection from an
306    /// unbounded number.
307    ///
308    /// Unlike a string length, a count cannot be compared against the remaining
309    /// bytes here - an element is not one byte, and the runtime does not know
310    /// how wide it is. The per-element reads enforce that bound as they run.
311    ///
312    /// # Errors
313    ///
314    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain;
315    /// [`DecodeError::LengthOverflow`] if the count exceeds the configured limit.
316    pub fn read_array_count(&mut self) -> Result<usize, DecodeError> {
317        let start = self.pos;
318        let count = self.read_u32()? as usize;
319        if count > self.limits.max_array_count {
320            self.pos = start;
321            return Err(DecodeError::LengthOverflow {
322                length: count,
323                limit: self.limits.max_array_count,
324            });
325        }
326        Ok(count)
327    }
328
329    // ------------------------------------------------------------ internals
330
331    /// Reads a `u32` length prefix and validates it against `limit`.
332    ///
333    /// Leaves the cursor just past the prefix on success; on a limit violation
334    /// the cursor is rewound so the caller observes no partial consumption.
335    #[inline]
336    fn read_prefixed_len(&mut self, limit: usize) -> Result<usize, DecodeError> {
337        let start = self.pos;
338        let len = self.read_u32()? as usize;
339        if len > limit {
340            self.pos = start;
341            return Err(DecodeError::LengthOverflow { length: len, limit });
342        }
343        Ok(len)
344    }
345
346    /// Borrows the next `N` bytes as a fixed-size array and advances the cursor.
347    #[inline]
348    fn take<const N: usize>(&mut self) -> Result<&'a [u8; N], DecodeError> {
349        let bytes = self.take_slice(N)?;
350        // `take_slice` returned exactly N bytes, so the conversion cannot fail.
351        Ok(bytes.try_into().expect("slice length checked above"))
352    }
353
354    /// Borrows the next `len` bytes and advances the cursor.
355    ///
356    /// This is the single place where the remaining-bytes check lives, so no
357    /// read path can allocate or index past the end of the buffer.
358    #[inline]
359    fn take_slice(&mut self, len: usize) -> Result<&'a [u8], DecodeError> {
360        let remaining = self.remaining();
361        if len > remaining {
362            return Err(DecodeError::UnexpectedEof { needed: len, remaining });
363        }
364        let bytes = &self.buf[self.pos..self.pos + len];
365        self.pos += len;
366        Ok(bytes)
367    }
368}