1use core::borrow::Borrow as _;
8use core::ops::Deref;
9
10use net_types::ip::{Ip as _, Ipv4, Ipv4Addr};
11use net_types::{MulticastAddr, Witness as _};
12use packet::records::{ParsedRecord, RecordParseResult, Records, RecordsImpl, RecordsImplLayout};
13use packet::{BufferView, FragmentedByteSlice, InnerPacketBuilder, ParsablePacket, ParseMetadata};
14use zerocopy::byteorder::network_endian::U16;
15use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
16
17use super::{
18 peek_message_type, IgmpMessage, IgmpNonEmptyBody, IgmpResponseTimeV2, IgmpResponseTimeV3,
19};
20use crate::error::{ParseError, UnrecognizedProtocolCode};
21use crate::gmp::{GmpReportGroupRecord, InvalidConstraintsError};
22use crate::igmp::{IgmpPacketBuilder, MessageType};
23
24create_protocol_enum!(
25 #[allow(missing_docs)]
27 #[derive(PartialEq, Copy, Clone)]
28 pub enum IgmpMessageType: u8 {
29 MembershipQuery, 0x11, "Membership Query";
30 MembershipReportV1,0x12, "Membership Report V1";
31 MembershipReportV2,0x16, "Membership Report V2";
32 MembershipReportV3,0x22, "Membership Report V3";
33 LeaveGroup, 0x17, "Leave Group";
34 }
35);
36
37macro_rules! impl_igmp_simple_message_type {
38 ($type:ident, $code:tt, $fixed_header:ident) => {
39 impl<B> MessageType<B> for $type {
40 type FixedHeader = $fixed_header;
41 const TYPE: IgmpMessageType = IgmpMessageType::$code;
42 type MaxRespTime = ();
43 declare_no_body!();
44 }
45 };
46}
47
48macro_rules! declare_no_body {
49 () => {
50 type VariableBody = ();
51
52 fn parse_body<BV: BufferView<B>>(
53 _header: &Self::FixedHeader,
54 bytes: BV,
55 ) -> Result<Self::VariableBody, ParseError>
56 where
57 B: SplitByteSlice,
58 {
59 if bytes.len() != 0 {
60 Err(ParseError::NotExpected)
61 } else {
62 Ok(())
63 }
64 }
65
66 fn body_bytes(_body: &Self::VariableBody) -> &[u8]
67 where
68 B: SplitByteSlice,
69 {
70 &[]
71 }
72 };
73}
74
75#[derive(Copy, Clone, Debug)]
92pub struct IgmpMembershipQueryV2;
93
94impl<B> MessageType<B> for IgmpMembershipQueryV2 {
95 type FixedHeader = Ipv4Addr;
96 type MaxRespTime = IgmpResponseTimeV2;
97 const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
98
99 declare_no_body!();
100}
101
102impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV2> {
103 pub fn is_igmpv1_query(&self) -> bool {
113 self.prefix.max_resp_code == 0
114 }
115}
116
117#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
123#[repr(C)]
124pub struct MembershipQueryData {
125 group_address: Ipv4Addr,
126 sqrv: u8,
127 qqic: u8,
128 number_of_sources: U16,
129}
130
131impl MembershipQueryData {
132 const S_FLAG: u8 = (1 << 3);
133 const QRV_MSK: u8 = 0x07;
134
135 pub fn number_of_sources(&self) -> u16 {
137 self.number_of_sources.get()
138 }
139
140 pub fn group_address(&self) -> Ipv4Addr {
142 self.group_address
143 }
144
145 pub fn suppress_router_side_processing(&self) -> bool {
153 (self.sqrv & Self::S_FLAG) != 0
154 }
155
156 pub fn querier_robustness_variable(&self) -> u8 {
170 self.sqrv & Self::QRV_MSK
171 }
172
173 pub fn querier_query_interval(&self) -> core::time::Duration {
184 Igmpv3QQIC::from(self.qqic).into()
187 }
188}
189
190pub type Igmpv3QRV = crate::gmp::QRV;
198
199pub type Igmpv3QQIC = crate::gmp::QQIC;
206
207#[derive(Copy, Clone, Debug)]
218pub struct IgmpMembershipQueryV3;
219
220impl<B> IgmpNonEmptyBody for Ref<B, [Ipv4Addr]> {}
221
222impl<B> MessageType<B> for IgmpMembershipQueryV3 {
223 type FixedHeader = MembershipQueryData;
224 type VariableBody = Ref<B, [Ipv4Addr]>;
225 type MaxRespTime = IgmpResponseTimeV3;
226 const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
227
228 fn parse_body<BV: BufferView<B>>(
229 header: &Self::FixedHeader,
230 mut bytes: BV,
231 ) -> Result<Self::VariableBody, ParseError>
232 where
233 B: SplitByteSlice,
234 {
235 bytes
236 .take_slice_front::<Ipv4Addr>(header.number_of_sources() as usize)
237 .ok_or(ParseError::Format)
238 }
239
240 fn body_bytes(body: &Self::VariableBody) -> &[u8]
241 where
242 B: SplitByteSlice,
243 {
244 Ref::bytes(body)
245 }
246}
247
248impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV3> {
249 pub fn as_v2_query(&self) -> IgmpMessage<&[u8], IgmpMembershipQueryV2> {
263 let Self { prefix, header, body: _ } = self;
264 let prefix = Ref::from_bytes(prefix.as_bytes()).unwrap();
266 let (header, _rest) = Ref::from_prefix(header.as_bytes()).unwrap();
267 IgmpMessage { prefix, header, body: () }
268 }
269}
270
271#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
277#[repr(C)]
278pub struct MembershipReportV3Data {
279 _reserved: [u8; 2],
280 number_of_group_records: U16,
281}
282
283impl MembershipReportV3Data {
284 pub fn number_of_group_records(self) -> u16 {
286 self.number_of_group_records.get()
287 }
288}
289
290pub type IgmpGroupRecordType = crate::gmp::GroupRecordType;
297
298#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
307#[repr(C)]
308pub struct GroupRecordHeader {
309 record_type: u8,
310 aux_data_len: u8,
311 number_of_sources: U16,
312 multicast_address: Ipv4Addr,
313}
314
315impl GroupRecordHeader {
316 pub fn number_of_sources(&self) -> u16 {
318 self.number_of_sources.get()
319 }
320
321 pub fn record_type(&self) -> Result<IgmpGroupRecordType, UnrecognizedProtocolCode<u8>> {
323 IgmpGroupRecordType::try_from(self.record_type)
324 }
325
326 pub fn multicast_addr(&self) -> &Ipv4Addr {
328 &self.multicast_address
329 }
330}
331
332pub struct GroupRecord<B> {
348 header: Ref<B, GroupRecordHeader>,
349 sources: Ref<B, [Ipv4Addr]>,
350}
351
352impl<B: SplitByteSlice> GroupRecord<B> {
353 pub fn header(&self) -> &GroupRecordHeader {
355 self.header.deref()
356 }
357
358 pub fn sources(&self) -> &[Ipv4Addr] {
360 self.sources.deref()
361 }
362}
363
364#[derive(Copy, Clone, Debug)]
376pub struct IgmpMembershipReportV3;
377
378impl<B> IgmpNonEmptyBody for Records<B, IgmpMembershipReportV3> {}
379
380impl<B> MessageType<B> for IgmpMembershipReportV3 {
381 type FixedHeader = MembershipReportV3Data;
382 type VariableBody = Records<B, IgmpMembershipReportV3>;
383 type MaxRespTime = ();
384 const TYPE: IgmpMessageType = IgmpMessageType::MembershipReportV3;
385
386 fn parse_body<BV: BufferView<B>>(
387 header: &Self::FixedHeader,
388 bytes: BV,
389 ) -> Result<Self::VariableBody, ParseError>
390 where
391 B: SplitByteSlice,
392 {
393 Records::parse_with_context(bytes.into_rest(), header.number_of_group_records().into())
394 }
395
396 fn body_bytes(body: &Self::VariableBody) -> &[u8]
397 where
398 B: SplitByteSlice,
399 {
400 body.bytes()
401 }
402}
403
404impl RecordsImplLayout for IgmpMembershipReportV3 {
405 type Context = usize;
406 type Error = ParseError;
407}
408
409impl RecordsImpl for IgmpMembershipReportV3 {
410 type Record<'a> = GroupRecord<&'a [u8]>;
411
412 fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
413 data: &mut BV,
414 _ctx: &mut usize,
415 ) -> RecordParseResult<GroupRecord<&'a [u8]>, ParseError> {
416 let header = data
417 .take_obj_front::<GroupRecordHeader>()
418 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't take group record header"))?;
419 let sources = data
420 .take_slice_front::<Ipv4Addr>(header.number_of_sources().into())
421 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't group record sources"))?;
422 let _ = data
425 .take_front(usize::from(header.aux_data_len) * 4)
426 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't skip auxiliary data"))?;
427
428 Ok(ParsedRecord::Parsed(Self::Record { header, sources }))
429 }
430}
431
432#[derive(Debug)]
446pub struct IgmpMembershipReportV1;
447
448impl_igmp_simple_message_type!(IgmpMembershipReportV1, MembershipReportV1, Ipv4Addr);
449
450#[derive(Debug)]
465pub struct IgmpMembershipReportV2;
466
467impl_igmp_simple_message_type!(IgmpMembershipReportV2, MembershipReportV2, Ipv4Addr);
468
469#[derive(Debug)]
484pub struct IgmpLeaveGroup;
485
486impl_igmp_simple_message_type!(IgmpLeaveGroup, LeaveGroup, Ipv4Addr);
487
488#[allow(missing_docs)]
495#[derive(Debug)]
496pub enum IgmpPacket<B: SplitByteSlice> {
497 MembershipQueryV2(IgmpMessage<B, IgmpMembershipQueryV2>),
498 MembershipQueryV3(IgmpMessage<B, IgmpMembershipQueryV3>),
499 MembershipReportV1(IgmpMessage<B, IgmpMembershipReportV1>),
500 MembershipReportV2(IgmpMessage<B, IgmpMembershipReportV2>),
501 MembershipReportV3(IgmpMessage<B, IgmpMembershipReportV3>),
502 LeaveGroup(IgmpMessage<B, IgmpLeaveGroup>),
503}
504
505impl<B: SplitByteSlice> ParsablePacket<B, ()> for IgmpPacket<B> {
506 type Error = ParseError;
507
508 fn parse_metadata(&self) -> ParseMetadata {
509 use self::IgmpPacket::*;
510 match self {
511 MembershipQueryV2(p) => p.parse_metadata(),
512 MembershipQueryV3(p) => p.parse_metadata(),
513 MembershipReportV1(p) => p.parse_metadata(),
514 MembershipReportV2(p) => p.parse_metadata(),
515 MembershipReportV3(p) => p.parse_metadata(),
516 LeaveGroup(p) => p.parse_metadata(),
517 }
518 }
519
520 fn parse<BV: BufferView<B>>(buffer: BV, args: ()) -> Result<Self, ParseError> {
521 macro_rules! mtch {
522 ($buffer:expr, $args:expr, $( ($code:ident, $long:tt) => $type:ty, $variant:ident )*) => {
523 match peek_message_type($buffer.as_ref())? {
524 $( (IgmpMessageType::$code, $long) => {
525 let packet = <IgmpMessage<B,$type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
526 IgmpPacket::$variant(packet)
527 })*,
528 }
529 }
530 }
531
532 Ok(mtch!(
533 buffer,
534 args,
535 (MembershipQuery, false) => IgmpMembershipQueryV2, MembershipQueryV2
536 (MembershipQuery, true) => IgmpMembershipQueryV3, MembershipQueryV3
537 (MembershipReportV1, _) => IgmpMembershipReportV1, MembershipReportV1
538 (MembershipReportV2, _) => IgmpMembershipReportV2, MembershipReportV2
539 (MembershipReportV3, _) => IgmpMembershipReportV3, MembershipReportV3
540 (LeaveGroup, _) => IgmpLeaveGroup, LeaveGroup
541 ))
542 }
543}
544
545#[derive(Debug)]
551pub struct IgmpMembershipQueryV3Builder<I> {
552 max_resp_time: IgmpResponseTimeV3,
553 group_addr: Option<MulticastAddr<Ipv4Addr>>,
554 s_flag: bool,
555 qrv: Igmpv3QRV,
556 qqic: Igmpv3QQIC,
557 sources: I,
558}
559
560impl<I> IgmpMembershipQueryV3Builder<I> {
561 pub fn new(
563 max_resp_time: IgmpResponseTimeV3,
564 group_addr: Option<MulticastAddr<Ipv4Addr>>,
565 s_flag: bool,
566 qrv: Igmpv3QRV,
567 qqic: Igmpv3QQIC,
568 sources: I,
569 ) -> Self {
570 Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources }
571 }
572
573 const HEADER_SIZE: usize = super::total_header_size::<MembershipQueryData>();
574}
575
576impl<I> InnerPacketBuilder for IgmpMembershipQueryV3Builder<I>
577where
578 I: Iterator<Item = Ipv4Addr> + Clone,
579{
580 fn bytes_len(&self) -> usize {
581 Self::HEADER_SIZE + self.sources.clone().count() * core::mem::size_of::<Ipv4Addr>()
582 }
583
584 fn serialize(&self, buf: &mut [u8]) {
585 use packet::BufferViewMut;
586
587 let Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources } = self;
588 let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
589 let mut bytes = &mut body[..];
590 let mut bytes = &mut bytes;
591 let mut count: u16 = 0;
593 for src in sources.clone() {
594 count = count.checked_add(1).expect("overflowed number of sources");
595 bytes.write_obj_front(&src).expect("too few bytes for source");
596 }
597 let builder = IgmpPacketBuilder::<&mut [u8], IgmpMembershipQueryV3>::new_with_resp_time(
598 MembershipQueryData {
599 group_address: group_addr
600 .as_ref()
601 .map(|a| a.get())
602 .unwrap_or(Ipv4::UNSPECIFIED_ADDRESS),
603 sqrv: (u8::from(*s_flag) << 3) | (MembershipQueryData::QRV_MSK & u8::from(*qrv)),
604 qqic: (*qqic).into(),
605 number_of_sources: count.into(),
606 },
607 *max_resp_time,
608 );
609 builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
610 }
611}
612
613#[derive(Debug)]
619pub struct IgmpMembershipReportV3Builder<I> {
620 groups: I,
621}
622
623impl<I> IgmpMembershipReportV3Builder<I> {
624 pub fn new(groups: I) -> Self {
626 Self { groups }
627 }
628
629 const HEADER_SIZE: usize = super::total_header_size::<MembershipReportV3Data>();
630}
631
632impl<I> IgmpMembershipReportV3Builder<I>
633where
634 I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr> + Clone> + Clone,
635{
636 pub fn with_len_limits(
648 self,
649 max_len: usize,
650 ) -> Result<
651 impl Iterator<
652 Item = IgmpMembershipReportV3Builder<
653 impl Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
654 >,
655 >,
656 InvalidConstraintsError,
657 > {
658 let Self { groups } = self;
659 crate::gmp::group_record_split_iterator(
660 max_len.saturating_sub(Self::HEADER_SIZE),
661 core::mem::size_of::<GroupRecordHeader>(),
662 groups,
663 )
664 .map(|iter| iter.map(|groups| IgmpMembershipReportV3Builder { groups }))
665 }
666}
667
668impl<I> InnerPacketBuilder for IgmpMembershipReportV3Builder<I>
669where
670 I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
671{
672 fn bytes_len(&self) -> usize {
673 Self::HEADER_SIZE
674 + self
675 .groups
676 .clone()
677 .map(|g| {
678 core::mem::size_of::<GroupRecordHeader>()
679 + g.sources().count() * core::mem::size_of::<Ipv4Addr>()
680 })
681 .sum::<usize>()
682 }
683
684 fn serialize(&self, buf: &mut [u8]) {
685 use packet::BufferViewMut;
686
687 let Self { groups } = self;
688 let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
689 let mut bytes = &mut body[..];
690 let mut bytes = &mut bytes;
691 let mut count: u16 = 0;
693 for group in groups.clone() {
694 count = count.checked_add(1).expect("multicast groups count overflows");
695 let mut header = bytes
696 .take_obj_front_zero::<GroupRecordHeader>()
697 .expect("too few bytes for record header");
698 let GroupRecordHeader {
699 record_type,
700 aux_data_len,
701 number_of_sources,
702 multicast_address,
703 } = &mut *header;
704 *record_type = group.record_type().into();
705 *aux_data_len = 0;
706 *multicast_address = group.group().into();
707 let mut source_count: u16 = 0;
708 for src in group.sources() {
709 source_count = source_count.checked_add(1).expect("sources count overflows");
710 bytes.write_obj_front(src.borrow()).expect("too few bytes for source");
711 }
712 *number_of_sources = source_count.into();
713 }
714
715 let builder =
716 IgmpPacketBuilder::<&mut [u8], IgmpMembershipReportV3>::new(MembershipReportV3Data {
717 _reserved: [0, 0],
718 number_of_group_records: count.into(),
719 });
720 builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
721 }
722}
723
724#[cfg(test)]
725mod tests {
726 use core::fmt::Debug;
727 use core::time::Duration;
728
729 use packet::{ParseBuffer, Serializer};
730
731 use super::*;
732 use crate::igmp::testdata::*;
733 use crate::igmp::IgmpMaxRespCode;
734 use crate::ip::Ipv4Proto;
735 use crate::ipv4::options::Ipv4Option;
736 use crate::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
737 use crate::testutil::set_logger_for_test;
738
739 const ALL_BUFFERS: [&[u8]; 6] = [
740 igmp_router_queries::v2::QUERY,
741 igmp_router_queries::v3::QUERY,
742 igmp_reports::v1::MEMBER_REPORT,
743 igmp_reports::v2::MEMBER_REPORT,
744 igmp_reports::v3::MEMBER_REPORT,
745 igmp_leave_group::LEAVE_GROUP,
746 ];
747
748 fn serialize_to_bytes<B: SplitByteSlice + Debug, M: MessageType<B> + Debug>(
749 igmp: &IgmpMessage<B, M>,
750 ) -> Vec<u8>
751 where
752 M::VariableBody: IgmpNonEmptyBody,
753 {
754 M::body_bytes(&igmp.body)
755 .into_serializer()
756 .encapsulate(igmp.builder())
757 .serialize_vec_outer()
758 .unwrap()
759 .as_ref()
760 .to_vec()
761 }
762
763 fn serialize_to_bytes_inner<
764 B: SplitByteSlice + Debug,
765 M: MessageType<B, VariableBody = ()> + Debug,
766 >(
767 igmp: &IgmpMessage<B, M>,
768 ) -> Vec<u8> {
769 igmp.builder().into_serializer().serialize_vec_outer().unwrap().as_ref().to_vec()
770 }
771
772 fn test_parse_and_serialize<
773 B: SplitByteSlice + Debug,
774 BV: BufferView<B>,
775 M: MessageType<B> + Debug,
776 F: FnOnce(&IgmpMessage<B, M>),
777 >(
778 req: BV,
779 check: F,
780 ) where
781 M::VariableBody: IgmpNonEmptyBody,
782 {
783 let orig_req = req.as_ref().to_owned();
784
785 let igmp = IgmpMessage::<_, M>::parse(req, ()).unwrap();
786 check(&igmp);
787
788 let data = serialize_to_bytes(&igmp);
789 assert_eq!(data, orig_req);
790 }
791
792 fn test_parse_and_serialize_inner<
793 M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
794 F: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
795 >(
796 mut req: &[u8],
797 check: F,
798 ) {
799 let orig_req = req;
800
801 let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
802 check(&igmp);
803
804 let data = serialize_to_bytes_inner(&igmp);
805 assert_eq!(&data[..], orig_req);
806 }
807
808 #[test]
809 fn membership_query_v2_parse_and_serialize() {
810 set_logger_for_test();
811 test_parse_and_serialize_inner::<IgmpMembershipQueryV2, _>(
812 igmp_router_queries::v2::QUERY,
813 |igmp| {
814 assert_eq!(
815 *igmp.header,
816 Ipv4Addr::new(igmp_router_queries::v2::HOST_GROUP_ADDRESS)
817 );
818 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v2::MAX_RESP_CODE);
819 },
820 );
821 }
822
823 #[test]
824 fn membership_query_v3_parse_and_serialize() {
825 set_logger_for_test();
826 let mut req = igmp_router_queries::v3::QUERY;
827 test_parse_and_serialize::<_, _, IgmpMembershipQueryV3, _>(&mut req, |igmp| {
828 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
829 assert_eq!(
830 igmp.header.group_address,
831 Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)
832 );
833 assert_eq!(igmp.header.number_of_sources(), igmp_router_queries::v3::NUMBER_OF_SOURCES);
834 assert_eq!(
835 igmp.header.suppress_router_side_processing(),
836 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE
837 );
838 assert_eq!(igmp.header.querier_robustness_variable(), igmp_router_queries::v3::QRV);
839 assert_eq!(
840 igmp.header.querier_query_interval().as_secs() as u32,
841 igmp_router_queries::v3::QQIC_SECS
842 );
843 assert_eq!(igmp.body.len(), igmp_router_queries::v3::NUMBER_OF_SOURCES as usize);
844 assert_eq!(igmp.body[0], Ipv4Addr::new(igmp_router_queries::v3::SOURCE));
845
846 let v2 = igmp.as_v2_query();
848 assert_eq!(v2.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
849 assert_eq!(*(v2.header), Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS));
850 });
851 }
852
853 #[test]
854 fn membership_report_v3_parse_and_serialize() {
855 use igmp_reports::v3::*;
856
857 set_logger_for_test();
858 let mut req = MEMBER_REPORT;
859 test_parse_and_serialize::<_, _, IgmpMembershipReportV3, _>(&mut req, |igmp| {
860 assert_eq!(igmp.header.number_of_group_records(), NUMBER_OF_RECORDS);
861 assert_eq!(igmp.prefix.max_resp_code, MAX_RESP_CODE);
862 let mut iter = igmp.body.iter();
863 let rec1 = iter.next().unwrap();
865 assert_eq!(rec1.header().number_of_sources(), NUMBER_OF_SOURCES_1);
866 assert_eq!(rec1.header().record_type, RECORD_TYPE_1);
867 assert_eq!(rec1.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_1));
868 assert_eq!(rec1.header().record_type(), Ok(IgmpGroupRecordType::ModeIsInclude));
869 assert_eq!(rec1.sources().len(), NUMBER_OF_SOURCES_1 as usize);
870 assert_eq!(rec1.sources()[0], Ipv4Addr::new(SRC_1_1));
871 assert_eq!(rec1.sources()[1], Ipv4Addr::new(SRC_1_2));
872
873 let rec2 = iter.next().unwrap();
875 assert_eq!(rec2.header().number_of_sources(), NUMBER_OF_SOURCES_2);
876 assert_eq!(rec2.header().record_type, RECORD_TYPE_2);
877 assert_eq!(rec2.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_2));
878 assert_eq!(rec2.header().record_type(), Ok(IgmpGroupRecordType::ModeIsExclude));
879 assert_eq!(rec2.sources().len(), NUMBER_OF_SOURCES_2 as usize);
880 assert_eq!(rec2.sources()[0], Ipv4Addr::new(SRC_2_1));
881
882 assert_eq!(iter.next().is_none(), true);
884 });
885 }
886
887 #[test]
888 fn membership_query_v3_builder() {
889 set_logger_for_test();
890 let builder = IgmpMembershipQueryV3Builder::new(
891 IgmpResponseTimeV3::from_code(igmp_router_queries::v3::MAX_RESP_CODE),
892 Some(
893 MulticastAddr::new(Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)).unwrap(),
894 ),
895 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE,
896 Igmpv3QRV::new(igmp_router_queries::v3::QRV),
897 Igmpv3QQIC::new_exact(Duration::from_secs(igmp_router_queries::v3::QQIC_SECS.into()))
898 .unwrap(),
899 [Ipv4Addr::new(igmp_router_queries::v3::SOURCE)].into_iter(),
900 );
901 let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
902 assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
903 }
904
905 #[test]
906 fn membership_report_v3_builder() {
907 set_logger_for_test();
908 use igmp_reports::v3::*;
909 let builder = IgmpMembershipReportV3Builder::new(
910 [
911 (MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
912 (MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
913 ]
914 .into_iter()
915 .map(|(addr, rec_type, sources)| {
916 (
917 MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
918 IgmpGroupRecordType::try_from(rec_type).unwrap(),
919 sources.into_iter().copied().map(Ipv4Addr::new),
920 )
921 }),
922 );
923 let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
924 assert_eq!(serialized.as_ref(), MEMBER_REPORT);
925 }
926
927 #[test]
939 fn membership_report_v3_split_many_sources() {
940 use igmp_reports::v3::*;
941 use packet::PacketBuilder;
942 const ETH_MTU: usize = 1500;
943 const MAX_SOURCES: usize = 365;
944
945 let src = Ipv4Addr::new(SRC_1_1);
946 let ip_builder = Ipv4PacketBuilderWithOptions::new(
947 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
948 &[Ipv4Option::RouterAlert { data: 0 }],
949 )
950 .unwrap();
951 let ip_header = ip_builder.constraints().header_len();
952
953 let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
954 let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
955 let reports = IgmpMembershipReportV3Builder::new(
956 [(
957 group_addr,
958 IgmpGroupRecordType::ModeIsInclude,
959 (0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
960 )]
961 .into_iter(),
962 )
963 .with_len_limits(ETH_MTU - ip_header)
964 .unwrap();
965
966 let mut reports = reports.map(|builder| {
967 builder
968 .into_serializer()
969 .encapsulate(ip_builder.clone())
970 .serialize_vec_outer()
971 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
972 .unwrap_b()
973 .into_inner()
974 });
975 let serialized = reports.next().unwrap();
977 assert_eq!(serialized.len(), ETH_MTU);
978
979 let mut buffer = &serialized[..];
980 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
981 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
982 let mut groups = igmp.body.iter();
983 let group = groups.next().expect("has group");
984 assert_eq!(group.header.multicast_address, group_addr.get());
985 assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
986 assert_eq!(group.sources().len(), MAX_SOURCES);
987 for (i, addr) in group.sources().iter().enumerate() {
988 assert_eq!(*addr, src_ip(i));
989 }
990 assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
991
992 assert_eq!(reports.next(), None);
994
995 let reports = IgmpMembershipReportV3Builder::new(
996 [(
997 group_addr,
998 IgmpGroupRecordType::ModeIsInclude,
999 core::iter::repeat(src).take(MAX_SOURCES + 1),
1000 )]
1001 .into_iter(),
1002 )
1003 .with_len_limits(ETH_MTU - ip_header)
1004 .unwrap();
1005 assert_eq!(
1007 reports
1008 .map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
1009 .collect::<Vec<_>>(),
1010 vec![vec![MAX_SOURCES], vec![1]]
1011 );
1012 }
1013
1014 #[test]
1025 fn membership_report_v3_split_many_groups() {
1026 use igmp_reports::v3::*;
1027 use packet::PacketBuilder;
1028
1029 const ETH_MTU: usize = 1500;
1030 const EXPECT_SERIALIZED: usize = 1496;
1031 const MAX_GROUPS: usize = 183;
1032
1033 let src = Ipv4Addr::new(SRC_1_1);
1034 let ip_builder = Ipv4PacketBuilderWithOptions::new(
1035 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
1036 &[Ipv4Option::RouterAlert { data: 0 }],
1037 )
1038 .unwrap();
1039 let ip_header = ip_builder.constraints().header_len();
1040
1041 let group_ip = |i: usize| {
1042 MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
1043 };
1044 let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
1045 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1046 }))
1047 .with_len_limits(ETH_MTU - ip_header)
1048 .unwrap();
1049
1050 let mut reports = reports.map(|builder| {
1051 builder
1052 .into_serializer()
1053 .encapsulate(ip_builder.clone())
1054 .serialize_vec_outer()
1055 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
1056 .unwrap_b()
1057 .into_inner()
1058 });
1059 let serialized = reports.next().unwrap();
1061 assert_eq!(serialized.len(), EXPECT_SERIALIZED);
1062
1063 let mut buffer = &serialized[..];
1064 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
1065 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
1066 assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
1067 for (i, group) in igmp.body.iter().enumerate() {
1068 assert_eq!(group.header.multicast_address, group_ip(i).get());
1069 assert_eq!(group.header.number_of_sources.get(), 0);
1070 }
1071
1072 assert_eq!(reports.next(), None);
1074
1075 let reports =
1076 IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
1077 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1078 }))
1079 .with_len_limits(ETH_MTU - ip_header)
1080 .unwrap();
1081 assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
1083 }
1084
1085 #[test]
1086 fn membership_report_v1_parse_and_serialize() {
1087 use igmp_reports::v1;
1088 set_logger_for_test();
1089 test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
1090 assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
1091 });
1092 }
1093
1094 #[test]
1095 fn membership_report_v2_parse_and_serialize() {
1096 use igmp_reports::v2;
1097 set_logger_for_test();
1098 test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
1099 assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
1100 });
1101 }
1102
1103 #[test]
1104 fn leave_group_parse_and_serialize() {
1105 set_logger_for_test();
1106 test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
1107 igmp_leave_group::LEAVE_GROUP,
1108 |igmp| {
1109 assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
1110 },
1111 );
1112 }
1113
1114 #[test]
1115 fn test_unknown_type() {
1116 let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
1117 let mut buff = buff.as_mut_slice();
1118 let packet = buff.parse_with::<_, IgmpPacket<_>>(());
1119 assert_eq!(packet.is_err(), true);
1122 }
1123
1124 #[test]
1125 fn test_full_parses() {
1126 let mut bufs = ALL_BUFFERS.to_vec();
1127 for buff in bufs.iter_mut() {
1128 let orig_req = &buff[..];
1129 let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
1130 let msg_type = match packet {
1131 IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
1132 IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
1133 IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
1134 IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
1135 IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
1136 IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
1137 };
1138 assert_eq!(msg_type, orig_req[0]);
1139 }
1140 }
1141
1142 #[test]
1143 fn test_partial_parses() {
1144 for buff in ALL_BUFFERS.iter() {
1147 for i in 0..buff.len() {
1148 let partial_buff = &mut &buff[0..i];
1149 let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
1150 assert_eq!(packet.is_err(), true)
1151 }
1152 }
1153 }
1154
1155 fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
1158 mut ground_truth: &[u8],
1159 ) {
1160 let ground_truth_len = ground_truth.len();
1161 let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
1162 let builder_len = igmp.builder().bytes_len();
1163 assert_eq!(builder_len, ground_truth_len);
1164 }
1165
1166 #[test]
1167 fn test_igmp_packet_length() {
1168 assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
1169 assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
1170 assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
1171 assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
1172 }
1173}