internet 0.1.0

Network library for rust
Documentation
//! IGMPv3 protocol following [RFC 3376].
//!
//! [RFC 3376]: https://datatracker.ietf.org/doc/html/rfc3376

use crate::{
    Buf, BufError, BufMut, BufResult, Codec, Cursor, ietf::igmp::Checksum, ietf::ipv4::Address,
};

/// IGMPv3 Group Record Types following [Section 4.2.4].
///
/// [Section 4.2.4]: https://datatracker.ietf.org/doc/html/rfc3376#section-4.2.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum RecordType {
    /// MODE_IS_INCLUDE (1)
    ModeIsInclude = 1,

    /// MODE_IS_EXCLUDE (2)
    ModeIsExclude = 2,

    /// CHANGE_TO_INCLUDE_MODE (3)
    ChangeToIncludeMode = 3,

    /// CHANGE_TO_EXCLUDE_MODE (4)
    ChangeToExcludeMode = 4,

    /// ALLOW_NEW_SOURCES (5)
    AllowNewSources = 5,

    /// BLOCK_OLD_SOURCES (6)
    BlockOldSources = 6,
}

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

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        match u8::decode(reader, ())? {
            x if x == (Self::ModeIsInclude as u8) => Ok(Self::ModeIsInclude),
            x if x == (Self::ModeIsExclude as u8) => Ok(Self::ModeIsExclude),
            x if x == (Self::ChangeToIncludeMode as u8) => Ok(Self::ChangeToIncludeMode),
            x if x == (Self::ChangeToExcludeMode as u8) => Ok(Self::ChangeToExcludeMode),
            x if x == (Self::AllowNewSources as u8) => Ok(Self::AllowNewSources),
            x if x == (Self::BlockOldSources as u8) => Ok(Self::BlockOldSources),
            _ => Err(BufError::UnexpectedValue),
        }
    }
}

/// An IGMPv3 Group Record following [Section 4.2.4].
///
/// [Section 4.2.4]: https://datatracker.ietf.org/doc/html/rfc3376#section-4.2.4
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GroupRecord {
    /// Type of group record.
    pub record_type: RecordType,
    /// Auxiliary Data length in 32-bit words.
    pub aux_data_len: u8,
    /// Multicast address.
    pub multicast_address: Address,
    /// Source addresses.
    pub source_addresses: Vec<Address>,
    /// Auxiliary data.
    pub auxiliary_data: Vec<u8>,
}

impl Codec for GroupRecord {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.record_type.encode(writer, ())?;
        self.aux_data_len.encode(writer, ())?;
        (self.source_addresses.len() as u16).encode(writer, ())?;
        let mc_bytes: [u8; 4] = self.multicast_address.into();
        mc_bytes.encode(writer, ())?;
        for src in &self.source_addresses {
            let src_bytes: [u8; 4] = (*src).into();
            src_bytes.encode(writer, ())?;
        }
        self.auxiliary_data.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        let record_type = RecordType::decode(reader, ())?;
        let aux_data_len = u8::decode(reader, ())?;
        let num_sources = u16::decode(reader, ())?;
        let multicast_address = Address::from(reader.read_array::<4>()?);

        let mut source_addresses = Vec::with_capacity(num_sources as usize);
        for _ in 0..num_sources {
            source_addresses.push(Address::from(reader.read_array::<4>()?));
        }

        let aux_len_bytes = aux_data_len as usize * 4;
        let mut auxiliary_data = vec![0u8; aux_len_bytes];
        reader.read_into(&mut auxiliary_data)?;

        Ok(Self {
            record_type,
            aux_data_len,
            multicast_address,
            source_addresses,
            auxiliary_data,
        })
    }
}

/// An IGMPv3 Membership Query following [Section 4.1].
///
/// [Section 4.1]: https://datatracker.ietf.org/doc/html/rfc3376#section-4.1
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Query {
    /// Max Response Code.
    pub max_response_code: u8,
    /// The Internet checksum of the entire IGMP message.
    pub checksum: Checksum,
    /// Group Address.
    pub group_address: Address,
    /// Suppress Router-Side Processing flag.
    pub s_flag: bool,
    /// Querier's Robustness Variable.
    pub qrv: u8,
    /// Querier's Query Interval Code.
    pub qqic: u8,
    /// Source addresses.
    pub source_addresses: Vec<Address>,
}

impl Query {
    /// Type field value: 0x11.
    pub const TYPE: u8 = 0x11;
}

impl Codec for Query {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        Self::TYPE.encode(writer, ())?;
        self.max_response_code.encode(writer, ())?;
        self.checksum.encode(writer, ())?;
        let group_bytes: [u8; 4] = self.group_address.into();
        group_bytes.encode(writer, ())?;

        let s_qrv = (if self.s_flag { 1 << 3 } else { 0 }) | (self.qrv & 0x07);
        s_qrv.encode(writer, ())?;
        self.qqic.encode(writer, ())?;

        (self.source_addresses.len() as u16).encode(writer, ())?;
        for src in &self.source_addresses {
            let src_bytes: [u8; 4] = (*src).into();
            src_bytes.encode(writer, ())?;
        }
        Ok(())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        if u8::decode(reader, ())? != Self::TYPE {
            return Err(BufError::UnexpectedValue);
        }
        let max_response_code = u8::decode(reader, ())?;
        let checksum = Checksum::decode(reader, ())?;
        let group_address = Address::from(reader.read_array::<4>()?);

        let s_qrv = u8::decode(reader, ())?;
        let s_flag = (s_qrv & 0x08) != 0;
        let qrv = s_qrv & 0x07;
        let qqic = u8::decode(reader, ())?;

        let num_sources = u16::decode(reader, ())?;
        let mut source_addresses = Vec::with_capacity(num_sources as usize);
        for _ in 0..num_sources {
            source_addresses.push(Address::from(reader.read_array::<4>()?));
        }

        Ok(Self {
            max_response_code,
            checksum,
            group_address,
            s_flag,
            qrv,
            qqic,
            source_addresses,
        })
    }
}

/// An IGMPv3 Membership Report following [Section 4.2].
///
/// [Section 4.2]: https://datatracker.ietf.org/doc/html/rfc3376#section-4.2
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Report {
    /// The Internet checksum of the entire IGMP message.
    pub checksum: Checksum,
    /// Group records.
    pub group_records: Vec<GroupRecord>,
}

impl Report {
    /// Type field value: 0x22.
    pub const TYPE: u8 = 0x22;
}

impl Codec for Report {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        Self::TYPE.encode(writer, ())?;
        0u8.encode(writer, ())?; // Reserved
        self.checksum.encode(writer, ())?;
        0u16.encode(writer, ())?; // Reserved
        (self.group_records.len() as u16).encode(writer, ())?;
        for record in &self.group_records {
            record.encode(writer, ())?;
        }
        Ok(())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        if u8::decode(reader, ())? != Self::TYPE {
            return Err(BufError::UnexpectedValue);
        }
        let _reserved1 = u8::decode(reader, ())?;
        let checksum = Checksum::decode(reader, ())?;
        let _reserved2 = u16::decode(reader, ())?;
        let num_records = u16::decode(reader, ())?;

        let mut group_records = Vec::with_capacity(num_records as usize);
        for _ in 0..num_records {
            group_records.push(GroupRecord::decode(reader, ())?);
        }

        Ok(Self {
            checksum,
            group_records,
        })
    }
}