cyclone-runtime-rust 1.0.1

Rust reference runtime for the Cyclone binary wire format: Writer, Reader, DecodeError.
Documentation
//! Decoding side of the runtime.

use crate::error::DecodeError;

/// Allocation guards applied while decoding.
///
/// A `u32` length field can claim up to 4 GiB, so a decoder that allocates
/// straight from an untrusted length is a denial-of-service target. These
/// limits are **not part of the wire format** (RFC-0002 §12): two peers with
/// different configurations may disagree on whether a byte stream is
/// acceptable, and neither is wrong.
///
/// They are additive, never a replacement for the normative check that a length
/// may not exceed the bytes actually remaining. Both are enforced, so the
/// effective ceiling is `min(remaining_bytes, configured_limit)`.
///
/// The default is `u32::MAX` for every field - i.e. no restriction beyond what
/// the wire format itself imposes. Applications accepting bytes from the
/// network are expected to lower them:
///
/// ```
/// use cyclone_runtime::{Limits, Reader};
///
/// let limits = Limits {
///     max_string_len: 1024 * 1024,
///     max_bytes_len: 1024 * 1024,
///     max_array_count: 100_000,
/// };
/// let bytes = [0u8; 0];
/// let reader = Reader::with_limits(&bytes, limits);
/// assert!(reader.is_empty());
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limits {
    /// Largest accepted UTF-8 byte length of a `String`.
    pub max_string_len: usize,
    /// Largest accepted byte length of a `Bytes` blob.
    pub max_bytes_len: usize,
    /// Largest accepted element count of an `Array`.
    pub max_array_count: usize,
}

impl Limits {
    /// The permissive default: `u32::MAX` for every field.
    pub const UNLIMITED: Limits = Limits {
        max_string_len: u32::MAX as usize,
        max_bytes_len: u32::MAX as usize,
        max_array_count: u32::MAX as usize,
    };
}

impl Default for Limits {
    #[inline]
    fn default() -> Self {
        Limits::UNLIMITED
    }
}

/// Reads Cyclone-encoded values from a borrowed byte buffer.
///
/// The reader holds a cursor into `buf` and advances it by exactly the bytes
/// each value occupies. It borrows rather than owns, so decoding a message
/// costs no copy beyond the `String` and `Bytes` values that must be owned.
///
/// Malformed input is always reported as [`DecodeError`]; the reader never
/// panics on it, and the cursor is left untouched when a read fails, so an
/// error cannot desynchronize a caller that chooses to continue.
///
/// ```
/// use cyclone_runtime::Reader;
///
/// let bytes = [0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, b'a', b'b', b'c'];
/// let mut r = Reader::new(&bytes);
/// assert_eq!(r.read_u32()?, 42);
/// assert_eq!(r.read_string()?, "abc");
/// assert!(r.is_empty());
/// # Ok::<(), cyclone_runtime::DecodeError>(())
/// ```
#[derive(Debug, Clone)]
pub struct Reader<'a> {
    buf: &'a [u8],
    pos: usize,
    limits: Limits,
}

impl<'a> Reader<'a> {
    /// Creates a reader over `buf` with [`Limits::UNLIMITED`].
    #[inline]
    pub fn new(buf: &'a [u8]) -> Self {
        Reader { buf, pos: 0, limits: Limits::UNLIMITED }
    }

    /// Creates a reader over `buf` with explicit allocation guards.
    #[inline]
    pub fn with_limits(buf: &'a [u8], limits: Limits) -> Self {
        Reader { buf, pos: 0, limits }
    }

    /// Returns the cursor position, in bytes from the start of the buffer.
    #[inline]
    pub fn position(&self) -> usize {
        self.pos
    }

    /// Returns the number of bytes left to read.
    #[inline]
    pub fn remaining(&self) -> usize {
        self.buf.len() - self.pos
    }

    /// Returns `true` when the cursor has reached the end of the buffer.
    ///
    /// After decoding a complete message this MUST be true; trailing bytes mean
    /// the sender and receiver disagree about the schema.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.remaining() == 0
    }

    /// Returns the limits this reader enforces.
    #[inline]
    pub fn limits(&self) -> Limits {
        self.limits
    }

    // ---------------------------------------------------------------- bool

    /// Reads a `bool` from 1 byte.
    ///
    /// # Errors
    ///
    /// [`DecodeError::InvalidBool`] if the byte is neither `0x00` nor `0x01` -
    /// "non-zero means true" is not permitted (RFC-0002 §2.4).
    /// [`DecodeError::UnexpectedEof`] if the buffer is exhausted.
    #[inline]
    pub fn read_bool(&mut self) -> Result<bool, DecodeError> {
        let byte = self.read_u8()?;
        match byte {
            0x00 => Ok(false),
            0x01 => Ok(true),
            other => {
                // Rewind: the byte was not a valid bool, so it was not consumed.
                self.pos -= 1;
                Err(DecodeError::InvalidBool(other))
            }
        }
    }

    // ------------------------------------------------------------ integers

    /// Reads an `i8` from 1 byte.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 1 byte remains.
    #[inline]
    pub fn read_i8(&mut self) -> Result<i8, DecodeError> {
        Ok(i8::from_le_bytes(*self.take::<1>()?))
    }

    /// Reads a `u8` from 1 byte.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 1 byte remains.
    #[inline]
    pub fn read_u8(&mut self) -> Result<u8, DecodeError> {
        Ok(u8::from_le_bytes(*self.take::<1>()?))
    }

    /// Reads an `i16` from 2 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 2 bytes remain.
    #[inline]
    pub fn read_i16(&mut self) -> Result<i16, DecodeError> {
        Ok(i16::from_le_bytes(*self.take::<2>()?))
    }

    /// Reads a `u16` from 2 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 2 bytes remain.
    #[inline]
    pub fn read_u16(&mut self) -> Result<u16, DecodeError> {
        Ok(u16::from_le_bytes(*self.take::<2>()?))
    }

    /// Reads an `i32` from 4 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
    #[inline]
    pub fn read_i32(&mut self) -> Result<i32, DecodeError> {
        Ok(i32::from_le_bytes(*self.take::<4>()?))
    }

    /// Reads a `u32` from 4 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
    #[inline]
    pub fn read_u32(&mut self) -> Result<u32, DecodeError> {
        Ok(u32::from_le_bytes(*self.take::<4>()?))
    }

    /// Reads an `i64` from 8 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
    #[inline]
    pub fn read_i64(&mut self) -> Result<i64, DecodeError> {
        Ok(i64::from_le_bytes(*self.take::<8>()?))
    }

    /// Reads a `u64` from 8 bytes, Little Endian.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
    #[inline]
    pub fn read_u64(&mut self) -> Result<u64, DecodeError> {
        Ok(u64::from_le_bytes(*self.take::<8>()?))
    }

    // -------------------------------------------------------------- floats

    /// Reads an `f32` from its raw 4-byte IEEE 754 bit pattern.
    ///
    /// The bits are reinterpreted, not normalized: a signaling `NaN` stays
    /// signaling, its payload survives, and `-0.0` does not collapse to `0.0`
    /// (RFC-0002 §2.3).
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain.
    #[inline]
    pub fn read_f32(&mut self) -> Result<f32, DecodeError> {
        Ok(f32::from_bits(self.read_u32()?))
    }

    /// Reads an `f64` from its raw 8-byte IEEE 754 bit pattern.
    ///
    /// Same rule as [`read_f32`](Self::read_f32): bits are preserved exactly.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 8 bytes remain.
    #[inline]
    pub fn read_f64(&mut self) -> Result<f64, DecodeError> {
        Ok(f64::from_bits(self.read_u64()?))
    }

    // ------------------------------------------------------------- complex

    /// Reads a string: a `u32` UTF-8 byte length followed by that many bytes.
    ///
    /// The length is checked against [`Limits::max_string_len`] and against the
    /// bytes actually remaining **before** anything is allocated.
    ///
    /// # Errors
    ///
    /// [`DecodeError::LengthOverflow`] if the length exceeds the configured
    /// limit, [`DecodeError::UnexpectedEof`] if it exceeds the remaining bytes,
    /// and [`DecodeError::InvalidUtf8`] if the byte region is not valid UTF-8.
    pub fn read_string(&mut self) -> Result<String, DecodeError> {
        let start = self.pos;
        let len = self.read_prefixed_len(self.limits.max_string_len)?;
        let bytes = self.take_slice(len).inspect_err(|_| self.pos = start)?;

        match core::str::from_utf8(bytes) {
            Ok(text) => Ok(text.to_owned()),
            Err(_) => {
                self.pos = start;
                Err(DecodeError::InvalidUtf8)
            }
        }
    }

    /// Reads a byte blob: a `u32` length followed by that many raw bytes.
    ///
    /// Identical to [`read_string`](Self::read_string) minus the UTF-8 check,
    /// and guarded by [`Limits::max_bytes_len`].
    ///
    /// # Errors
    ///
    /// [`DecodeError::LengthOverflow`] if the length exceeds the configured
    /// limit; [`DecodeError::UnexpectedEof`] if it exceeds the remaining bytes.
    pub fn read_bytes(&mut self) -> Result<Vec<u8>, DecodeError> {
        let start = self.pos;
        let len = self.read_prefixed_len(self.limits.max_bytes_len)?;
        let bytes = self.take_slice(len).inspect_err(|_| self.pos = start)?;
        Ok(bytes.to_vec())
    }

    /// Reads the element count of an array as a `u32` (RFC-0002 §6).
    ///
    /// The elements themselves are read by the generated codec, which is the
    /// only party that knows their type. The count is checked against
    /// [`Limits::max_array_count`] so a codec never sizes a collection from an
    /// unbounded number.
    ///
    /// Unlike a string length, a count cannot be compared against the remaining
    /// bytes here - an element is not one byte, and the runtime does not know
    /// how wide it is. The per-element reads enforce that bound as they run.
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnexpectedEof`] if fewer than 4 bytes remain;
    /// [`DecodeError::LengthOverflow`] if the count exceeds the configured limit.
    pub fn read_array_count(&mut self) -> Result<usize, DecodeError> {
        let start = self.pos;
        let count = self.read_u32()? as usize;
        if count > self.limits.max_array_count {
            self.pos = start;
            return Err(DecodeError::LengthOverflow {
                length: count,
                limit: self.limits.max_array_count,
            });
        }
        Ok(count)
    }

    // ------------------------------------------------------------ internals

    /// Reads a `u32` length prefix and validates it against `limit`.
    ///
    /// Leaves the cursor just past the prefix on success; on a limit violation
    /// the cursor is rewound so the caller observes no partial consumption.
    #[inline]
    fn read_prefixed_len(&mut self, limit: usize) -> Result<usize, DecodeError> {
        let start = self.pos;
        let len = self.read_u32()? as usize;
        if len > limit {
            self.pos = start;
            return Err(DecodeError::LengthOverflow { length: len, limit });
        }
        Ok(len)
    }

    /// Borrows the next `N` bytes as a fixed-size array and advances the cursor.
    #[inline]
    fn take<const N: usize>(&mut self) -> Result<&'a [u8; N], DecodeError> {
        let bytes = self.take_slice(N)?;
        // `take_slice` returned exactly N bytes, so the conversion cannot fail.
        Ok(bytes.try_into().expect("slice length checked above"))
    }

    /// Borrows the next `len` bytes and advances the cursor.
    ///
    /// This is the single place where the remaining-bytes check lives, so no
    /// read path can allocate or index past the end of the buffer.
    #[inline]
    fn take_slice(&mut self, len: usize) -> Result<&'a [u8], DecodeError> {
        let remaining = self.remaining();
        if len > remaining {
            return Err(DecodeError::UnexpectedEof { needed: len, remaining });
        }
        let bytes = &self.buf[self.pos..self.pos + len];
        self.pos += len;
        Ok(bytes)
    }
}