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 IgmpMessage, IgmpNonEmptyBody, IgmpResponseTimeV2, IgmpResponseTimeV3, peek_message_type,
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 { Err(ParseError::NotExpected) } else { Ok(()) }
60 }
61
62 fn body_bytes(_body: &Self::VariableBody) -> &[u8]
63 where
64 B: SplitByteSlice,
65 {
66 &[]
67 }
68 };
69}
70
71#[derive(Copy, Clone, Debug)]
88pub struct IgmpMembershipQueryV2;
89
90impl<B> MessageType<B> for IgmpMembershipQueryV2 {
91 type FixedHeader = Ipv4Addr;
92 type MaxRespTime = IgmpResponseTimeV2;
93 const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
94
95 declare_no_body!();
96}
97
98impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV2> {
99 pub fn is_igmpv1_query(&self) -> bool {
109 self.prefix.max_resp_code == 0
110 }
111}
112
113#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
119#[repr(C)]
120pub struct MembershipQueryData {
121 group_address: Ipv4Addr,
122 sqrv: u8,
123 qqic: u8,
124 number_of_sources: U16,
125}
126
127impl MembershipQueryData {
128 const S_FLAG: u8 = (1 << 3);
129 const QRV_MSK: u8 = 0x07;
130
131 pub fn number_of_sources(&self) -> u16 {
133 self.number_of_sources.get()
134 }
135
136 pub fn group_address(&self) -> Ipv4Addr {
138 self.group_address
139 }
140
141 pub fn suppress_router_side_processing(&self) -> bool {
149 (self.sqrv & Self::S_FLAG) != 0
150 }
151
152 pub fn querier_robustness_variable(&self) -> u8 {
166 self.sqrv & Self::QRV_MSK
167 }
168
169 pub fn querier_query_interval(&self) -> core::time::Duration {
180 Igmpv3QQIC::from(self.qqic).into()
183 }
184}
185
186pub type Igmpv3QRV = crate::gmp::QRV;
194
195pub type Igmpv3QQIC = crate::gmp::QQIC;
202
203#[derive(Copy, Clone, Debug)]
214pub struct IgmpMembershipQueryV3;
215
216impl<B> IgmpNonEmptyBody for Ref<B, [Ipv4Addr]> {}
217
218impl<B> MessageType<B> for IgmpMembershipQueryV3 {
219 type FixedHeader = MembershipQueryData;
220 type VariableBody = Ref<B, [Ipv4Addr]>;
221 type MaxRespTime = IgmpResponseTimeV3;
222 const TYPE: IgmpMessageType = IgmpMessageType::MembershipQuery;
223
224 fn parse_body<BV: BufferView<B>>(
225 header: &Self::FixedHeader,
226 mut bytes: BV,
227 ) -> Result<Self::VariableBody, ParseError>
228 where
229 B: SplitByteSlice,
230 {
231 bytes
232 .take_slice_front::<Ipv4Addr>(header.number_of_sources() as usize)
233 .ok_or(ParseError::Format)
234 }
235
236 fn body_bytes(body: &Self::VariableBody) -> &[u8]
237 where
238 B: SplitByteSlice,
239 {
240 Ref::bytes(body)
241 }
242}
243
244impl<B: SplitByteSlice> IgmpMessage<B, IgmpMembershipQueryV3> {
245 pub fn as_v2_query(&self) -> IgmpMessage<&[u8], IgmpMembershipQueryV2> {
259 let Self { prefix, header, body: _ } = self;
260 let prefix = Ref::from_bytes(prefix.as_bytes()).unwrap();
262 let (header, _rest) = Ref::from_prefix(header.as_bytes()).unwrap();
263 IgmpMessage { prefix, header, body: () }
264 }
265}
266
267#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
273#[repr(C)]
274pub struct MembershipReportV3Data {
275 _reserved: [u8; 2],
276 number_of_group_records: U16,
277}
278
279impl MembershipReportV3Data {
280 pub fn number_of_group_records(self) -> u16 {
282 self.number_of_group_records.get()
283 }
284}
285
286pub type IgmpGroupRecordType = crate::gmp::GroupRecordType;
293
294#[derive(Copy, Clone, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
303#[repr(C)]
304pub struct GroupRecordHeader {
305 record_type: u8,
306 aux_data_len: u8,
307 number_of_sources: U16,
308 multicast_address: Ipv4Addr,
309}
310
311impl GroupRecordHeader {
312 pub fn number_of_sources(&self) -> u16 {
314 self.number_of_sources.get()
315 }
316
317 pub fn record_type(&self) -> Result<IgmpGroupRecordType, UnrecognizedProtocolCode<u8>> {
319 IgmpGroupRecordType::try_from(self.record_type)
320 }
321
322 pub fn multicast_addr(&self) -> &Ipv4Addr {
324 &self.multicast_address
325 }
326}
327
328pub struct GroupRecord<B> {
344 header: Ref<B, GroupRecordHeader>,
345 sources: Ref<B, [Ipv4Addr]>,
346}
347
348impl<B: SplitByteSlice> GroupRecord<B> {
349 pub fn header(&self) -> &GroupRecordHeader {
351 self.header.deref()
352 }
353
354 pub fn sources(&self) -> &[Ipv4Addr] {
356 self.sources.deref()
357 }
358}
359
360#[derive(Copy, Clone, Debug)]
372pub struct IgmpMembershipReportV3;
373
374impl<B> IgmpNonEmptyBody for Records<B, IgmpMembershipReportV3> {}
375
376impl<B> MessageType<B> for IgmpMembershipReportV3 {
377 type FixedHeader = MembershipReportV3Data;
378 type VariableBody = Records<B, IgmpMembershipReportV3>;
379 type MaxRespTime = ();
380 const TYPE: IgmpMessageType = IgmpMessageType::MembershipReportV3;
381
382 fn parse_body<BV: BufferView<B>>(
383 header: &Self::FixedHeader,
384 bytes: BV,
385 ) -> Result<Self::VariableBody, ParseError>
386 where
387 B: SplitByteSlice,
388 {
389 Records::parse_with_context(bytes.into_rest(), header.number_of_group_records().into())
390 }
391
392 fn body_bytes(body: &Self::VariableBody) -> &[u8]
393 where
394 B: SplitByteSlice,
395 {
396 body.bytes()
397 }
398}
399
400impl RecordsImplLayout for IgmpMembershipReportV3 {
401 type Context = usize;
402 type Error = ParseError;
403}
404
405impl RecordsImpl for IgmpMembershipReportV3 {
406 type Record<'a> = GroupRecord<&'a [u8]>;
407
408 fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
409 data: &mut BV,
410 _ctx: &mut usize,
411 ) -> RecordParseResult<GroupRecord<&'a [u8]>, ParseError> {
412 let header = data
413 .take_obj_front::<GroupRecordHeader>()
414 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't take group record header"))?;
415 let sources = data
416 .take_slice_front::<Ipv4Addr>(header.number_of_sources().into())
417 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't group record sources"))?;
418 let _ = data
421 .take_front(usize::from(header.aux_data_len) * 4)
422 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't skip auxiliary data"))?;
423
424 Ok(ParsedRecord::Parsed(Self::Record { header, sources }))
425 }
426}
427
428#[derive(Debug)]
442pub struct IgmpMembershipReportV1;
443
444impl_igmp_simple_message_type!(IgmpMembershipReportV1, MembershipReportV1, Ipv4Addr);
445
446#[derive(Debug)]
461pub struct IgmpMembershipReportV2;
462
463impl_igmp_simple_message_type!(IgmpMembershipReportV2, MembershipReportV2, Ipv4Addr);
464
465#[derive(Debug)]
480pub struct IgmpLeaveGroup;
481
482impl_igmp_simple_message_type!(IgmpLeaveGroup, LeaveGroup, Ipv4Addr);
483
484#[allow(missing_docs)]
491#[derive(Debug)]
492pub enum IgmpPacket<B: SplitByteSlice> {
493 MembershipQueryV2(IgmpMessage<B, IgmpMembershipQueryV2>),
494 MembershipQueryV3(IgmpMessage<B, IgmpMembershipQueryV3>),
495 MembershipReportV1(IgmpMessage<B, IgmpMembershipReportV1>),
496 MembershipReportV2(IgmpMessage<B, IgmpMembershipReportV2>),
497 MembershipReportV3(IgmpMessage<B, IgmpMembershipReportV3>),
498 LeaveGroup(IgmpMessage<B, IgmpLeaveGroup>),
499}
500
501impl<B: SplitByteSlice> ParsablePacket<B, ()> for IgmpPacket<B> {
502 type Error = ParseError;
503
504 fn parse_metadata(&self) -> ParseMetadata {
505 use self::IgmpPacket::*;
506 match self {
507 MembershipQueryV2(p) => p.parse_metadata(),
508 MembershipQueryV3(p) => p.parse_metadata(),
509 MembershipReportV1(p) => p.parse_metadata(),
510 MembershipReportV2(p) => p.parse_metadata(),
511 MembershipReportV3(p) => p.parse_metadata(),
512 LeaveGroup(p) => p.parse_metadata(),
513 }
514 }
515
516 fn parse<BV: BufferView<B>>(buffer: BV, args: ()) -> Result<Self, ParseError> {
517 macro_rules! mtch {
518 ($buffer:expr, $args:expr, $( ($code:ident, $long:tt) => $type:ty, $variant:ident )*) => {
519 match peek_message_type($buffer.as_ref())? {
520 $( (IgmpMessageType::$code, $long) => {
521 let packet = <IgmpMessage<B,$type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
522 IgmpPacket::$variant(packet)
523 })*,
524 }
525 }
526 }
527
528 Ok(mtch!(
529 buffer,
530 args,
531 (MembershipQuery, false) => IgmpMembershipQueryV2, MembershipQueryV2
532 (MembershipQuery, true) => IgmpMembershipQueryV3, MembershipQueryV3
533 (MembershipReportV1, _) => IgmpMembershipReportV1, MembershipReportV1
534 (MembershipReportV2, _) => IgmpMembershipReportV2, MembershipReportV2
535 (MembershipReportV3, _) => IgmpMembershipReportV3, MembershipReportV3
536 (LeaveGroup, _) => IgmpLeaveGroup, LeaveGroup
537 ))
538 }
539}
540
541#[derive(Debug)]
547pub struct IgmpMembershipQueryV3Builder<I> {
548 max_resp_time: IgmpResponseTimeV3,
549 group_addr: Option<MulticastAddr<Ipv4Addr>>,
550 s_flag: bool,
551 qrv: Igmpv3QRV,
552 qqic: Igmpv3QQIC,
553 sources: I,
554}
555
556impl<I> IgmpMembershipQueryV3Builder<I> {
557 pub fn new(
559 max_resp_time: IgmpResponseTimeV3,
560 group_addr: Option<MulticastAddr<Ipv4Addr>>,
561 s_flag: bool,
562 qrv: Igmpv3QRV,
563 qqic: Igmpv3QQIC,
564 sources: I,
565 ) -> Self {
566 Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources }
567 }
568
569 const HEADER_SIZE: usize = super::total_header_size::<MembershipQueryData>();
570}
571
572impl<I> InnerPacketBuilder for IgmpMembershipQueryV3Builder<I>
573where
574 I: Iterator<Item = Ipv4Addr> + Clone,
575{
576 fn bytes_len(&self) -> usize {
577 Self::HEADER_SIZE + self.sources.clone().count() * core::mem::size_of::<Ipv4Addr>()
578 }
579
580 fn serialize(&self, buf: &mut [u8]) {
581 use packet::BufferViewMut;
582
583 let Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources } = self;
584 let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
585 let mut bytes = &mut body[..];
586 let mut bytes = &mut bytes;
587 let mut count: u16 = 0;
589 for src in sources.clone() {
590 count = count.checked_add(1).expect("overflowed number of sources");
591 bytes.write_obj_front(&src).expect("too few bytes for source");
592 }
593 let builder = IgmpPacketBuilder::<&mut [u8], IgmpMembershipQueryV3>::new_with_resp_time(
594 MembershipQueryData {
595 group_address: group_addr
596 .as_ref()
597 .map(|a| a.get())
598 .unwrap_or(Ipv4::UNSPECIFIED_ADDRESS),
599 sqrv: (u8::from(*s_flag) << 3) | (MembershipQueryData::QRV_MSK & u8::from(*qrv)),
600 qqic: (*qqic).into(),
601 number_of_sources: count.into(),
602 },
603 *max_resp_time,
604 );
605 builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
606 }
607}
608
609#[derive(Debug)]
615pub struct IgmpMembershipReportV3Builder<I> {
616 groups: I,
617}
618
619impl<I> IgmpMembershipReportV3Builder<I> {
620 pub fn new(groups: I) -> Self {
622 Self { groups }
623 }
624
625 const HEADER_SIZE: usize = super::total_header_size::<MembershipReportV3Data>();
626}
627
628impl<I> IgmpMembershipReportV3Builder<I>
629where
630 I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr> + Clone> + Clone,
631{
632 pub fn with_len_limits(
644 self,
645 max_len: usize,
646 ) -> Result<
647 impl Iterator<
648 Item = IgmpMembershipReportV3Builder<
649 impl Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
650 >,
651 >,
652 InvalidConstraintsError,
653 > {
654 let Self { groups } = self;
655 crate::gmp::group_record_split_iterator(
656 max_len.saturating_sub(Self::HEADER_SIZE),
657 core::mem::size_of::<GroupRecordHeader>(),
658 groups,
659 )
660 .map(|iter| iter.map(|groups| IgmpMembershipReportV3Builder { groups }))
661 }
662}
663
664impl<I> InnerPacketBuilder for IgmpMembershipReportV3Builder<I>
665where
666 I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
667{
668 fn bytes_len(&self) -> usize {
669 Self::HEADER_SIZE
670 + self
671 .groups
672 .clone()
673 .map(|g| {
674 core::mem::size_of::<GroupRecordHeader>()
675 + g.sources().count() * core::mem::size_of::<Ipv4Addr>()
676 })
677 .sum::<usize>()
678 }
679
680 fn serialize(&self, buf: &mut [u8]) {
681 use packet::BufferViewMut;
682
683 let Self { groups } = self;
684 let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
685 let mut bytes = &mut body[..];
686 let mut bytes = &mut bytes;
687 let mut count: u16 = 0;
689 for group in groups.clone() {
690 count = count.checked_add(1).expect("multicast groups count overflows");
691 let mut header = bytes
692 .take_obj_front_zero::<GroupRecordHeader>()
693 .expect("too few bytes for record header");
694 let GroupRecordHeader {
695 record_type,
696 aux_data_len,
697 number_of_sources,
698 multicast_address,
699 } = &mut *header;
700 *record_type = group.record_type().into();
701 *aux_data_len = 0;
702 *multicast_address = group.group().into();
703 let mut source_count: u16 = 0;
704 for src in group.sources() {
705 source_count = source_count.checked_add(1).expect("sources count overflows");
706 bytes.write_obj_front(src.borrow()).expect("too few bytes for source");
707 }
708 *number_of_sources = source_count.into();
709 }
710
711 let builder =
712 IgmpPacketBuilder::<&mut [u8], IgmpMembershipReportV3>::new(MembershipReportV3Data {
713 _reserved: [0, 0],
714 number_of_group_records: count.into(),
715 });
716 builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use core::fmt::Debug;
723 use core::time::Duration;
724
725 use packet::{PacketBuilder, ParseBuffer, Serializer};
726
727 use super::*;
728 use crate::igmp::IgmpMaxRespCode;
729 use crate::igmp::testdata::*;
730 use crate::ip::Ipv4Proto;
731 use crate::ipv4::options::Ipv4Option;
732 use crate::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
733 use crate::testutil::set_logger_for_test;
734
735 const ALL_BUFFERS: [&[u8]; 6] = [
736 igmp_router_queries::v2::QUERY,
737 igmp_router_queries::v3::QUERY,
738 igmp_reports::v1::MEMBER_REPORT,
739 igmp_reports::v2::MEMBER_REPORT,
740 igmp_reports::v3::MEMBER_REPORT,
741 igmp_leave_group::LEAVE_GROUP,
742 ];
743
744 fn serialize_to_bytes<B: SplitByteSlice + Debug, M: MessageType<B> + Debug>(
745 igmp: &IgmpMessage<B, M>,
746 ) -> Vec<u8>
747 where
748 M::VariableBody: IgmpNonEmptyBody,
749 {
750 igmp.builder()
751 .wrap_body(M::body_bytes(&igmp.body).into_serializer())
752 .serialize_vec_outer()
753 .unwrap()
754 .as_ref()
755 .to_vec()
756 }
757
758 fn serialize_to_bytes_inner<
759 B: SplitByteSlice + Debug,
760 M: MessageType<B, VariableBody = ()> + Debug,
761 >(
762 igmp: &IgmpMessage<B, M>,
763 ) -> Vec<u8> {
764 igmp.builder().into_serializer().serialize_vec_outer().unwrap().as_ref().to_vec()
765 }
766
767 fn test_parse_and_serialize<
768 B: SplitByteSlice + Debug,
769 BV: BufferView<B>,
770 M: MessageType<B> + Debug,
771 F: FnOnce(&IgmpMessage<B, M>),
772 >(
773 req: BV,
774 check: F,
775 ) where
776 M::VariableBody: IgmpNonEmptyBody,
777 {
778 let orig_req = req.as_ref().to_owned();
779
780 let igmp = IgmpMessage::<_, M>::parse(req, ()).unwrap();
781 check(&igmp);
782
783 let data = serialize_to_bytes(&igmp);
784 assert_eq!(data, orig_req);
785 }
786
787 fn test_parse_and_serialize_inner<
788 M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
789 F: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
790 >(
791 mut req: &[u8],
792 check: F,
793 ) {
794 let orig_req = req;
795
796 let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
797 check(&igmp);
798
799 let data = serialize_to_bytes_inner(&igmp);
800 assert_eq!(&data[..], orig_req);
801 }
802
803 #[test]
804 fn membership_query_v2_parse_and_serialize() {
805 set_logger_for_test();
806 test_parse_and_serialize_inner::<IgmpMembershipQueryV2, _>(
807 igmp_router_queries::v2::QUERY,
808 |igmp| {
809 assert_eq!(
810 *igmp.header,
811 Ipv4Addr::new(igmp_router_queries::v2::HOST_GROUP_ADDRESS)
812 );
813 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v2::MAX_RESP_CODE);
814 },
815 );
816 }
817
818 #[test]
819 fn membership_query_v3_parse_and_serialize() {
820 set_logger_for_test();
821 let mut req = igmp_router_queries::v3::QUERY;
822 test_parse_and_serialize::<_, _, IgmpMembershipQueryV3, _>(&mut req, |igmp| {
823 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
824 assert_eq!(
825 igmp.header.group_address,
826 Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)
827 );
828 assert_eq!(igmp.header.number_of_sources(), igmp_router_queries::v3::NUMBER_OF_SOURCES);
829 assert_eq!(
830 igmp.header.suppress_router_side_processing(),
831 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE
832 );
833 assert_eq!(igmp.header.querier_robustness_variable(), igmp_router_queries::v3::QRV);
834 assert_eq!(
835 igmp.header.querier_query_interval().as_secs() as u32,
836 igmp_router_queries::v3::QQIC_SECS
837 );
838 assert_eq!(igmp.body.len(), igmp_router_queries::v3::NUMBER_OF_SOURCES as usize);
839 assert_eq!(igmp.body[0], Ipv4Addr::new(igmp_router_queries::v3::SOURCE));
840
841 let v2 = igmp.as_v2_query();
843 assert_eq!(v2.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
844 assert_eq!(*(v2.header), Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS));
845 });
846 }
847
848 #[test]
849 fn membership_report_v3_parse_and_serialize() {
850 use igmp_reports::v3::*;
851
852 set_logger_for_test();
853 let mut req = MEMBER_REPORT;
854 test_parse_and_serialize::<_, _, IgmpMembershipReportV3, _>(&mut req, |igmp| {
855 assert_eq!(igmp.header.number_of_group_records(), NUMBER_OF_RECORDS);
856 assert_eq!(igmp.prefix.max_resp_code, MAX_RESP_CODE);
857 let mut iter = igmp.body.iter();
858 let rec1 = iter.next().unwrap();
860 assert_eq!(rec1.header().number_of_sources(), NUMBER_OF_SOURCES_1);
861 assert_eq!(rec1.header().record_type, RECORD_TYPE_1);
862 assert_eq!(rec1.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_1));
863 assert_eq!(rec1.header().record_type(), Ok(IgmpGroupRecordType::ModeIsInclude));
864 assert_eq!(rec1.sources().len(), NUMBER_OF_SOURCES_1 as usize);
865 assert_eq!(rec1.sources()[0], Ipv4Addr::new(SRC_1_1));
866 assert_eq!(rec1.sources()[1], Ipv4Addr::new(SRC_1_2));
867
868 let rec2 = iter.next().unwrap();
870 assert_eq!(rec2.header().number_of_sources(), NUMBER_OF_SOURCES_2);
871 assert_eq!(rec2.header().record_type, RECORD_TYPE_2);
872 assert_eq!(rec2.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_2));
873 assert_eq!(rec2.header().record_type(), Ok(IgmpGroupRecordType::ModeIsExclude));
874 assert_eq!(rec2.sources().len(), NUMBER_OF_SOURCES_2 as usize);
875 assert_eq!(rec2.sources()[0], Ipv4Addr::new(SRC_2_1));
876
877 assert_eq!(iter.next().is_none(), true);
879 });
880 }
881
882 #[test]
883 fn membership_query_v3_builder() {
884 set_logger_for_test();
885 let builder = IgmpMembershipQueryV3Builder::new(
886 IgmpResponseTimeV3::from_code(igmp_router_queries::v3::MAX_RESP_CODE),
887 Some(
888 MulticastAddr::new(Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)).unwrap(),
889 ),
890 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE,
891 Igmpv3QRV::new(igmp_router_queries::v3::QRV),
892 Igmpv3QQIC::new_exact(Duration::from_secs(igmp_router_queries::v3::QQIC_SECS.into()))
893 .unwrap(),
894 [Ipv4Addr::new(igmp_router_queries::v3::SOURCE)].into_iter(),
895 );
896 let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
897 assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
898 }
899
900 #[test]
901 fn membership_report_v3_builder() {
902 set_logger_for_test();
903 use igmp_reports::v3::*;
904 let builder = IgmpMembershipReportV3Builder::new(
905 [
906 (MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
907 (MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
908 ]
909 .into_iter()
910 .map(|(addr, rec_type, sources)| {
911 (
912 MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
913 IgmpGroupRecordType::try_from(rec_type).unwrap(),
914 sources.into_iter().copied().map(Ipv4Addr::new),
915 )
916 }),
917 );
918 let serialized = builder.into_serializer().serialize_vec_outer().unwrap().unwrap_b();
919 assert_eq!(serialized.as_ref(), MEMBER_REPORT);
920 }
921
922 #[test]
934 fn membership_report_v3_split_many_sources() {
935 use igmp_reports::v3::*;
936 use packet::PacketBuilder;
937 const ETH_MTU: usize = 1500;
938 const MAX_SOURCES: usize = 365;
939
940 let src = Ipv4Addr::new(SRC_1_1);
941 let ip_builder = Ipv4PacketBuilderWithOptions::new(
942 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
943 &[Ipv4Option::RouterAlert { data: 0 }],
944 )
945 .unwrap();
946 let ip_header = ip_builder.constraints().header_len();
947
948 let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
949 let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
950 let reports = IgmpMembershipReportV3Builder::new(
951 [(
952 group_addr,
953 IgmpGroupRecordType::ModeIsInclude,
954 (0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
955 )]
956 .into_iter(),
957 )
958 .with_len_limits(ETH_MTU - ip_header)
959 .unwrap();
960
961 let mut reports = reports.map(|builder| {
962 builder
963 .into_serializer()
964 .wrap_in(ip_builder.clone())
965 .serialize_vec_outer()
966 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
967 .unwrap_b()
968 .into_inner()
969 });
970 let serialized = reports.next().unwrap();
972 assert_eq!(serialized.len(), ETH_MTU);
973
974 let mut buffer = &serialized[..];
975 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
976 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
977 let mut groups = igmp.body.iter();
978 let group = groups.next().expect("has group");
979 assert_eq!(group.header.multicast_address, group_addr.get());
980 assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
981 assert_eq!(group.sources().len(), MAX_SOURCES);
982 for (i, addr) in group.sources().iter().enumerate() {
983 assert_eq!(*addr, src_ip(i));
984 }
985 assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
986
987 assert_eq!(reports.next(), None);
989
990 let reports = IgmpMembershipReportV3Builder::new(
991 [(
992 group_addr,
993 IgmpGroupRecordType::ModeIsInclude,
994 core::iter::repeat(src).take(MAX_SOURCES + 1),
995 )]
996 .into_iter(),
997 )
998 .with_len_limits(ETH_MTU - ip_header)
999 .unwrap();
1000 assert_eq!(
1002 reports
1003 .map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
1004 .collect::<Vec<_>>(),
1005 vec![vec![MAX_SOURCES], vec![1]]
1006 );
1007 }
1008
1009 #[test]
1020 fn membership_report_v3_split_many_groups() {
1021 use igmp_reports::v3::*;
1022 use packet::PacketBuilder;
1023
1024 const ETH_MTU: usize = 1500;
1025 const EXPECT_SERIALIZED: usize = 1496;
1026 const MAX_GROUPS: usize = 183;
1027
1028 let src = Ipv4Addr::new(SRC_1_1);
1029 let ip_builder = Ipv4PacketBuilderWithOptions::new(
1030 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
1031 &[Ipv4Option::RouterAlert { data: 0 }],
1032 )
1033 .unwrap();
1034 let ip_header = ip_builder.constraints().header_len();
1035
1036 let group_ip = |i: usize| {
1037 MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
1038 };
1039 let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
1040 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1041 }))
1042 .with_len_limits(ETH_MTU - ip_header)
1043 .unwrap();
1044
1045 let mut reports = reports.map(|builder| {
1046 builder
1047 .into_serializer()
1048 .wrap_in(ip_builder.clone())
1049 .serialize_vec_outer()
1050 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
1051 .unwrap_b()
1052 .into_inner()
1053 });
1054 let serialized = reports.next().unwrap();
1056 assert_eq!(serialized.len(), EXPECT_SERIALIZED);
1057
1058 let mut buffer = &serialized[..];
1059 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
1060 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
1061 assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
1062 for (i, group) in igmp.body.iter().enumerate() {
1063 assert_eq!(group.header.multicast_address, group_ip(i).get());
1064 assert_eq!(group.header.number_of_sources.get(), 0);
1065 }
1066
1067 assert_eq!(reports.next(), None);
1069
1070 let reports =
1071 IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
1072 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1073 }))
1074 .with_len_limits(ETH_MTU - ip_header)
1075 .unwrap();
1076 assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
1078 }
1079
1080 #[test]
1081 fn membership_report_v1_parse_and_serialize() {
1082 use igmp_reports::v1;
1083 set_logger_for_test();
1084 test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
1085 assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
1086 });
1087 }
1088
1089 #[test]
1090 fn membership_report_v2_parse_and_serialize() {
1091 use igmp_reports::v2;
1092 set_logger_for_test();
1093 test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
1094 assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
1095 });
1096 }
1097
1098 #[test]
1099 fn leave_group_parse_and_serialize() {
1100 set_logger_for_test();
1101 test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
1102 igmp_leave_group::LEAVE_GROUP,
1103 |igmp| {
1104 assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
1105 },
1106 );
1107 }
1108
1109 #[test]
1110 fn test_unknown_type() {
1111 let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
1112 let mut buff = buff.as_mut_slice();
1113 let packet = buff.parse_with::<_, IgmpPacket<_>>(());
1114 assert_eq!(packet.is_err(), true);
1117 }
1118
1119 #[test]
1120 fn test_full_parses() {
1121 let mut bufs = ALL_BUFFERS.to_vec();
1122 for buff in bufs.iter_mut() {
1123 let orig_req = &buff[..];
1124 let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
1125 let msg_type = match packet {
1126 IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
1127 IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
1128 IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
1129 IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
1130 IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
1131 IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
1132 };
1133 assert_eq!(msg_type, orig_req[0]);
1134 }
1135 }
1136
1137 #[test]
1138 fn test_partial_parses() {
1139 for buff in ALL_BUFFERS.iter() {
1142 for i in 0..buff.len() {
1143 let partial_buff = &mut &buff[0..i];
1144 let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
1145 assert_eq!(packet.is_err(), true)
1146 }
1147 }
1148 }
1149
1150 fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
1153 mut ground_truth: &[u8],
1154 ) {
1155 let ground_truth_len = ground_truth.len();
1156 let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
1157 let builder_len = igmp.builder().bytes_len();
1158 assert_eq!(builder_len, ground_truth_len);
1159 }
1160
1161 #[test]
1162 fn test_igmp_packet_length() {
1163 assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
1164 assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
1165 assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
1166 assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
1167 }
1168}