tls_codec 0.5.0

A pure Rust implementation of the TLS (de)serialization
Documentation
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(
    clippy::mod_module_files,
    clippy::unwrap_used,
    rust_2018_idioms,
    unused_lifetimes
)]

//! ## Usage
//!
//! ```
//! # #[cfg(feature = "std")]
//! # {
//! use tls_codec::{TlsVecU8, Serialize, Deserialize};
//! let mut b = &[1u8, 4, 77, 88, 1, 99] as &[u8];
//!
//! let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize");
//! assert_eq!(1, a);
//! println!("b: {:?}", b);
//! let v = TlsVecU8::<u8>::tls_deserialize(&mut b).expect("Unable to tls_deserialize");
//! assert_eq!(&[77, 88, 1, 99], v.as_slice());
//! # }
//! ```

#[macro_use]
extern crate alloc;

#[cfg(feature = "std")]
extern crate std;

use alloc::{string::String, vec::Vec};
use core::fmt::{self, Display};
#[cfg(feature = "std")]
use std::io::{Read, Write};

mod arrays;
mod primitives;
mod quic_vec;
mod string;
mod tls_vec;
mod varint;

pub use tls_vec::{
    SecretTlsVecU8, SecretTlsVecU16, SecretTlsVecU24, SecretTlsVecU32, TlsByteSliceU8,
    TlsByteSliceU16, TlsByteSliceU24, TlsByteSliceU32, TlsByteVecU8, TlsByteVecU16, TlsByteVecU24,
    TlsByteVecU32, TlsSliceU8, TlsSliceU16, TlsSliceU24, TlsSliceU32, TlsVecU8, TlsVecU16,
    TlsVecU24, TlsVecU32,
};

#[cfg(feature = "std")]
#[cfg_attr(feature = "future_deprecations", allow(deprecated))]
pub use quic_vec::SecretVLBytes;
#[cfg_attr(feature = "future_deprecations", allow(deprecated))]
pub use quic_vec::VLBytes;
#[cfg(feature = "std")]
pub use quic_vec::{SecretVLByteVec, rw as vlen};
pub use quic_vec::{VLByteSlice, VLByteVec};

#[cfg(feature = "derive")]
pub use tls_codec_derive::{
    TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSerializeBytes, TlsSize,
};

#[cfg(feature = "conditional_deserialization")]
pub use tls_codec_derive::conditionally_deserializable;

pub use varint::TlsVarInt;

/// Errors that are thrown by this crate.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Error {
    /// An error occurred during encoding.
    EncodingError(String),

    /// The length of a vector is invalid.
    InvalidVectorLength,

    /// Error writing everything out.
    ///
    /// **Deprecated:** This error variant is not returned anymore and only kept to avoid breaking
    /// existing code.
    InvalidWriteLength(String),

    /// Invalid input when trying to decode a primitive integer.
    InvalidInput,

    /// An error occurred during decoding.
    DecodingError(String),

    /// Reached the end of a byte stream.
    EndOfStream,

    /// Found unexpected data after deserializing.
    TrailingData,

    /// An unknown value in an enum.
    /// The application might not want to treat this as an error because it is
    /// only an unknown value, not an invalid value.
    UnknownValue(u64),

    /// An internal library error that indicates a bug.
    LibraryError,
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{self:?}"))
    }
}

#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        match e.kind() {
            std::io::ErrorKind::UnexpectedEof => Self::EndOfStream,
            _ => Self::DecodingError(format!("io error: {e:?}")),
        }
    }
}

/// Upper bound on an up-front allocation sized from an untrusted length hint, so
/// that a bogus (large) length field can't trigger a huge allocation before any
/// data is read. Callers cap the initial capacity at this value and let the
/// vector grow as data actually arrives.
#[cfg(any(feature = "std", feature = "serde"))]
pub(crate) const MAX_PREALLOC: usize = 4096;

/// Read exactly `len` bytes from `reader` into a freshly allocated vector.
///
/// Unlike `vec![0u8; len]` followed by `read_exact`, this does **not** eagerly
/// allocate `len` bytes up front: the initial allocation is capped so that a
/// bogus (large) length field in untrusted input can't trigger a huge
/// allocation before any bytes are read. The vector grows as data actually
/// arrives.
///
/// Returns [`Error::EndOfStream`] if the reader is exhausted before `len` bytes
/// are read.
#[cfg(feature = "std")]
fn read_bytes_bounded<R: std::io::Read>(reader: &mut R, len: usize) -> Result<Vec<u8>, Error> {
    if len > isize::MAX as usize {
        return Err(Error::InvalidVectorLength);
    }

    // Cap the initial allocation. `read_to_end` grows the vector as data
    // actually arrives, and `Take` bounds the reader to `len` so growth can
    // never exceed the request.
    let mut result = Vec::with_capacity(core::cmp::min(len, MAX_PREALLOC));

    // `read_to_end` reads directly into the vector's spare capacity and retries
    // `ErrorKind::Interrupted` internally, unlike a bare `read` loop.
    reader.take(len as u64).read_to_end(&mut result)?;

    // `Take` caps output at `len`, so a short read means the stream was
    // exhausted early.
    if result.len() != len {
        return Err(Error::EndOfStream);
    }
    Ok(result)
}

/// Adds two serialized-length components, guarding against `usize` overflow
/// only on platforms where it can actually occur.
///
/// On 64-bit targets every length is bounded by the amount of addressable
/// memory (`isize::MAX`), so a sum of serialized lengths can never overflow
/// `usize`. There this compiles down to a plain addition with no branch,
/// keeping the serialization hot path free of overflow checks.
///
/// On narrower targets (32-bit, 16-bit is not officially supported)
/// the serialized form of a large in-memory structure can carry enough
/// length-prefix / discriminant overhead to exceed `usize::MAX`.
/// There we saturate at `usize::MAX` rather than silently wrapping to a small
/// value, so the oversized length is subsequently rejected by the
/// length-encoding bounds checks instead of producing a truncated, mismatched
/// length prefix on the wire.
///
/// `tls_codec_derive` emits the equivalent logic inline, so this helper does not
/// need to be part of the public API.
#[inline(always)]
#[cfg(target_pointer_width = "64")]
pub(crate) const fn len_add(a: usize, b: usize) -> usize {
    a + b
}

#[inline(always)]
#[cfg(not(target_pointer_width = "64"))]
pub(crate) const fn len_add(a: usize, b: usize) -> usize {
    a.saturating_add(b)
}

/// Like [`len_add`], but for contexts that can surface an error.
///
/// On 64-bit targets this is a plain, branch-free addition (see [`len_add`] for
/// why it can't overflow). On narrower targets an overflow becomes
/// [`Error::InvalidVectorLength`] so a wrapped, too-small length is never
/// written to the wire.
#[inline(always)]
#[cfg(target_pointer_width = "64")]
pub(crate) fn checked_len_add(a: usize, b: usize) -> Result<usize, Error> {
    Ok(a + b)
}

#[inline(always)]
#[cfg(not(target_pointer_width = "64"))]
pub(crate) fn checked_len_add(a: usize, b: usize) -> Result<usize, Error> {
    a.checked_add(b).ok_or(Error::InvalidVectorLength)
}

/// Validates `len` as a [`Vec`] capacity.
///
/// [`Vec::with_capacity`] *panics* when the requested capacity exceeds
/// `isize::MAX`, so anything larger becomes [`Error::InvalidVectorLength`]
/// instead of a panic. Call this at allocation sites — not in the per-element
/// length folds, which use the cheaper [`checked_len_add`].
#[inline(always)]
pub(crate) fn checked_capacity(len: usize) -> Result<usize, Error> {
    if len > isize::MAX as usize {
        return Err(Error::InvalidVectorLength);
    }
    Ok(len)
}

/// Adds two length components and validates the result as a [`Vec`] capacity
/// (see [`checked_capacity`]).
///
/// The `saturating_add` collapses a `usize` wrap (possible on narrow targets)
/// into a value the `isize::MAX` check then rejects, so both failure modes are
/// covered by a single check.
#[inline(always)]
pub(crate) fn checked_alloc_len(a: usize, b: usize) -> Result<usize, Error> {
    checked_capacity(a.saturating_add(b))
}

/// The `Size` trait needs to be implemented by any struct that should be
/// efficiently serialized.
/// This allows to collect the length of a serialized structure before allocating
/// memory.
pub trait Size {
    fn tls_serialized_len(&self) -> usize;
}

/// The `Serialize` trait provides functions to serialize a struct or enum.
///
/// The trait provides two functions:
/// * `tls_serialize` that takes a buffer to write the serialization to
/// * `tls_serialize_detached` that returns a byte vector
pub trait Serialize: Size {
    /// Serialize `self` and write it to the `writer`.
    /// The function returns the number of bytes written to `writer`.
    #[cfg(feature = "std")]
    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, Error>;

    /// Serialize `self` and return it as a byte vector.
    #[cfg(feature = "std")]
    fn tls_serialize_detached(&self) -> Result<Vec<u8>, Error> {
        let mut buffer = Vec::with_capacity(checked_capacity(self.tls_serialized_len())?);
        let written = self.tls_serialize(&mut buffer)?;
        debug_assert_eq!(
            written,
            buffer.len(),
            "Expected that {} bytes were written but the output holds {} bytes",
            written,
            buffer.len()
        );
        if written != buffer.len() {
            Err(Error::EncodingError(format!(
                "Expected that {} bytes were written but the output holds {} bytes",
                written,
                buffer.len()
            )))
        } else {
            Ok(buffer)
        }
    }
}

/// The `SerializeBytes` trait provides a function to serialize a struct or enum.
///
/// The trait provides one function:
/// * `tls_serialize_bytes` that returns a byte vector
pub trait SerializeBytes: Size {
    /// Serialize `self` and return it as a byte vector.
    fn tls_serialize_bytes(&self) -> Result<Vec<u8>, Error>;
}

/// The `Deserialize` trait defines functions to deserialize a byte slice to a
/// struct or enum.
pub trait Deserialize: Size {
    /// This function deserializes the `bytes` from the provided a [`std::io::Read`]
    /// and returns the populated struct.
    ///
    /// In order to get the amount of bytes read, use [`Size::tls_serialized_len`].
    ///
    /// Returns an error if one occurs during deserialization.
    #[cfg(feature = "std")]
    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, Error>
    where
        Self: Sized;

    /// This function deserializes the provided `bytes` and returns the populated
    /// struct. All bytes must be consumed.
    ///
    /// Returns an error if not all bytes are read from the input, or if an error
    /// occurs during deserialization.
    #[cfg(feature = "std")]
    fn tls_deserialize_exact(bytes: impl AsRef<[u8]>) -> Result<Self, Error>
    where
        Self: Sized,
    {
        let mut bytes = bytes.as_ref();
        let out = Self::tls_deserialize(&mut bytes)?;

        if !bytes.is_empty() {
            return Err(Error::TrailingData);
        }

        Ok(out)
    }
}

/// The `DeserializeBytes` trait defines functions to deserialize a byte slice
/// to a struct or enum. In contrast to [`Deserialize`], this trait operates
/// directly on byte slices and can return any remaining bytes.
pub trait DeserializeBytes: Size {
    /// This function deserializes the `bytes` from the provided a `&[u8]`
    /// and returns the populated struct, as well as the remaining slice.
    ///
    /// In order to get the amount of bytes read, use [`Size::tls_serialized_len`].
    ///
    /// Returns an error if one occurs during deserialization.
    fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>
    where
        Self: Sized;

    /// This function deserializes the provided `bytes` and returns the populated
    /// struct. All bytes must be consumed.
    ///
    /// Returns an error if not all bytes are read from the input, or if an error
    /// occurs during deserialization.
    fn tls_deserialize_exact_bytes(bytes: &[u8]) -> Result<Self, Error>
    where
        Self: Sized,
    {
        let (out, remainder) = Self::tls_deserialize_bytes(bytes)?;

        if !remainder.is_empty() {
            return Err(Error::TrailingData);
        }

        Ok(out)
    }
}

/// A 3 byte wide unsigned integer type as defined in [RFC 5246].
///
/// [RFC 5246]: https://datatracker.ietf.org/doc/html/rfc5246#section-4.4
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct U24([u8; 3]);

impl U24 {
    pub const MAX: Self = Self([255u8; 3]);
    pub const MIN: Self = Self([0u8; 3]);

    pub fn from_be_bytes(bytes: [u8; 3]) -> Self {
        U24(bytes)
    }

    pub fn to_be_bytes(self) -> [u8; 3] {
        self.0
    }
}

impl From<U24> for usize {
    fn from(value: U24) -> usize {
        const LEN: usize = core::mem::size_of::<usize>();
        let mut usize_bytes = [0u8; LEN];
        usize_bytes[LEN - 3..].copy_from_slice(&value.0);
        usize::from_be_bytes(usize_bytes)
    }
}

impl TryFrom<usize> for U24 {
    type Error = Error;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        const LEN: usize = core::mem::size_of::<usize>();
        // In practice, our usages of this conversion should never be invalid, as the values
        // have to come from `TryFrom<U24> for usize`.
        if value > (1 << 24) - 1 {
            Err(Error::LibraryError)
        } else {
            Ok(U24(value.to_be_bytes()[LEN - 3..].try_into()?))
        }
    }
}