cyclone_runtime/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}