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 if *ctx == 0 {
413 return Ok(ParsedRecord::Done);
414 }
415 let header = data
416 .take_obj_front::<GroupRecordHeader>()
417 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't take group record header"))?;
418 let sources = data
419 .take_slice_front::<Ipv4Addr>(header.number_of_sources().into())
420 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't group record sources"))?;
421 let _ = data
424 .take_front(usize::from(header.aux_data_len) * 4)
425 .ok_or_else(debug_err_fn!(ParseError::Format, "Can't skip auxiliary data"))?;
426
427 *ctx -= 1;
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::{NestablePacketBuilder as _, NoOpSerializationContext, ParseBuffer, Serializer};
730
731 use super::*;
732 use crate::igmp::IgmpMaxRespCode;
733 use crate::igmp::testdata::*;
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 use net_declare::net_ip_v4;
739
740 const ALL_BUFFERS: [&[u8]; 6] = [
741 igmp_router_queries::v2::QUERY,
742 igmp_router_queries::v3::QUERY,
743 igmp_reports::v1::MEMBER_REPORT,
744 igmp_reports::v2::MEMBER_REPORT,
745 igmp_reports::v3::MEMBER_REPORT,
746 igmp_leave_group::LEAVE_GROUP,
747 ];
748
749 fn serialize_to_bytes<B: SplitByteSlice + Debug, M: MessageType<B> + Debug>(
750 igmp: &IgmpMessage<B, M>,
751 ) -> Vec<u8>
752 where
753 M::VariableBody: IgmpNonEmptyBody,
754 {
755 igmp.builder()
756 .wrap_body(M::body_bytes(&igmp.body).into_serializer())
757 .serialize_vec_outer(&mut NoOpSerializationContext)
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()
770 .into_serializer()
771 .serialize_vec_outer(&mut NoOpSerializationContext)
772 .unwrap()
773 .as_ref()
774 .to_vec()
775 }
776
777 fn test_parse_and_serialize<
778 B: SplitByteSlice + Debug,
779 BV: BufferView<B>,
780 M: MessageType<B> + Debug,
781 F: FnOnce(&IgmpMessage<B, M>),
782 >(
783 req: BV,
784 check: F,
785 ) where
786 M::VariableBody: IgmpNonEmptyBody,
787 {
788 let orig_req = req.as_ref().to_owned();
789
790 let igmp = IgmpMessage::<_, M>::parse(req, ()).unwrap();
791 check(&igmp);
792
793 let data = serialize_to_bytes(&igmp);
794 assert_eq!(data, orig_req);
795 }
796
797 fn test_parse_and_serialize_inner<
798 M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
799 F: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
800 >(
801 mut req: &[u8],
802 check: F,
803 ) {
804 let orig_req = req;
805
806 let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
807 check(&igmp);
808
809 let data = serialize_to_bytes_inner(&igmp);
810 assert_eq!(&data[..], orig_req);
811 }
812
813 #[test]
814 fn membership_query_v2_parse_and_serialize() {
815 set_logger_for_test();
816 test_parse_and_serialize_inner::<IgmpMembershipQueryV2, _>(
817 igmp_router_queries::v2::QUERY,
818 |igmp| {
819 assert_eq!(
820 *igmp.header,
821 Ipv4Addr::new(igmp_router_queries::v2::HOST_GROUP_ADDRESS)
822 );
823 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v2::MAX_RESP_CODE);
824 },
825 );
826 }
827
828 #[test]
829 fn membership_query_v3_parse_and_serialize() {
830 set_logger_for_test();
831 let mut req = igmp_router_queries::v3::QUERY;
832 test_parse_and_serialize::<_, _, IgmpMembershipQueryV3, _>(&mut req, |igmp| {
833 assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
834 assert_eq!(
835 igmp.header.group_address,
836 Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)
837 );
838 assert_eq!(igmp.header.number_of_sources(), igmp_router_queries::v3::NUMBER_OF_SOURCES);
839 assert_eq!(
840 igmp.header.suppress_router_side_processing(),
841 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE
842 );
843 assert_eq!(igmp.header.querier_robustness_variable(), igmp_router_queries::v3::QRV);
844 assert_eq!(
845 igmp.header.querier_query_interval().as_secs() as u32,
846 igmp_router_queries::v3::QQIC_SECS
847 );
848 assert_eq!(igmp.body.len(), igmp_router_queries::v3::NUMBER_OF_SOURCES as usize);
849 assert_eq!(igmp.body[0], Ipv4Addr::new(igmp_router_queries::v3::SOURCE));
850
851 let v2 = igmp.as_v2_query();
853 assert_eq!(v2.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
854 assert_eq!(*(v2.header), Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS));
855 });
856 }
857
858 #[test]
859 fn membership_report_v3_parse_and_serialize() {
860 use igmp_reports::v3::*;
861
862 set_logger_for_test();
863 let mut req = MEMBER_REPORT;
864 test_parse_and_serialize::<_, _, IgmpMembershipReportV3, _>(&mut req, |igmp| {
865 assert_eq!(igmp.header.number_of_group_records(), NUMBER_OF_RECORDS);
866 assert_eq!(igmp.prefix.max_resp_code, MAX_RESP_CODE);
867 let mut iter = igmp.body.iter();
868 let rec1 = iter.next().unwrap();
870 assert_eq!(rec1.header().number_of_sources(), NUMBER_OF_SOURCES_1);
871 assert_eq!(rec1.header().record_type, RECORD_TYPE_1);
872 assert_eq!(rec1.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_1));
873 assert_eq!(rec1.header().record_type(), Ok(IgmpGroupRecordType::ModeIsInclude));
874 assert_eq!(rec1.sources().len(), NUMBER_OF_SOURCES_1 as usize);
875 assert_eq!(rec1.sources()[0], Ipv4Addr::new(SRC_1_1));
876 assert_eq!(rec1.sources()[1], Ipv4Addr::new(SRC_1_2));
877
878 let rec2 = iter.next().unwrap();
880 assert_eq!(rec2.header().number_of_sources(), NUMBER_OF_SOURCES_2);
881 assert_eq!(rec2.header().record_type, RECORD_TYPE_2);
882 assert_eq!(rec2.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_2));
883 assert_eq!(rec2.header().record_type(), Ok(IgmpGroupRecordType::ModeIsExclude));
884 assert_eq!(rec2.sources().len(), NUMBER_OF_SOURCES_2 as usize);
885 assert_eq!(rec2.sources()[0], Ipv4Addr::new(SRC_2_1));
886
887 assert_eq!(iter.next().is_none(), true);
889 });
890 }
891
892 #[test]
893 fn membership_query_v3_builder() {
894 set_logger_for_test();
895 let builder = IgmpMembershipQueryV3Builder::new(
896 IgmpResponseTimeV3::from_code(igmp_router_queries::v3::MAX_RESP_CODE),
897 Some(
898 MulticastAddr::new(Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)).unwrap(),
899 ),
900 igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE,
901 Igmpv3QRV::new(igmp_router_queries::v3::QRV),
902 Igmpv3QQIC::new_exact(Duration::from_secs(igmp_router_queries::v3::QQIC_SECS.into()))
903 .unwrap(),
904 [Ipv4Addr::new(igmp_router_queries::v3::SOURCE)].into_iter(),
905 );
906 let serialized = builder
907 .into_serializer()
908 .serialize_vec_outer(&mut NoOpSerializationContext)
909 .unwrap()
910 .unwrap_b();
911 assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
912 }
913
914 #[test]
915 fn membership_report_v3_builder() {
916 set_logger_for_test();
917 use igmp_reports::v3::*;
918 let builder = IgmpMembershipReportV3Builder::new(
919 [
920 (MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
921 (MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
922 ]
923 .into_iter()
924 .map(|(addr, rec_type, sources)| {
925 (
926 MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
927 IgmpGroupRecordType::try_from(rec_type).unwrap(),
928 sources.iter().copied().map(Ipv4Addr::new),
929 )
930 }),
931 );
932 let serialized = builder
933 .into_serializer()
934 .serialize_vec_outer(&mut NoOpSerializationContext)
935 .unwrap()
936 .unwrap_b();
937 assert_eq!(serialized.as_ref(), MEMBER_REPORT);
938 }
939
940 #[test]
952 fn membership_report_v3_split_many_sources() {
953 use igmp_reports::v3::*;
954 use packet::{NestablePacketBuilder as _, NestableSerializer as _};
955 const ETH_MTU: usize = 1500;
956 const MAX_SOURCES: usize = 365;
957
958 let src = Ipv4Addr::new(SRC_1_1);
959 let ip_builder = Ipv4PacketBuilderWithOptions::new(
960 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
961 &[Ipv4Option::RouterAlert { data: 0 }],
962 )
963 .unwrap();
964 let ip_header = ip_builder.constraints().header_len();
965
966 let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
967 let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
968 let reports = IgmpMembershipReportV3Builder::new(
969 [(
970 group_addr,
971 IgmpGroupRecordType::ModeIsInclude,
972 (0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
973 )]
974 .into_iter(),
975 )
976 .with_len_limits(ETH_MTU - ip_header)
977 .unwrap();
978
979 let mut reports = reports.map(|builder| {
980 builder
981 .into_serializer()
982 .wrap_in(ip_builder.clone())
983 .serialize_vec_outer(&mut NoOpSerializationContext)
984 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
985 .unwrap_b()
986 .into_inner()
987 });
988 let serialized = reports.next().unwrap();
990 assert_eq!(serialized.len(), ETH_MTU);
991
992 let mut buffer = &serialized[..];
993 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
994 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
995 let mut groups = igmp.body.iter();
996 let group = groups.next().expect("has group");
997 assert_eq!(group.header.multicast_address, group_addr.get());
998 assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
999 assert_eq!(group.sources().len(), MAX_SOURCES);
1000 for (i, addr) in group.sources().iter().enumerate() {
1001 assert_eq!(*addr, src_ip(i));
1002 }
1003 assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
1004
1005 assert_eq!(reports.next(), None);
1007
1008 let reports = IgmpMembershipReportV3Builder::new(
1009 [(
1010 group_addr,
1011 IgmpGroupRecordType::ModeIsInclude,
1012 core::iter::repeat(src).take(MAX_SOURCES + 1),
1013 )]
1014 .into_iter(),
1015 )
1016 .with_len_limits(ETH_MTU - ip_header)
1017 .unwrap();
1018 assert_eq!(
1020 reports
1021 .map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
1022 .collect::<Vec<_>>(),
1023 vec![vec![MAX_SOURCES], vec![1]]
1024 );
1025 }
1026
1027 #[test]
1038 fn membership_report_v3_split_many_groups() {
1039 use igmp_reports::v3::*;
1040 use packet::{NestablePacketBuilder as _, NestableSerializer as _};
1041
1042 const ETH_MTU: usize = 1500;
1043 const EXPECT_SERIALIZED: usize = 1496;
1044 const MAX_GROUPS: usize = 183;
1045
1046 let src = Ipv4Addr::new(SRC_1_1);
1047 let ip_builder = Ipv4PacketBuilderWithOptions::new(
1048 Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
1049 &[Ipv4Option::RouterAlert { data: 0 }],
1050 )
1051 .unwrap();
1052 let ip_header = ip_builder.constraints().header_len();
1053
1054 let group_ip = |i: usize| {
1055 MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
1056 };
1057 let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
1058 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1059 }))
1060 .with_len_limits(ETH_MTU - ip_header)
1061 .unwrap();
1062
1063 let mut reports = reports.map(|builder| {
1064 builder
1065 .into_serializer()
1066 .wrap_in(ip_builder.clone())
1067 .serialize_vec_outer(&mut NoOpSerializationContext)
1068 .unwrap_or_else(|(err, _)| panic!("{err:?}"))
1069 .unwrap_b()
1070 .into_inner()
1071 });
1072 let serialized = reports.next().unwrap();
1074 assert_eq!(serialized.len(), EXPECT_SERIALIZED);
1075
1076 let mut buffer = &serialized[..];
1077 let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
1078 let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
1079 assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
1080 for (i, group) in igmp.body.iter().enumerate() {
1081 assert_eq!(group.header.multicast_address, group_ip(i).get());
1082 assert_eq!(group.header.number_of_sources.get(), 0);
1083 }
1084
1085 assert_eq!(reports.next(), None);
1087
1088 let reports =
1089 IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
1090 (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1091 }))
1092 .with_len_limits(ETH_MTU - ip_header)
1093 .unwrap();
1094 assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
1096 }
1097
1098 #[test]
1099 fn membership_report_v1_parse_and_serialize() {
1100 use igmp_reports::v1;
1101 set_logger_for_test();
1102 test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
1103 assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
1104 });
1105 }
1106
1107 #[test]
1108 fn membership_report_v2_parse_and_serialize() {
1109 use igmp_reports::v2;
1110 set_logger_for_test();
1111 test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
1112 assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
1113 });
1114 }
1115
1116 #[test]
1117 fn leave_group_parse_and_serialize() {
1118 set_logger_for_test();
1119 test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
1120 igmp_leave_group::LEAVE_GROUP,
1121 |igmp| {
1122 assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
1123 },
1124 );
1125 }
1126
1127 #[test]
1128 fn test_unknown_type() {
1129 let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
1130 let mut buff = buff.as_mut_slice();
1131 let packet = buff.parse_with::<_, IgmpPacket<_>>(());
1132 assert_eq!(packet.is_err(), true);
1135 }
1136
1137 #[test]
1138 fn test_full_parses() {
1139 let mut bufs = ALL_BUFFERS.to_vec();
1140 for buff in bufs.iter_mut() {
1141 let orig_req = &buff[..];
1142 let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
1143 let msg_type = match packet {
1144 IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
1145 IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
1146 IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
1147 IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
1148 IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
1149 IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
1150 };
1151 assert_eq!(msg_type, orig_req[0]);
1152 }
1153 }
1154
1155 #[test]
1156 fn test_partial_parses() {
1157 for buff in ALL_BUFFERS.iter() {
1160 for i in 0..buff.len() {
1161 let partial_buff = &mut &buff[0..i];
1162 let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
1163 assert_eq!(packet.is_err(), true)
1164 }
1165 }
1166 }
1167
1168 fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
1171 mut ground_truth: &[u8],
1172 ) {
1173 let ground_truth_len = ground_truth.len();
1174 let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
1175 let builder_len = igmp.builder().bytes_len();
1176 assert_eq!(builder_len, ground_truth_len);
1177 }
1178
1179 #[test]
1180 fn test_igmp_packet_length() {
1181 assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
1182 assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
1183 assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
1184 assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
1185 }
1186
1187 #[test]
1188 fn test_igmp_too_few_records() {
1189 let src = net_ip_v4!("192.168.0.1");
1190 let group_addr = MulticastAddr::new(net_ip_v4!("224.0.0.1")).unwrap();
1191
1192 let builder = IgmpMembershipReportV3Builder::new(
1194 [(group_addr, IgmpGroupRecordType::ModeIsInclude, core::iter::once(src))].into_iter(),
1195 );
1196 let serialized = builder
1197 .into_serializer()
1198 .serialize_vec_outer(&mut NoOpSerializationContext)
1199 .unwrap()
1200 .unwrap_b()
1201 .into_inner();
1202
1203 let header_size = crate::igmp::total_header_size::<MembershipReportV3Data>();
1205 let record_bytes = &serialized[header_size..];
1206
1207 let mut ctx = 2; let res =
1209 Records::<_, IgmpMembershipReportV3>::parse_with_mut_context(record_bytes, &mut ctx);
1210 assert_eq!(res.unwrap_err(), ParseError::Format);
1211 assert_eq!(ctx, 1); }
1213}