use core::borrow::Borrow as _;
use core::ops::Deref;
use net_types::ip::{Ip as _, Ipv4, Ipv4Addr};
use net_types::{MulticastAddr, Witness as _};
use packet::records::{ParsedRecord, RecordParseResult, Records, RecordsImpl, RecordsImplLayout};
use packet::{BufferView, FragmentedByteSlice, InnerPacketBuilder, ParsablePacket, ParseMetadata};
use zerocopy::byteorder::network_endian::U16;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
use super::{
peek_message_type, IgmpMessage, IgmpNonEmptyBody, IgmpResponseTimeV2, IgmpResponseTimeV3,
};
use crate::error::{ParseError, UnrecognizedProtocolCode};
use crate::gmp::{GmpReportGroupRecord, InvalidConstraintsError};
use crate::igmp::{IgmpPacketBuilder, MessageType};
create_protocol_enum!(
#[allow(missing_docs)]
#[derive(PartialEq, Copy, Clone)]
pub enum IgmpMessageType: u8 {
MembershipQuery, 0x11, "Membership Query";
MembershipReportV1,0x12, "Membership Report V1";
MembershipReportV2,0x16, "Membership Report V2";
MembershipReportV3,0x22, "Membership Report V3";
LeaveGroup, 0x17, "Leave Group";
}
);
macro_rules! impl_igmp_simple_message_type {
($type:ident, $code:tt, $fixed_header:ident) => {
impl<B> MessageType<B> for $type {
type FixedHeader = $fixed_header;
const TYPE: IgmpMessageType = IgmpMessageType::$code;
type MaxRespTime = ();
declare_no_body!();
}
};
}
macro_rules! declare_no_body {
() => {
type VariableBody = ();
fn parse_body<BV: BufferView<B>>(
_header: &Self::FixedHeader,
bytes: BV,
) -> Result<Self::VariableBody, ParseError>
where
B: SplitByteSlice,
{
if bytes.len() != 0 {
Err(ParseError::NotExpected)
} else {
Ok(())
}
}
fn body_bytes(_body: &Self::VariableBody) -> &[u8]
where
B: SplitByteSlice,
{
&[]
}
};
}
#[derive(Copy, Clone, Debug)]
pub struct IgmpMembershipQueryV2;
impl<B> MessageType<B> for IgmpMembershipQueryV2 {
type FixedHeader = Ipv4Addr;
type MaxRespTime = IgmpResponseTimeV2;
const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
declare_no_body!();
}
impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV2> {
pub fn is_igmpv1_query(&self) -> bool {
self.prefix.max_resp_code == 0
}
}
#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
#[repr(C)]
pub struct MembershipQueryData {
group_address: Ipv4Addr,
sqrv: u8,
qqic: u8,
number_of_sources: U16,
}
impl MembershipQueryData {
const S_FLAG: u8 = (1 << 3);
const QRV_MSK: u8 = 0x07;
pub fn number_of_sources(&self) -> u16 {
self.number_of_sources.get()
}
pub fn group_address(&self) -> Ipv4Addr {
self.group_address
}
pub fn suppress_router_side_processing(&self) -> bool {
(self.sqrv & Self::S_FLAG) != 0
}
pub fn querier_robustness_variable(&self) -> u8 {
self.sqrv & Self::QRV_MSK
}
pub fn querier_query_interval(&self) -> core::time::Duration {
Igmpv3QQIC::from(self.qqic).into()
}
}
pub type Igmpv3QRV = crate::gmp::QRV;
pub type Igmpv3QQIC = crate::gmp::QQIC;
#[derive(Copy, Clone, Debug)]
pub struct IgmpMembershipQueryV3;
impl<B> IgmpNonEmptyBody for Ref<B, [Ipv4Addr]> {}
impl<B> MessageType<B> for IgmpMembershipQueryV3 {
type FixedHeader = MembershipQueryData;
type VariableBody = Ref<B, [Ipv4Addr]>;
type MaxRespTime = IgmpResponseTimeV3;
const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
fn parse_body<BV: BufferView<B>>(
header: &Self::FixedHeader,
mut bytes: BV,
) -> Result<Self::VariableBody, ParseError>
where
B: SplitByteSlice,
{
bytes
.take_slice_front::<Ipv4Addr>(header.number_of_sources() as usize)
.ok_or(ParseError::Format)
}
fn body_bytes(body: &Self::VariableBody) -> &[u8]
where
B: SplitByteSlice,
{
Ref::bytes(body)
}
}
impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV3> {
pub fn as_v2_query(&self) -> IgmpMessage<&[u8], IgmpMembershipQueryV2> {
let Self { prefix, header, body: _ } = self;
let prefix = Ref::from_bytes(prefix.as_bytes()).unwrap();
let (header, _rest) = Ref::from_prefix(header.as_bytes()).unwrap();
IgmpMessage { prefix, header, body: () }
}
}
#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
#[repr(C)]
pub struct MembershipReportV3Data {
_reserved: [u8; 2],
number_of_group_records: U16,
}
impl MembershipReportV3Data {
pub fn number_of_group_records(self) -> u16 {
self.number_of_group_records.get()
}
}
pub type IgmpGroupRecordType = crate::gmp::GroupRecordType;
#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
#[repr(C)]
pub struct GroupRecordHeader {
record_type: u8,
aux_data_len: u8,
number_of_sources: U16,
multicast_address: Ipv4Addr,
}
impl GroupRecordHeader {
pub fn number_of_sources(&self) -> u16 {
self.number_of_sources.get()
}
pub fn record_type(&self) -> Result<IgmpGroupRecordType, UnrecognizedProtocolCode<u8>> {
IgmpGroupRecordType::try_from(self.record_type)
}
pub fn multicast_addr(&self) -> &Ipv4Addr {
&self.multicast_address
}
}
pub struct GroupRecord<B> {
header: Ref<B, GroupRecordHeader>,
sources: Ref<B, [Ipv4Addr]>,
}
impl<B: SplitByteSlice> GroupRecord<B> {
pub fn header(&self) -> &GroupRecordHeader {
self.header.deref()
}
pub fn sources(&self) -> &[Ipv4Addr] {
self.sources.deref()
}
}
#[derive(Copy, Clone, Debug)]
pub struct IgmpMembershipReportV3;
impl<B> IgmpNonEmptyBody for Records<B, IgmpMembershipReportV3> {}
impl<B> MessageType<B> for IgmpMembershipReportV3 {
type FixedHeader = MembershipReportV3Data;
type VariableBody = Records<B, IgmpMembershipReportV3>;
type MaxRespTime = ();
const TYPE: IgmpMessageType = IgmpMessageType::MembershipReportV3;
fn parse_body<BV: BufferView<B>>(
header: &Self::FixedHeader,
bytes: BV,
) -> Result<Self::VariableBody, ParseError>
where
B: SplitByteSlice,
{
Records::parse_with_context(bytes.into_rest(), header.number_of_group_records().into())
}
fn body_bytes(body: &Self::VariableBody) -> &[u8]
where
B: SplitByteSlice,
{
body.bytes()
}
}
impl RecordsImplLayout for IgmpMembershipReportV3 {
type Context = usize;
type Error = ParseError;
}
impl RecordsImpl for IgmpMembershipReportV3 {
type Record<'a> = GroupRecord<&'a [u8]>;
fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
data: &mut BV,
_ctx: &mut usize,
) -> RecordParseResult<GroupRecord<&'a [u8]>, ParseError> {
let header = data
.take_obj_front::<GroupRecordHeader>()
.ok_or_else(debug_err_fn!(ParseError::Format, "Can't take group record header"))?;
let sources = data
.take_slice_front::<Ipv4Addr>(header.number_of_sources().into())
.ok_or_else(debug_err_fn!(ParseError::Format, "Can't group record sources"))?;
let _ = data
.take_front(usize::from(header.aux_data_len) * 4)
.ok_or_else(debug_err_fn!(ParseError::Format, "Can't skip auxiliary data"))?;
Ok(ParsedRecord::Parsed(Self::Record { header, sources }))
}
}
#[derive(Debug)]
pub struct IgmpMembershipReportV1;
impl_igmp_simple_message_type!(IgmpMembershipReportV1, MembershipReportV1, Ipv4Addr);
#[derive(Debug)]
pub struct IgmpMembershipReportV2;
impl_igmp_simple_message_type!(IgmpMembershipReportV2, MembershipReportV2, Ipv4Addr);
#[derive(Debug)]
pub struct IgmpLeaveGroup;
impl_igmp_simple_message_type!(IgmpLeaveGroup, LeaveGroup, Ipv4Addr);
#[allow(missing_docs)]
#[derive(Debug)]
pub enum IgmpPacket<B: SplitByteSlice> {
MembershipQueryV2(IgmpMessage<B, IgmpMembershipQueryV2>),
MembershipQueryV3(IgmpMessage<B, IgmpMembershipQueryV3>),
MembershipReportV1(IgmpMessage<B, IgmpMembershipReportV1>),
MembershipReportV2(IgmpMessage<B, IgmpMembershipReportV2>),
MembershipReportV3(IgmpMessage<B, IgmpMembershipReportV3>),
LeaveGroup(IgmpMessage<B, IgmpLeaveGroup>),
}
impl<B: SplitByteSlice> ParsablePacket<B, ()> for IgmpPacket<B> {
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
use self::IgmpPacket::*;
match self {
MembershipQueryV2(p) => p.parse_metadata(),
MembershipQueryV3(p) => p.parse_metadata(),
MembershipReportV1(p) => p.parse_metadata(),
MembershipReportV2(p) => p.parse_metadata(),
MembershipReportV3(p) => p.parse_metadata(),
LeaveGroup(p) => p.parse_metadata(),
}
}
fn parse<BV: BufferView<B>>(buffer: BV, args: ()) -> Result<Self, ParseError> {
macro_rules! mtch {
($buffer:expr, $args:expr, $( ($code:ident, $long:tt) => $type:ty, $variant:ident )*) => {
match peek_message_type($buffer.as_ref())? {
$( (IgmpMessageType::$code, $long) => {
let packet = <IgmpMessage<B,$type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
IgmpPacket::$variant(packet)
})*,
}
}
}
Ok(mtch!(
buffer,
args,
(MembershipQuery, false) => IgmpMembershipQueryV2, MembershipQueryV2
(MembershipQuery, true) => IgmpMembershipQueryV3, MembershipQueryV3
(MembershipReportV1, _) => IgmpMembershipReportV1, MembershipReportV1
(MembershipReportV2, _) => IgmpMembershipReportV2, MembershipReportV2
(MembershipReportV3, _) => IgmpMembershipReportV3, MembershipReportV3
(LeaveGroup, _) => IgmpLeaveGroup, LeaveGroup
))
}
}
#[derive(Debug)]
pub struct IgmpMembershipQueryV3Builder<I> {
max_resp_time: IgmpResponseTimeV3,
group_addr: Option<MulticastAddr<Ipv4Addr>>,
s_flag: bool,
qrv: Igmpv3QRV,
qqic: Igmpv3QQIC,
sources: I,
}
impl<I> IgmpMembershipQueryV3Builder<I> {
pub fn new(
max_resp_time: IgmpResponseTimeV3,
group_addr: Option<MulticastAddr<Ipv4Addr>>,
s_flag: bool,
qrv: Igmpv3QRV,
qqic: Igmpv3QQIC,
sources: I,
) -> Self {
Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources }
}
const HEADER_SIZE: usize = super::total_header_size::<MembershipQueryData>();
}
impl<I> InnerPacketBuilder for IgmpMembershipQueryV3Builder<I>
where
I: Iterator<Item = Ipv4Addr> + Clone,
{
fn bytes_len(&self) -> usize {
Self::HEADER_SIZE + self.sources.clone().count() * core::mem::size_of::<Ipv4Addr>()
}
fn serialize(&self, buf: &mut [u8]) {
use packet::BufferViewMut;
let Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources } = self;
let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
let mut bytes = &mut body[..];
let mut bytes = &mut bytes;
let mut count: u16 = 0;
for src in sources.clone() {
count = count.checked_add(1).expect("overflowed number of sources");
bytes.write_obj_front(&src).expect("too few bytes for source");
}
let builder = IgmpPacketBuilder::<&mut [u8], IgmpMembershipQueryV3>::new_with_resp_time(
MembershipQueryData {
group_address: group_addr
.as_ref()
.map(|a| a.get())
.unwrap_or(Ipv4::UNSPECIFIED_ADDRESS),
sqrv: (u8::from(*s_flag) << 3) | (MembershipQueryData::QRV_MSK & u8::from(*qrv)),
qqic: (*qqic).into(),
number_of_sources: count.into(),
},
*max_resp_time,
);
builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
}
}
#[derive(Debug)]
pub struct IgmpMembershipReportV3Builder<I> {
groups: I,
}
impl<I> IgmpMembershipReportV3Builder<I> {
pub fn new(groups: I) -> Self {
Self { groups }
}
const HEADER_SIZE: usize = super::total_header_size::<MembershipReportV3Data>();
}
impl<I> IgmpMembershipReportV3Builder<I>
where
I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr> + Clone> + Clone,
{
pub fn with_len_limits(
self,
max_len: usize,
) -> Result<
impl Iterator<
Item = IgmpMembershipReportV3Builder<
impl Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
>,
>,
InvalidConstraintsError,
> {
let Self { groups } = self;
crate::gmp::group_record_split_iterator(
max_len.saturating_sub(Self::HEADER_SIZE),
core::mem::size_of::<GroupRecordHeader>(),
groups,
)
.map(|iter| iter.map(|groups| IgmpMembershipReportV3Builder { groups }))
}
}
impl<I> InnerPacketBuilder for IgmpMembershipReportV3Builder<I>
where
I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
{
fn bytes_len(&self) -> usize {
Self::HEADER_SIZE
+ self
.groups
.clone()
.map(|g| {
core::mem::size_of::<GroupRecordHeader>()
+ g.sources().count() * core::mem::size_of::<Ipv4Addr>()
})
.sum::<usize>()
}
fn serialize(&self, buf: &mut [u8]) {
use packet::BufferViewMut;
let Self { groups } = self;
let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
let mut bytes = &mut body[..];
let mut bytes = &mut bytes;
let mut count: u16 = 0;
for group in groups.clone() {
count = count.checked_add(1).expect("multicast groups count overflows");
let mut header = bytes
.take_obj_front_zero::<GroupRecordHeader>()
.expect("too few bytes for record header");
let GroupRecordHeader {
record_type,
aux_data_len,
number_of_sources,
multicast_address,
} = &mut *header;
*record_type = group.record_type().into();
*aux_data_len = 0;
*multicast_address = group.group().into();
let mut source_count: u16 = 0;
for src in group.sources() {
source_count = source_count.checked_add(1).expect("sources count overflows");
bytes.write_obj_front(src.borrow()).expect("too few bytes for source");
}
*number_of_sources = source_count.into();
}
let builder =
IgmpPacketBuilder::<&mut [u8], IgmpMembershipReportV3>::new(MembershipReportV3Data {
_reserved: [0, 0],
number_of_group_records: count.into(),
});
builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
}
}
#[cfg(test)]
mod tests {
use core::fmt::Debug;
use core::time::Duration;
use packet::{ParseBuffer, Serializer};
use super::*;
use crate::igmp::testdata::*;
use crate::igmp::IgmpMaxRespCode;
use crate::ip::Ipv4Proto;
use crate::ipv4::options::Ipv4Option;
use crate::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
use crate::testutil::set_logger_for_test;
const ALL_BUFFERS: [&[u8]; 6] = [
igmp_router_queries::v2::QUERY,
igmp_router_queries::v3::QUERY,
igmp_reports::v1::MEMBER_REPORT,
igmp_reports::v2::MEMBER_REPORT,
igmp_reports::v3::MEMBER_REPORT,
igmp_leave_group::LEAVE_GROUP,
];
fn serialize_to_bytes<B: SplitByteSlice + Debug, M: MessageType<B> + Debug>(
igmp: &IgmpMessage<B, M>,
) -> Vec<u8>
where
M::VariableBody: IgmpNonEmptyBody,
{
M::body_bytes(&igmp.body)
.into_serializer()
.encapsulate(igmp.builder())
.serialize_vec_outer()
.unwrap()
.as_ref()
.to_vec()
}
fn serialize_to_bytes_inner<
B: SplitByteSlice + Debug,
M: MessageType<B, VariableBody = ()> + Debug,
>(
igmp: &IgmpMessage<B, M>,
) -> Vec<u8> {
igmp.builder().into_serializer().serialize_vec_outer().unwrap().as_ref().to_vec()
}
fn test_parse_and_serialize<
B: SplitByteSlice + Debug,
BV: BufferView<B>,
M: MessageType<B> + Debug,
F: FnOnce(&IgmpMessage<B, M>),
>(
req: BV,
check: F,
) where
M::VariableBody: IgmpNonEmptyBody,
{
let orig_req = req.as_ref().to_owned();
let igmp = IgmpMessage::<_, M>::parse(req, ()).unwrap();
check(&igmp);
let data = serialize_to_bytes(&igmp);
assert_eq!(data, orig_req);
}
fn test_parse_and_serialize_inner<
M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
F: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
>(
mut req: &[u8],
check: F,
) {
let orig_req = req;
let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
check(&igmp);
let data = serialize_to_bytes_inner(&igmp);
assert_eq!(&data[..], orig_req);
}
#[test]
fn membership_query_v2_parse_and_serialize() {
set_logger_for_test();
test_parse_and_serialize_inner::<IgmpMembershipQueryV2, _>(
igmp_router_queries::v2::QUERY,
|igmp| {
assert_eq!(
*igmp.header,
Ipv4Addr::new(igmp_router_queries::v2::HOST_GROUP_ADDRESS)
);
assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v2::MAX_RESP_CODE);
},
);
}
#[test]
fn membership_query_v3_parse_and_serialize() {
set_logger_for_test();
let mut req = igmp_router_queries::v3::QUERY;
test_parse_and_serialize::<_, _, IgmpMembershipQueryV3, _>(&mut req, |igmp| {
assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
assert_eq!(
igmp.header.group_address,
Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)
);
assert_eq!(igmp.header.number_of_sources(), igmp_router_queries::v3::NUMBER_OF_SOURCES);
assert_eq!(
igmp.header.suppress_router_side_processing(),
igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE
);
assert_eq!(igmp.header.querier_robustness_variable(), igmp_router_queries::v3::QRV);
assert_eq!(
igmp.header.querier_query_interval().as_secs() as u32,
igmp_router_queries::v3::QQIC_SECS
);
assert_eq!(igmp.body.len(), igmp_router_queries::v3::NUMBER_OF_SOURCES as usize);
assert_eq!(igmp.body[0], Ipv4Addr::new(igmp_router_queries::v3::SOURCE));
let v2 = igmp.as_v2_query();
assert_eq!(v2.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
assert_eq!(*(v2.header), Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS));
});
}
#[test]
fn membership_report_v3_parse_and_serialize() {
use igmp_reports::v3::*;
set_logger_for_test();
let mut req = MEMBER_REPORT;
test_parse_and_serialize::<_, _, IgmpMembershipReportV3, _>(&mut req, |igmp| {
assert_eq!(igmp.header.number_of_group_records(), NUMBER_OF_RECORDS);
assert_eq!(igmp.prefix.max_resp_code, MAX_RESP_CODE);
let mut iter = igmp.body.iter();
let rec1 = iter.next().unwrap();
assert_eq!(rec1.header().number_of_sources(), NUMBER_OF_SOURCES_1);
assert_eq!(rec1.header().record_type, RECORD_TYPE_1);
assert_eq!(rec1.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_1));
assert_eq!(rec1.header().record_type(), Ok(IgmpGroupRecordType::ModeIsInclude));
assert_eq!(rec1.sources().len(), NUMBER_OF_SOURCES_1 as usize);
assert_eq!(rec1.sources()[0], Ipv4Addr::new(SRC_1_1));
assert_eq!(rec1.sources()[1], Ipv4Addr::new(SRC_1_2));
let rec2 = iter.next().unwrap();
assert_eq!(rec2.header().number_of_sources(), NUMBER_OF_SOURCES_2);
assert_eq!(rec2.header().record_type, RECORD_TYPE_2);
assert_eq!(rec2.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_2));
assert_eq!(rec2.header().record_type(), Ok(IgmpGroupRecordType::ModeIsExclude));
assert_eq!(rec2.sources().len(), NUMBER_OF_SOURCES_2 as usize);
assert_eq!(rec2.sources()[0], Ipv4Addr::new(SRC_2_1));
assert_eq!(iter.next().is_none(), true);
});
}
#[test]
fn membership_query_v3_builder() {
set_logger_for_test();
let builder = IgmpMembershipQueryV3Builder::new(
IgmpResponseTimeV3::from_code(igmp_router_queries::v3::MAX_RESP_CODE),
Some(
MulticastAddr::new(Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)).unwrap(),
),
igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE,
Igmpv3QRV::new(igmp_router_queries::v3::QRV),
Igmpv3QQIC::new_exact(Duration::from_secs(igmp_router_queries::v3::QQIC_SECS.into()))
.unwrap(),
[Ipv4Addr::new(igmp_router_queries::v3::SOURCE)].into_iter(),
);
let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
}
#[test]
fn membership_report_v3_builder() {
set_logger_for_test();
use igmp_reports::v3::*;
let builder = IgmpMembershipReportV3Builder::new(
[
(MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
(MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
]
.into_iter()
.map(|(addr, rec_type, sources)| {
(
MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
IgmpGroupRecordType::try_from(rec_type).unwrap(),
sources.into_iter().copied().map(Ipv4Addr::new),
)
}),
);
let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
assert_eq!(serialized.as_ref(), MEMBER_REPORT);
}
#[test]
fn membership_report_v3_split_many_sources() {
use igmp_reports::v3::*;
use packet::PacketBuilder;
const ETH_MTU: usize = 1500;
const MAX_SOURCES: usize = 365;
let src = Ipv4Addr::new(SRC_1_1);
let ip_builder = Ipv4PacketBuilderWithOptions::new(
Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
&[Ipv4Option::RouterAlert { data: 0 }],
)
.unwrap();
let ip_header = ip_builder.constraints().header_len();
let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
let reports = IgmpMembershipReportV3Builder::new(
[(
group_addr,
IgmpGroupRecordType::ModeIsInclude,
(0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
)]
.into_iter(),
)
.with_len_limits(ETH_MTU - ip_header)
.unwrap();
let mut reports = reports.map(|builder| {
builder
.into_serializer()
.encapsulate(ip_builder.clone())
.serialize_vec_outer()
.unwrap_or_else(|(err, _)| panic!("{err:?}"))
.unwrap_b()
.into_inner()
});
let serialized = reports.next().unwrap();
assert_eq!(serialized.len(), ETH_MTU);
let mut buffer = &serialized[..];
let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
let mut groups = igmp.body.iter();
let group = groups.next().expect("has group");
assert_eq!(group.header.multicast_address, group_addr.get());
assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
assert_eq!(group.sources().len(), MAX_SOURCES);
for (i, addr) in group.sources().iter().enumerate() {
assert_eq!(*addr, src_ip(i));
}
assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
assert_eq!(reports.next(), None);
let reports = IgmpMembershipReportV3Builder::new(
[(
group_addr,
IgmpGroupRecordType::ModeIsInclude,
core::iter::repeat(src).take(MAX_SOURCES + 1),
)]
.into_iter(),
)
.with_len_limits(ETH_MTU - ip_header)
.unwrap();
assert_eq!(
reports
.map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
.collect::<Vec<_>>(),
vec![vec![MAX_SOURCES], vec![1]]
);
}
#[test]
fn membership_report_v3_split_many_groups() {
use igmp_reports::v3::*;
use packet::PacketBuilder;
const ETH_MTU: usize = 1500;
const EXPECT_SERIALIZED: usize = 1496;
const MAX_GROUPS: usize = 183;
let src = Ipv4Addr::new(SRC_1_1);
let ip_builder = Ipv4PacketBuilderWithOptions::new(
Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
&[Ipv4Option::RouterAlert { data: 0 }],
)
.unwrap();
let ip_header = ip_builder.constraints().header_len();
let group_ip = |i: usize| {
MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
};
let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
(group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
}))
.with_len_limits(ETH_MTU - ip_header)
.unwrap();
let mut reports = reports.map(|builder| {
builder
.into_serializer()
.encapsulate(ip_builder.clone())
.serialize_vec_outer()
.unwrap_or_else(|(err, _)| panic!("{err:?}"))
.unwrap_b()
.into_inner()
});
let serialized = reports.next().unwrap();
assert_eq!(serialized.len(), EXPECT_SERIALIZED);
let mut buffer = &serialized[..];
let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
for (i, group) in igmp.body.iter().enumerate() {
assert_eq!(group.header.multicast_address, group_ip(i).get());
assert_eq!(group.header.number_of_sources.get(), 0);
}
assert_eq!(reports.next(), None);
let reports =
IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
(group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
}))
.with_len_limits(ETH_MTU - ip_header)
.unwrap();
assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
}
#[test]
fn membership_report_v1_parse_and_serialize() {
use igmp_reports::v1;
set_logger_for_test();
test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
});
}
#[test]
fn membership_report_v2_parse_and_serialize() {
use igmp_reports::v2;
set_logger_for_test();
test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
});
}
#[test]
fn leave_group_parse_and_serialize() {
set_logger_for_test();
test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
igmp_leave_group::LEAVE_GROUP,
|igmp| {
assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
},
);
}
#[test]
fn test_unknown_type() {
let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
let mut buff = buff.as_mut_slice();
let packet = buff.parse_with::<_, IgmpPacket<_>>(());
assert_eq!(packet.is_err(), true);
}
#[test]
fn test_full_parses() {
let mut bufs = ALL_BUFFERS.to_vec();
for buff in bufs.iter_mut() {
let orig_req = &buff[..];
let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
let msg_type = match packet {
IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
};
assert_eq!(msg_type, orig_req[0]);
}
}
#[test]
fn test_partial_parses() {
for buff in ALL_BUFFERS.iter() {
for i in 0..buff.len() {
let partial_buff = &mut &buff[0..i];
let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
assert_eq!(packet.is_err(), true)
}
}
}
fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
mut ground_truth: &[u8],
) {
let ground_truth_len = ground_truth.len();
let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
let builder_len = igmp.builder().bytes_len();
assert_eq!(builder_len, ground_truth_len);
}
#[test]
fn test_igmp_packet_length() {
assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
}
}