zerec 0.2.1

Minimal zero-copy binary codec for Rust.
Documentation
//! Read-side of the ZRC wire format.

use crate::error::DecodeError;

/// Maximum nesting depth for collection types during decode.
///
/// Prevents stack overflow from adversarially crafted deeply nested
/// structures. 64 levels is far deeper than any real-world data.
pub const MAX_DECODE_DEPTH: u32 = 64;

/// A read-only cursor over a borrowed byte slice used during decoding.
///
/// `BufDecoder` advances an internal position as bytes are consumed.
/// Every read is bounds-checked; on underflow it returns
/// [`DecodeError::UnexpectedEof`] rather than panicking.
///
/// The lifetime `'buf` ties the decoder to the source buffer, which
/// enables zero-copy reads via [`crate::ZeroBuf`].
///
/// # Example
///
/// ```rust
/// use zerec::decoder::BufDecoder;
///
/// let data = 42u32.to_le_bytes();
/// let mut dec = BufDecoder::new(&data);
/// assert_eq!(dec.read_u32().unwrap(), 42);
/// assert_eq!(dec.remaining(), 0);
/// ```
pub struct BufDecoder<'buf> {
    buf: &'buf [u8],
    pos: usize,
    depth: u32,
}

impl<'buf> BufDecoder<'buf> {
    /// Creates a decoder starting at byte 0 of `buf`.
    #[inline]
    pub fn new(buf: &'buf [u8]) -> Self {
        Self { buf, pos: 0, depth: 0 }
    }

    /// Number of bytes consumed so far.
    #[inline]
    pub fn position(&self) -> usize {
        self.pos
    }

    /// Number of bytes not yet consumed.
    #[inline]
    pub fn remaining(&self) -> usize {
        self.buf.len().saturating_sub(self.pos)
    }

    /// Reads exactly `n` bytes and advances the cursor.
    ///
    /// Returns a slice into the original buffer - no allocation.
    /// Fails with [`DecodeError::UnexpectedEof`] if fewer than `n`
    /// bytes remain.
    #[inline]
    pub fn read_bytes(&mut self, n: usize) -> Result<&'buf [u8], DecodeError> {
        let end = self.pos.checked_add(n).filter(|&e| e <= self.buf.len()).ok_or(DecodeError::UnexpectedEof {
            needed: n,
            remaining: self.remaining(),
        })?;
        let slice = &self.buf[self.pos..end];
        self.pos = end;
        Ok(slice)
    }

    /// Reads one byte.
    #[inline]
    pub fn read_u8(&mut self) -> Result<u8, DecodeError> {
        Ok(self.read_bytes(1)?[0])
    }

    /// Reads two bytes as a little-endian `u16`.
    #[inline]
    pub fn read_u16(&mut self) -> Result<u16, DecodeError> {
        let b = self.read_bytes(2)?;
        Ok(u16::from_le_bytes([b[0], b[1]]))
    }

    /// Reads four bytes as a little-endian `u32`.
    #[inline]
    pub fn read_u32(&mut self) -> Result<u32, DecodeError> {
        let b = self.read_bytes(4)?;
        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
    }

    /// Reads eight bytes as a little-endian `u64`.
    #[inline]
    pub fn read_u64(&mut self) -> Result<u64, DecodeError> {
        let b = self.read_bytes(8)?;
        Ok(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]))
    }

    /// Increments the nesting depth counter.
    ///
    /// Call this at the start of any [`Decode`] impl that dynamically
    /// decodes a variable number of inner values (e.g. `Vec<T>`).
    /// Always pair with [`Self::leave`] on the success path.
    ///
    /// Returns [`DecodeError::NestingTooDeep`] if the limit is exceeded.
    /// On error paths inside the guarded region, `leave` need not be
    /// called — a failed decode cannot be meaningfully continued.
    #[inline]
    pub fn enter(&mut self) -> Result<(), DecodeError> {
        if self.depth >= MAX_DECODE_DEPTH {
            return Err(DecodeError::NestingTooDeep);
        }

        self.depth += 1;

        Ok(())
    }

    /// Decrements the nesting depth counter. Pair with [`Self::enter`].
    #[inline]
    pub fn leave(&mut self) {
        debug_assert!(self.depth > 0, "leave() called without matching enter()");
        self.depth = self.depth.saturating_sub(1);
    }
}