internet 0.1.0

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

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

/// A Hop-by-Hop Options header.
///
/// Must be examined by every node along the packet's delivery path.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HopByHopOptionsHeader {
    /// The Next Header field.
    pub next_header: Protocol,
    /// Length of the header in 8-octet units, not including the first 8 octets.
    pub hdr_ext_len: u8,
    /// The options data.
    pub options: Vec<u8>,
}

impl Codec for HopByHopOptionsHeader {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.next_header.encode(writer, ())?;
        self.hdr_ext_len.encode(writer, ())?;
        writer.write_slice(&self.options)
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let next_header = Protocol::decode(reader, ())?;
        let hdr_ext_len = u8::decode(reader, ())?;

        let data_len = (hdr_ext_len as usize) * 8;
        let mut options = vec![0u8; data_len];
        if data_len > 0 {
            reader.read_into(&mut options)?;
        }

        Ok(Self {
            next_header,
            hdr_ext_len,
            options,
        })
    }
}

/// A Routing header.
///
/// Used by an IPv6 source to list one or more intermediate nodes to be "visited" on the way to a packet's destination.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoutingHeader {
    /// The Next Header field.
    pub next_header: Protocol,
    /// Length of the header in 8-octet units, not including the first 8 octets.
    pub hdr_ext_len: u8,
    /// The routing type.
    pub routing_type: u8,
    /// The number of route segments remaining.
    pub segments_left: u8,
    /// Type-specific data.
    pub data: Vec<u8>,
}

impl Codec for RoutingHeader {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.next_header.encode(writer, ())?;
        self.hdr_ext_len.encode(writer, ())?;
        self.routing_type.encode(writer, ())?;
        self.segments_left.encode(writer, ())?;
        0u32.encode(writer, ())?; // Reserved field (32 bits)
        writer.write_slice(&self.data)
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let next_header = Protocol::decode(reader, ())?;
        let hdr_ext_len = u8::decode(reader, ())?;
        let routing_type = u8::decode(reader, ())?;
        let segments_left = u8::decode(reader, ())?;
        let _reserved = u32::decode(reader, ())?;

        let data_len = (hdr_ext_len as usize) * 8 - 4; // Subtract 4 bytes of reserved field
        let mut data = vec![0u8; data_len];
        if data_len > 0 {
            reader.read_into(&mut data)?;
        }

        Ok(Self {
            next_header,
            hdr_ext_len,
            routing_type,
            segments_left,
            data,
        })
    }
}

/// A Fragment header.
///
/// Used to carry information about fragmented packets.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FragmentHeader {
    /// The Next Header field.
    pub next_header: Protocol,
    /// Reserved field (must be zero).
    pub reserved: u8,
    /// The fragment offset in 8-octet units.
    pub fragment_offset: u16,
    /// More Fragments flag.
    pub m: bool,
    /// The identification value.
    pub identification: u32,
}

impl Codec for FragmentHeader {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.next_header.encode(writer, ())?;
        self.reserved.encode(writer, ())?;

        let frag_off_res_m: u16 = ((self.fragment_offset & 0x1FFF) << 3) | ((self.m as u16) & 0x01);
        frag_off_res_m.encode(writer, ())?;
        self.identification.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let next_header = Protocol::decode(reader, ())?;
        let reserved = u8::decode(reader, ())?;

        let frag_off_res_m = reader.read_u16_be()?;
        let fragment_offset = frag_off_res_m >> 3;
        let m = (frag_off_res_m & 0x01) != 0;

        let identification = reader.read_u32_be()?;

        Ok(Self {
            next_header,
            reserved,
            fragment_offset,
            m,
            identification,
        })
    }
}

/// A Destination Options header.
///
/// Used to carry optional information that needs to be examined only by the packet's destination node.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DestinationOptionsHeader {
    /// The Next Header field.
    pub next_header: Protocol,
    /// Length of the header in 8-octet units, not including the first 8 octets.
    pub hdr_ext_len: u8,
    /// The options data.
    pub options: Vec<u8>,
}

impl Codec for DestinationOptionsHeader {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.next_header.encode(writer, ())?;
        self.hdr_ext_len.encode(writer, ())?;
        writer.write_slice(&self.options)
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let next_header = Protocol::decode(reader, ())?;
        let hdr_ext_len = u8::decode(reader, ())?;

        let data_len = (hdr_ext_len as usize) * 8;
        let mut options = vec![0u8; data_len];
        if data_len > 0 {
            reader.read_into(&mut options)?;
        }

        Ok(Self {
            next_header,
            hdr_ext_len,
            options,
        })
    }
}

#[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 hop_by_hop_options() {
        let etalon_bytes = &[0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // Next: TCP, Len: 1 (8 bytes data), 8 bytes of zeros
        let etalon_struct = HopByHopOptionsHeader {
            next_header: Protocol::TCP,
            hdr_ext_len: 1,
            options: vec![0; 8],
        };
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn routing_header() {
        let etalon_bytes = &[
            0x06, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00,
        ];
        let etalon_struct = RoutingHeader {
            next_header: Protocol::TCP,
            hdr_ext_len: 2, // 16 bytes total, 4 bytes reserved, 12 bytes data
            routing_type: 0,
            segments_left: 1,
            data: vec![0; 12],
        };
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn fragment_header() {
        let etalon_bytes = &[
            0x06, // Next Header: TCP
            0x00, // Reserved
            0x00, 0x01, // Fragment Offset: 0, M: 1 (0x0001)
            0x12, 0x34, 0x56, 0x78, // Identification
        ];
        let etalon_struct = FragmentHeader {
            next_header: Protocol::TCP,
            reserved: 0,
            fragment_offset: 0,
            m: true,
            identification: 0x12345678,
        };
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn destination_options() {
        let etalon_bytes = &[0x06, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
        let etalon_struct = DestinationOptionsHeader {
            next_header: Protocol::TCP,
            hdr_ext_len: 1,
            options: vec![1, 2, 3, 4, 5, 6, 7, 8],
        };
        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }
}