internet 0.0.3

Network library for rust
Documentation
//! QUIC Version-Independent Properties Encoding.
//!
//! Codecs for protocol elements common to all versions of QUIC following [RFC 8999].
//!
//! This module provides:
//!
//! - [`HeaderForm`]: A long or short header indicator.
//! - [`Version`]: A four-byte protocol version identifier.
//! - [`ConnectionId`]: An opaque endpoint identifier.
//! - [`VersionNegotiationPacket`]: A server response listing supported versions.
//!
//! [RFC 8999]: https://datatracker.ietf.org/doc/html/rfc8999

use crate::{Buf, BufError, BufMut, BufResult, Codec, Cursor};

/// A packet header form following [Section 5].
///
/// Indicates whether a packet uses a long or short header.
///
/// [Section 5]: https://datatracker.ietf.org/doc/html/rfc8999#section-5
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum HeaderForm {
    /// A short header.
    ShortHeader = 0x00,
    /// A long header.
    LongHeader = 0x01,
}

impl Codec for HeaderForm {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        let current_byte = writer.peek_u8().unwrap_or(0x00);
        let header_bit = (*self as u8) << 7;
        let byte = (current_byte & !0x80) | header_bit;
        writer.poke_u8(byte)
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let value = reader.peek_u8()? & 0x80;
        match value {
            0 => Ok(Self::ShortHeader),
            _ => Ok(Self::LongHeader),
        }
    }
}

/// A protocol version following [RFC 8999, Section 5.4] and [RFC 9000, Section 15].
///
/// A four-byte identifier for the QUIC version in use.
///
/// [RFC 8999, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc8999#section-5.4
/// [RFC 9000, Section 15]: https://datatracker.ietf.org/doc/html/rfc9000#section-15
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Version(pub u32);

impl Version {
    /// Reserved for version negotiation; see [`VersionNegotiationPacket`].
    pub const VERSION_NEGOTIATION: Self = Self(0x00000000);

    /// QUIC version 1.
    pub const QUIC_V1: Self = Self(0x00000001);

    /// QUIC version 2
    pub const QUIC_V2: Self = Self(0x6b3343cf);

    /// Versions that follow the pattern `0x?a?a?a?a` are reserved for GREASE.
    pub fn is_grease(&self) -> bool {
        (self.0 & 0x0f0f0f0f) == 0x0a0a0a0a
    }
}

impl Codec for Version {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.0.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        Ok(Self(u32::decode(reader, ())?))
    }
}

/// A connection ID following [Section 5.3].
///
/// An opaque connection identifier of up to 255 bytes.
///
/// [Section 5.3]: https://datatracker.ietf.org/doc/html/rfc8999#section-5.3
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConnectionId(Vec<u8>);

impl ConnectionId {
    /// Length limit is 255 bytes.
    pub const MAXIMAL_LENGTH: u8 = u8::MAX;

    /// Creates connection ID from slice.
    pub fn from_slice(connection_id: &[u8]) -> BufResult<Self> {
        if connection_id.length() > Self::MAXIMAL_LENGTH as usize {
            return Err(BufError::InvalidLength);
        }
        Ok(Self(connection_id.to_vec()))
    }

    /// Creates connection ID from Vec.
    pub fn from_vec(connection_id: Vec<u8>) -> BufResult<Self> {
        if connection_id.len() > Self::MAXIMAL_LENGTH as usize {
            return Err(BufError::InvalidLength);
        }
        Ok(Self(connection_id))
    }

    /// Returns length.
    pub fn length(&self) -> u8 {
        self.0.len() as u8
    }

    /// Returns bytes clone.
    pub fn bytes(&self) -> Vec<u8> {
        self.0.clone()
    }
}

impl Codec for ConnectionId {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        (self.0.len() as u8).encode(writer, ())?;
        self.0.encode(writer, self.0.len())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let bytes = &mut [0u8; Self::MAXIMAL_LENGTH as usize];
        let length = u8::decode(reader, ())?;
        reader.read_into(&mut bytes[..length as usize])?;
        Ok(Self::from_slice(&bytes[..length as usize])?)
    }
}

impl Codec<u8> for ConnectionId {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: u8) -> BufResult<()> {
        writer.write_slice(&self.0[..self.length() as usize])
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, cil: u8) -> BufResult<Self> {
        let bytes = &mut [0u8; Self::MAXIMAL_LENGTH as usize];
        reader.read_into(&mut bytes[..cil as usize])?;
        Ok(Self::from_slice(&bytes[..cil as usize])?)
    }
}

/// A version negotiation packet following [Section 6].
///
/// Sent by a server to list the [`Version`]s it supports.
///
/// [Section 6]: https://datatracker.ietf.org/doc/html/rfc8999#section-6
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VersionNegotiationPacket {
    /// The destination connection id.
    pub destination_connection_id: ConnectionId,
    /// The source connection id.
    pub source_connection_id: ConnectionId,
    /// Supported protocol versions.
    pub supported_version: Vec<Version>,
}

impl VersionNegotiationPacket {
    /// A [HeaderForm].
    pub const HEADER_FORM: HeaderForm = HeaderForm::LongHeader;
    /// A [Version] set to packet specific value.
    pub const VERSION: Version = Version::VERSION_NEGOTIATION;
}

impl Codec for VersionNegotiationPacket {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        Self::HEADER_FORM.encode(writer, ())?;
        writer.advance(1)?;
        Self::VERSION.encode(writer, ())?;
        self.destination_connection_id.encode(writer, ())?;
        self.source_connection_id.encode(writer, ())?;
        self.supported_version.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        if HeaderForm::decode(reader, ())? != Self::HEADER_FORM {
            return Err(BufError::UnexpectedValue);
        }
        reader.advance(1)?;
        if Version::decode(reader, ())? != Self::VERSION {
            return Err(BufError::UnexpectedValue);
        }
        let destination_connection_id = ConnectionId::decode(reader, ())?;
        let source_connection_id = ConnectionId::decode(reader, ())?;
        let supported_version = Vec::decode(reader, ())?;

        Ok(Self {
            destination_connection_id,
            source_connection_id,
            supported_version,
        })
    }
}

#[cfg(test)]
mod tests {
    use core::fmt::Debug;

    use crate::{
        BufError, Codec, Cursor,
        quic::{ConnectionId, HeaderForm, Version, VersionNegotiationPacket},
    };

    fn codec_roundtrip<T: Codec<C> + Debug + Eq, C: Copy>(
        etalon_struct: T,
        etalon_bytes: &[u8],
        context: C,
    ) {
        let mut encoded_bytes = vec![];
        {
            let writer = &mut Cursor::new(&mut encoded_bytes);
            etalon_struct.encode(writer, context).unwrap();
        }
        assert_eq!(etalon_bytes, &encoded_bytes);

        let decoded_struct = {
            let reader = &mut Cursor::new(&mut encoded_bytes);
            T::decode(reader, context).unwrap()
        };
        assert_eq!(etalon_struct, decoded_struct);

        encoded_bytes.fill(0x00);
        {
            let writer = &mut Cursor::new(&mut encoded_bytes);
            decoded_struct.encode(writer, context).unwrap();
        }
        assert_eq!(etalon_bytes, &encoded_bytes);
    }

    #[test]
    fn header_form() {
        let etalon_bytes = &[0b10000000];
        let etalon_struct = HeaderForm::LongHeader;
        codec_roundtrip(etalon_struct, etalon_bytes, ());

        let etalon_bytes = &[0b00000000];
        let etalon_struct = HeaderForm::ShortHeader;
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn version() {
        let etalon_bytes = &[0x00, 0x00, 0x00, 0x01];
        let etalon_struct = Version(1);
        codec_roundtrip(etalon_struct, etalon_bytes, ());
        assert_eq!(etalon_struct.is_grease(), false);

        let etalon_bytes = &[0x1a, 0x1a, 0x1a, 0x1a];
        let etalon_struct = Version(0x1a1a1a1a);
        codec_roundtrip(etalon_struct, etalon_bytes, ());
        assert_eq!(etalon_struct.is_grease(), true);
    }

    #[test]
    fn connection_id() {
        assert_eq!(
            ConnectionId::from_slice(&[0x08; 256]),
            Err(BufError::InvalidLength)
        );

        let etalon_bytes = &[0x08; 9];
        let etalon_struct = ConnectionId::from_slice(&[0x08; 8]).unwrap();
        codec_roundtrip(etalon_struct, etalon_bytes, ());

        let etalon_bytes = &[0x014; 21];
        let etalon_struct = ConnectionId::from_slice(&[0x014; 20]).unwrap();
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn version_negotiation_packet() {
        let etalon_struct = VersionNegotiationPacket {
            destination_connection_id: ConnectionId::from_slice(&[0x82; 8]).unwrap(),
            source_connection_id: ConnectionId::from_slice(&[0x41; 8]).unwrap(),
            supported_version: vec![Version(0)],
        };

        let etalon_bytes: &[u8] = &[
            0b10000000, // First byte
            0x00, 0x00, 0x00, 0x00, // Version
            0x08, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, // Destination Connection ID
            0x08, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, // Source Connection ID
            0x00, 0x00, 0x00, 0x00,
        ];
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }
}