internet 0.1.0

Network library for rust
Documentation
//! IPv6 header types and codecs.
//!
//! As defined in [RFC 8200].
//!
//! [IETF RFC 8200]: https://datatracker.ietf.org/doc/html/rfc8200

use crate::ietf::ip::Protocol;
use crate::ietf::ipv6::Address;
use crate::{Buf, BufError, BufMut, BufResult, Codec, Cursor};

/// Traffic Class field of the IPv6 header.
///
/// An 8-bit field used for packet classification, similar to the IPv4 DSCP and ECN fields.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TrafficClass(pub u8);

impl From<u8> for TrafficClass {
    fn from(value: u8) -> Self {
        Self(value)
    }
}

impl From<TrafficClass> for u8 {
    fn from(value: TrafficClass) -> Self {
        value.0
    }
}

impl Codec for TrafficClass {
    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(u8::decode(reader, ())?))
    }
}

/// Flow Label field of the IPv6 header.
///
/// A 20-bit field used by a source to label sequences of packets for special handling.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct FlowLabel(pub u32);

impl From<u32> for FlowLabel {
    fn from(value: u32) -> Self {
        Self(value & 0x000F_FFFF)
    }
}

impl From<FlowLabel> for u32 {
    fn from(value: FlowLabel) -> Self {
        value.0 & 0x000F_FFFF
    }
}

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

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let val = u32::decode(reader, ())?;
        Ok(Self(val & 0x000F_FFFF))
    }
}

/// An IPv6 header.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
    /// Traffic Class field.
    pub traffic_class: TrafficClass,
    /// Flow Label field.
    pub flow_label: FlowLabel,
    /// Payload Length field (in bytes).
    pub payload_length: u16,
    /// Next Header field.
    pub next_header: Protocol,
    /// Hop Limit field.
    pub hop_limit: u8,
    /// Source Address field.
    pub source_address: Address,
    /// Destination Address field.
    pub destination_address: Address,
}

impl Header {
    /// The IPv6 version number (6).
    pub const VERSION: u8 = 6;
}

impl Codec for Header {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        let v_tc_fl = ((Self::VERSION as u32) << 28)
            | ((self.traffic_class.0 as u32) << 20)
            | (self.flow_label.0 & 0x000F_FFFF);

        writer.write_u32_be(v_tc_fl)?;
        self.payload_length.encode(writer, ())?;
        self.next_header.encode(writer, ())?;
        self.hop_limit.encode(writer, ())?;
        self.source_address.encode(writer, ())?;
        self.destination_address.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let v_tc_fl = reader.read_u32_be()?;

        let version = v_tc_fl >> 28;
        if version != Self::VERSION as u32 {
            return Err(BufError::UnexpectedValue);
        }

        let traffic_class = TrafficClass(((v_tc_fl >> 20) & 0xFF) as u8);
        let flow_label = FlowLabel(v_tc_fl & 0x000F_FFFF);
        let payload_length = reader.read_u16_be()?;
        let next_header = Protocol::decode(reader, ())?;
        let hop_limit = reader.read_u8()?;
        let source_address = Address::decode(reader, ())?;
        let destination_address = Address::decode(reader, ())?;

        Ok(Self {
            traffic_class,
            flow_label,
            payload_length,
            next_header,
            hop_limit,
            source_address,
            destination_address,
        })
    }
}

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

    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(&encoded_bytes);
            T::decode(reader, context).unwrap()
        };
        assert_eq!(etalon_struct, decoded_struct);
    }

    #[test]
    fn header_roundtrip() {
        let etalon_bytes = &[
            0x60, 0x00, 0x00, 0x00, // Version 6, TC 0, Flow Label 0
            0x00, 0x20, // Payload Length: 32
            0x06, // Next Header: TCP (6)
            0x40, // Hop Limit: 64
            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x01, // Source
            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x02, // Dest
        ];

        let etalon_struct = Header {
            traffic_class: TrafficClass(0),
            flow_label: FlowLabel(0),
            payload_length: 32,
            next_header: Protocol::TCP,
            hop_limit: 64,
            source_address: Address::from([
                0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x01,
            ]),
            destination_address: Address::from([
                0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x02,
            ]),
        };

        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }
}