socker 1.0.0

Sans-IO SOCKS4, SOCKS4a, SOCKS5 and SOCKS5h protocol implementation
Documentation
//! The SOCKS5 UDP datagram header used with `UDP_ASSOCIATE`, as defined by
//! RFC 1928 section 7.
//!
//! Every datagram exchanged between the client and the relay carries a
//! ten-plus-byte header in front of the payload: `RSV RSV FRAG ATYP
//! DST.ADDR DST.PORT DATA`. [`UdpDatagram`] represents one such datagram and
//! borrows its payload, so relaying never copies the user data.
//!
//! The `FRAG` field is parsed and emitted as-is; fragment *reassembly* is
//! explicitly out of scope - relays conventionally drop any datagram whose
//! `FRAG` is nonzero.

use super::types::Address;
use crate::{
    DecodeError,
    DecodeStatus,
    EncodeError,
};

/// A SOCKS5 UDP datagram: the header defined by RFC 1928 section 7 plus a
/// borrowed payload.
///
/// This type deliberately borrows the payload so that a relay can pass user
/// data through without copying; it therefore does not (and cannot)
/// implement the owned [`crate::Message`] trait and instead offers the same
/// three operations as inherent methods.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UdpDatagram<'payload> {
    /// The fragment number: `0` means the datagram is not fragmented.
    /// Preserved verbatim, including nonstandard values.
    pub fragment: u8,

    /// The destination (client-to-relay) or source (relay-to-client)
    /// address.
    pub address: Address,

    /// The destination or source port.
    pub port: u16,

    /// The user payload following the header.
    pub payload: &'payload [u8],
}

impl UdpDatagram<'_> {
    /// Returns the exact number of bytes [`UdpDatagram::encode_into`] will
    /// append.
    #[must_use]
    pub const fn encoded_len(&self) -> usize {
        self.address
            .encoded_len()
            .saturating_add(self.payload.len())
            .saturating_add(5)
    }

    /// Appends the header and the payload to `buffer` as one contiguous
    /// sequence.
    ///
    /// # Errors
    ///
    /// Returns [`EncodeError::TooLong`] if the embedded address cannot be
    /// encoded.
    pub fn encode_into(&self, buffer: &mut Vec<u8>) -> Result<(), EncodeError> {
        buffer.reserve(self.encoded_len());
        buffer.push(0x00); // RSV
        buffer.push(0x00); // RSV
        buffer.push(self.fragment);
        self.address.encode_into(buffer)?;
        buffer.extend_from_slice(&self.port.to_be_bytes());
        buffer.extend_from_slice(self.payload);
        Ok(())
    }

    /// Attempts to parse a datagram from the front of `source`, borrowing the
    /// payload from it.
    ///
    /// # Errors
    ///
    /// Returns [`DecodeError::Malformed`] if either reserved byte is nonzero
    /// or the address is invalid, and [`DecodeStatus::Partial`] when more
    /// bytes are required.
    pub fn decode(source: &[u8]) -> Result<DecodeStatus<(UdpDatagram<'_>, usize)>, DecodeError> {
        let Some((&reserved_high, after_high)) = source.split_first() else {
            return Ok(DecodeStatus::Partial);
        };
        let Some((&reserved_low, after_low)) = after_high.split_first() else {
            return Ok(DecodeStatus::Partial);
        };
        let Some((&fragment, rest)) = after_low.split_first() else {
            return Ok(DecodeStatus::Partial);
        };
        if reserved_high != 0x00 || reserved_low != 0x00 {
            return Err(DecodeError::Malformed("reserved bytes are not zero"));
        }
        let (address, consumed) = match Address::decode_from(rest)? {
            | DecodeStatus::Complete(complete) => complete,
            | DecodeStatus::Partial => return Ok(DecodeStatus::Partial),
        };
        let Some(after_address) = rest.get(consumed ..) else {
            return Ok(DecodeStatus::Partial);
        };
        let Some(&[port_high, port_low]) = after_address.first_chunk::<2>() else {
            return Ok(DecodeStatus::Partial);
        };
        let Some(payload) = after_address.get(2 ..) else {
            return Ok(DecodeStatus::Partial);
        };
        Ok(DecodeStatus::Complete((
            UdpDatagram {
                fragment,
                address,
                port: u16::from_be_bytes([port_high, port_low]),
                payload,
            },
            consumed.saturating_add(payload.len()).saturating_add(5),
        )))
    }
}