Skip to main content

packet_formats/igmp/
messages.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implementation of IGMP Messages.
6
7use 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    /// An IGMP message type.
26    #[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/// IGMPv2 Membership Query message.
72///
73/// `IgmpMembershipQueryV2` implements `MessageType`, providing the intended
74/// behavior for IGMPv2 Membership Queries as defined in [RFC 2236].
75///
76/// There are two sub-types of Membership Query messages:
77/// - General Query, used to learn which groups have members on an
78///   attached network.
79/// - Group-Specific Query, used to learn if a particular group
80///   has any members on an attached network.
81///
82/// These two messages are differentiated by the Group Address, as
83/// described in [RFC 2236 section 1.4].
84///
85/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
86/// [RFC 2236 section 1.4]: https://tools.ietf.org/html/rfc2236#section-1.4
87#[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    /// Returns true if this is an IGMPv1 query.
100    ///
101    /// Defined in [RFC 2236 section 4.1]:
102    ///
103    /// > The IGMPv1 router will send General Queries with the Max Response Time
104    /// > set to 0...
105    ///
106    /// [RFC 2236 section 4.1]:
107    ///     https://datatracker.ietf.org/doc/html/rfc2236#section-4
108    pub fn is_igmpv1_query(&self) -> bool {
109        self.prefix.max_resp_code == 0
110    }
111}
112
113/// Fixed information in IGMPv3 Membership Queries.
114///
115/// A `MembershipQueryData` struct represents the fixed data in IGMPv3
116/// Membership Queries.
117/// It is defined as the `FixedHeader` type for `IgmpMembershipQueryV3`.
118#[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    /// Returns the the number of sources.
132    pub fn number_of_sources(&self) -> u16 {
133        self.number_of_sources.get()
134    }
135
136    /// Returns the query's group address.
137    pub fn group_address(&self) -> Ipv4Addr {
138        self.group_address
139    }
140
141    /// Gets value of `S Flag`.
142    ///
143    /// Indicates to any receiving multicast routers that they are to suppress
144    /// the normal timer updates they perform upon hearing a Query. It does not,
145    /// however, suppress the querier election or the normal "host-side"
146    /// processing of a Query that a router may be required to perform as a
147    /// consequence of itself being a group member.
148    pub fn suppress_router_side_processing(&self) -> bool {
149        (self.sqrv & Self::S_FLAG) != 0
150    }
151
152    /// Gets querier robustness variable (QRV).
153    ///
154    /// If non-zero, the QRV field contains the *Robustness Variable* value
155    /// used by the querier, i.e., the sender of the Query.  If the querier's
156    /// *Robustness Variable* exceeds 7, the maximum value of the QRV field,
157    /// the QRV is set to zero.  Routers adopt the QRV value from the most
158    /// recently received Query as their own *Robustness Variable* value,
159    /// unless that most recently received QRV was zero, in which case the
160    /// receivers use the default *Robustness Variable* value specified in
161    /// [RFC 3376 section 8.1] (defined as `DEFAULT_QRV`) or a statically
162    /// configured value.
163    ///
164    /// [RFC 3376 section 8.1]: https://tools.ietf.org/html/rfc3376#section-8.1
165    pub fn querier_robustness_variable(&self) -> u8 {
166        self.sqrv & Self::QRV_MSK
167    }
168
169    /// Gets the querier's query interval (QQI).
170    ///
171    /// Multicast routers that are not the current querier adopt the QQI
172    /// value from the most recently received Query as their own *Query
173    /// Interval* value, unless that most recently received QQI was zero, in
174    /// which case the receiving routers use the default *Query Interval*
175    /// value specified in [RFC 3376 section 8.2], defined as
176    /// `DEFAULT_QUERY_INTERVAL`.
177    ///
178    /// [RFC 3376 section 8.2]: https://tools.ietf.org/html/rfc3376#section-8.2
179    pub fn querier_query_interval(&self) -> core::time::Duration {
180        // qqic is represented in a packed floating point format and interpreted
181        // as units of seconds.
182        Igmpv3QQIC::from(self.qqic).into()
183    }
184}
185
186/// QRV (Querier's Robustness Variable) defined
187/// in [RFC 3376 section 4.1.6].
188///
189/// Aliased to shared GMP implementation for convenience.
190///
191/// [RFC 3376 section 4.1.6]:
192///     https://datatracker.ietf.org/doc/html/rfc3376#section-4.1.6
193pub type Igmpv3QRV = crate::gmp::QRV;
194
195/// QQIC (Querier's Query Interval Code) defined in [RFC 3376 section 4.1.7].
196///
197/// Aliased to shared GMP implementation for convenience.
198///
199/// [RFC 3376 section 4.1.7]:
200///     https://datatracker.ietf.org/doc/html/rfc3376#section-4.1.7
201pub type Igmpv3QQIC = crate::gmp::QQIC;
202
203/// IGMPv3 Membership Query message.
204///
205/// `IgmpMembershipQueryV3` implements `MessageType`, providing the intended
206/// behavior for IGMPv3 Membership Queries as defined in
207/// [RFC 3376 section 4.1].
208///
209/// Membership Queries are sent by IP multicast routers to query the
210/// multicast reception state of neighboring interfaces.
211///
212/// [RFC 3376 section 4.1]: https://tools.ietf.org/html/rfc3376#section-4.1
213#[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    /// Reinterprets this [`IgmpMembershipQueryV3`] message as an
246    /// [`IgmpMembershipQueryV2`] message.
247    ///
248    /// Given this crate parses the version separately, users desiring to
249    /// operate in IGMPv2 or IGMPv1 modes *SHOULD* reinterpret V3 queries as the
250    /// older version.
251    ///
252    /// See [RFC 3376 section 7.2.1] and [RFC 2236 section 2.5].
253    ///
254    /// [RFC 3376 section 7.2.1]:
255    ///     https://datatracker.ietf.org/doc/html/rfc3376#section-7.2.1
256    /// [RFC 2236 section 2.5]:
257    ///     https://datatracker.ietf.org/doc/html/rfc2236#section-2.5
258    pub fn as_v2_query(&self) -> IgmpMessage<&[u8], IgmpMembershipQueryV2> {
259        let Self { prefix, header, body: _ } = self;
260        // Unwraps are okay here, we know the sizes and alignments must fit.
261        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/// Fixed information in IGMPv3 Membership Reports.
268///
269/// A `MembershipReportV3Data` struct represents the fixed data in IGMPv3
270/// Membership Reports.
271/// It is defined as the `FixedHeader` type for `IgmpMembershipReportV3`.
272#[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    /// Returns the number of group records.
281    pub fn number_of_group_records(self) -> u16 {
282        self.number_of_group_records.get()
283    }
284}
285
286/// Group Record Types as defined in [RFC 3376 section 4.2.12]
287///
288/// Aliased to shared GMP implementation for convenience.
289///
290/// [RFC 3376 section 4.2.12]:
291///     https://tools.ietf.org/html/rfc3376#section-4.2.12
292pub type IgmpGroupRecordType = crate::gmp::GroupRecordType;
293
294/// Fixed information for IGMPv3 Membership Report's Group Records.
295///
296/// A `GroupRecordHeader` struct represents the fixed header of a Group Record
297/// in IGMPv3 Membership Report messages as defined in [RFC 3376 section 4.2.4].
298///
299/// The `GroupRecordHeader` is followed by a series of source IPv4 addresses.
300///
301/// [RFC 3376 section 4.2.4]: https://tools.ietf.org/html/rfc3376#section-4.2.4
302#[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    /// Returns the number of sources.
313    pub fn number_of_sources(&self) -> u16 {
314        self.number_of_sources.get()
315    }
316
317    /// Returns the type of the record.
318    pub fn record_type(&self) -> Result<IgmpGroupRecordType, UnrecognizedProtocolCode<u8>> {
319        IgmpGroupRecordType::try_from(self.record_type)
320    }
321
322    /// Returns the multicast address.
323    pub fn multicast_addr(&self) -> &Ipv4Addr {
324        &self.multicast_address
325    }
326}
327
328/// Wire representation of an IGMPv3 Membership Report's Group Records.
329///
330/// A `GroupRecord` struct is composed of a fixed part provided by
331/// `GroupRecordHeader` and a variable part, which is a list of IPv4 addresses.
332///
333/// Each Group Record is a block of fields containing information
334/// pertaining to the sender's membership in a single multicast group on
335/// the interface from which the Report is sent.
336///
337/// The structure of a Group Record includes Auxiliary Data, as defined in
338/// [RFC 3376 section 4.2.10]. As the information in Auxiliary Data is supposed
339/// to be ignored, when parsing a `GroupRecord` struct from a buffer, the
340/// information in Auxiliary Data, if any, is discarded.
341///
342/// [RFC 3376 section 4.2.10]: https://tools.ietf.org/html/rfc3376#section-4.2.10
343pub struct GroupRecord<B> {
344    header: Ref<B, GroupRecordHeader>,
345    sources: Ref<B, [Ipv4Addr]>,
346}
347
348impl<B: SplitByteSlice> GroupRecord<B> {
349    /// Returns the group record header.
350    pub fn header(&self) -> &GroupRecordHeader {
351        self.header.deref()
352    }
353
354    /// Returns the group record's sources.
355    pub fn sources(&self) -> &[Ipv4Addr] {
356        self.sources.deref()
357    }
358}
359
360/// IGMPv3 Membership Report message.
361///
362/// `IgmpMembershipReportV3` implements `MessageType`, providing the intended
363/// behavior for IGMPv3 Membership Reports as defined in
364/// [RFC 3376 section 4.2].
365///
366/// Version 3 Membership Reports are sent by IP systems to report (to
367/// neighboring routers) the current multicast reception state, or
368/// changes in the multicast reception state, of their interfaces.
369///
370/// [RFC 3376 section 4.2]: https://tools.ietf.org/html/rfc3376#section-4.2
371#[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        // every record may have aux_data_len 32-bit words at the end.
419        // we need to update our buffer view to reflect that.
420        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/// IGMPv1 Membership Report message.
429///
430/// `IgmpMembershipReportV1` implements `MessageType`, providing the intended
431/// behavior for IGMPv1 Membership Reports as defined in [RFC 1112].
432///
433/// In a Host Membership Report message, the group address field (expressed in
434/// `FixedHeader`) holds the IP host group address of the group being reported.
435///
436/// Hosts respond to a Query by generating Host Membership Reports, reporting
437/// each host group to which they belong on the network interface from which the
438/// Query was received.
439///
440/// [RFC 1112]: https://tools.ietf.org/html/rfc1112
441#[derive(Debug)]
442pub struct IgmpMembershipReportV1;
443
444impl_igmp_simple_message_type!(IgmpMembershipReportV1, MembershipReportV1, Ipv4Addr);
445
446/// IGMPv2 Membership Report message.
447///
448/// `IgmpMembershipReportV2` implements `MessageType`, providing the intended
449/// behavior for IGMPv2 Membership Reports as defined in [RFC 2236].
450///
451/// In a Membership Report message, the group address field (expressed in
452/// `FixedHeader`) holds the IP multicast group address of the group being
453/// reported.
454///
455/// Hosts respond to a Query by generating Host Membership Reports, reporting
456/// each host group to which they belong on the network interface from which the
457/// Query was received.
458///
459/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
460#[derive(Debug)]
461pub struct IgmpMembershipReportV2;
462
463impl_igmp_simple_message_type!(IgmpMembershipReportV2, MembershipReportV2, Ipv4Addr);
464
465/// IGMP Leave Group message.
466///
467/// `IgmpLeaveGroup` implements `MessageType`, providing the intended behavior
468/// for IGMP LeaveGroup as defined in [RFC 2236].
469///
470/// In a Leave Group message, the group address field (expressed in
471/// `FixedHeader`) holds the IP multicast group address of the group being
472/// left.
473///
474/// When a host leaves a multicast group, if it was the last host to reply to a
475/// Query with a Membership Report for that group, it SHOULD send a Leave Group
476/// message to the all-routers multicast group (224.0.0.2).
477///
478/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
479#[derive(Debug)]
480pub struct IgmpLeaveGroup;
481
482impl_igmp_simple_message_type!(IgmpLeaveGroup, LeaveGroup, Ipv4Addr);
483
484/// An IGMP packet with a dynamic message type.
485///
486/// Each enum variant contains an `IgmpMessage` of
487/// the appropriate static type, making it easier to call `parse` without
488/// knowing the message type ahead of time while still getting the benefits of a
489/// statically-typed packet struct after parsing is complete.
490#[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/// A builder for IGMPv3 membership queries.
542///
543/// This differs from [`IgmpPacketBuilder`] in that it implements
544/// [`InnerPacketBuilder`] directly, and provides a more ergonomic way of
545/// building queries than nesting builders.
546#[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    /// Creates a new [`IgmpMembershipQueryV3Builder`].
558    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        // Serialize the body first, counting the records.
588        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/// A builder for IGMPv3 membership reports.
610///
611/// This differs from [`IgmpPacketBuilder`] in that it implements
612/// [`InnerPacketBuilder`\ directly, and provides a more ergonomic way of
613/// building reports than nesting builders.
614#[derive(Debug)]
615pub struct IgmpMembershipReportV3Builder<I> {
616    groups: I,
617}
618
619impl<I> IgmpMembershipReportV3Builder<I> {
620    /// Creates a new [`IgmpMembershipReportV3Builder`].
621    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    /// Transform this builder into an iterator of builders with a given
633    /// `max_len` for each generated packet.
634    ///
635    /// `max_len` is the maximum length each builder yielded by the returned
636    /// iterator can have. The groups used to create this builder are split into
637    /// multiple reports in order to meet this length. Note that this length
638    /// does _not_ account for the IP header, but it accounts for the entire
639    /// IGMP packet.
640    ///
641    /// Returns `Err` if `max_len` is not large enough to meet minimal
642    /// constraints for each report.
643    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        // Serialize the body first, counting the records.
688        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::{NestablePacketBuilder as _, NoOpSerializationContext, 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(&mut NoOpSerializationContext)
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()
765            .into_serializer()
766            .serialize_vec_outer(&mut NoOpSerializationContext)
767            .unwrap()
768            .as_ref()
769            .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            // When interpreted as a v2 query we should get the same values.
847            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            // look at first group record:
864            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            // look at second group record:
874            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 that no other records came in:
883            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
902            .into_serializer()
903            .serialize_vec_outer(&mut NoOpSerializationContext)
904            .unwrap()
905            .unwrap_b();
906        assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
907    }
908
909    #[test]
910    fn membership_report_v3_builder() {
911        set_logger_for_test();
912        use igmp_reports::v3::*;
913        let builder = IgmpMembershipReportV3Builder::new(
914            [
915                (MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
916                (MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
917            ]
918            .into_iter()
919            .map(|(addr, rec_type, sources)| {
920                (
921                    MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
922                    IgmpGroupRecordType::try_from(rec_type).unwrap(),
923                    sources.iter().copied().map(Ipv4Addr::new),
924                )
925            }),
926        );
927        let serialized = builder
928            .into_serializer()
929            .serialize_vec_outer(&mut NoOpSerializationContext)
930            .unwrap()
931            .unwrap_b();
932        assert_eq!(serialized.as_ref(), MEMBER_REPORT);
933    }
934
935    // Test that our maximum sources accounting matches an equivalent example
936    // in RFC 3376 section 4.1.8:
937    //
938    //  For example, on an Ethernet with an MTU of 1500 octets, the IP header
939    //  including the Router Alert option consumes 24 octets, and the IGMP
940    //  fields up to including the Number of Sources (N) field consume 12
941    //  octets, leaving 1464 octets for source addresses, which limits the
942    //  number of source addresses to 366 (1464/4).
943    //
944    // This example is for queries, reports have 8 octets of IGMP headers + 8
945    // octets of group record header, hence (1500 - 24 - 16)/4 = 365.
946    #[test]
947    fn membership_report_v3_split_many_sources() {
948        use igmp_reports::v3::*;
949        use packet::{NestablePacketBuilder as _, NestableSerializer as _};
950        const ETH_MTU: usize = 1500;
951        const MAX_SOURCES: usize = 365;
952
953        let src = Ipv4Addr::new(SRC_1_1);
954        let ip_builder = Ipv4PacketBuilderWithOptions::new(
955            Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
956            &[Ipv4Option::RouterAlert { data: 0 }],
957        )
958        .unwrap();
959        let ip_header = ip_builder.constraints().header_len();
960
961        let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
962        let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
963        let reports = IgmpMembershipReportV3Builder::new(
964            [(
965                group_addr,
966                IgmpGroupRecordType::ModeIsInclude,
967                (0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
968            )]
969            .into_iter(),
970        )
971        .with_len_limits(ETH_MTU - ip_header)
972        .unwrap();
973
974        let mut reports = reports.map(|builder| {
975            builder
976                .into_serializer()
977                .wrap_in(ip_builder.clone())
978                .serialize_vec_outer(&mut NoOpSerializationContext)
979                .unwrap_or_else(|(err, _)| panic!("{err:?}"))
980                .unwrap_b()
981                .into_inner()
982        });
983        // We can generate a report at exactly ETH_MTU.
984        let serialized = reports.next().unwrap();
985        assert_eq!(serialized.len(), ETH_MTU);
986
987        let mut buffer = &serialized[..];
988        let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
989        let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
990        let mut groups = igmp.body.iter();
991        let group = groups.next().expect("has group");
992        assert_eq!(group.header.multicast_address, group_addr.get());
993        assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
994        assert_eq!(group.sources().len(), MAX_SOURCES);
995        for (i, addr) in group.sources().iter().enumerate() {
996            assert_eq!(*addr, src_ip(i));
997        }
998        assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
999
1000        // Only one report is generated.
1001        assert_eq!(reports.next(), None);
1002
1003        let reports = IgmpMembershipReportV3Builder::new(
1004            [(
1005                group_addr,
1006                IgmpGroupRecordType::ModeIsInclude,
1007                core::iter::repeat(src).take(MAX_SOURCES + 1),
1008            )]
1009            .into_iter(),
1010        )
1011        .with_len_limits(ETH_MTU - ip_header)
1012        .unwrap();
1013        // 2 reports are generated with one extra source.
1014        assert_eq!(
1015            reports
1016                .map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
1017                .collect::<Vec<_>>(),
1018            vec![vec![MAX_SOURCES], vec![1]]
1019        );
1020    }
1021
1022    // Like membership_report_v3_split_many_sources but we calculate how many
1023    // groups with no sources specified we can have in the same 1500 Ethernet
1024    // MTU.
1025    //
1026    // * 24 bytes for IPv4 header + router alert option.
1027    // * 8 bytes for IGMP header + report up to number of groups.
1028    // * 8 bytes per group with no sources.
1029    //
1030    // So we should be able to fit (1500 - 24 - 8)/8 = 183.5 groups. 183
1031    // groups result in a a 1496 byte-long message.
1032    #[test]
1033    fn membership_report_v3_split_many_groups() {
1034        use igmp_reports::v3::*;
1035        use packet::{NestablePacketBuilder as _, NestableSerializer as _};
1036
1037        const ETH_MTU: usize = 1500;
1038        const EXPECT_SERIALIZED: usize = 1496;
1039        const MAX_GROUPS: usize = 183;
1040
1041        let src = Ipv4Addr::new(SRC_1_1);
1042        let ip_builder = Ipv4PacketBuilderWithOptions::new(
1043            Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
1044            &[Ipv4Option::RouterAlert { data: 0 }],
1045        )
1046        .unwrap();
1047        let ip_header = ip_builder.constraints().header_len();
1048
1049        let group_ip = |i: usize| {
1050            MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
1051        };
1052        let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
1053            (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1054        }))
1055        .with_len_limits(ETH_MTU - ip_header)
1056        .unwrap();
1057
1058        let mut reports = reports.map(|builder| {
1059            builder
1060                .into_serializer()
1061                .wrap_in(ip_builder.clone())
1062                .serialize_vec_outer(&mut NoOpSerializationContext)
1063                .unwrap_or_else(|(err, _)| panic!("{err:?}"))
1064                .unwrap_b()
1065                .into_inner()
1066        });
1067        // We can generate a report at exactly ETH_MTU.
1068        let serialized = reports.next().unwrap();
1069        assert_eq!(serialized.len(), EXPECT_SERIALIZED);
1070
1071        let mut buffer = &serialized[..];
1072        let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
1073        let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
1074        assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
1075        for (i, group) in igmp.body.iter().enumerate() {
1076            assert_eq!(group.header.multicast_address, group_ip(i).get());
1077            assert_eq!(group.header.number_of_sources.get(), 0);
1078        }
1079
1080        // Only one report is generated.
1081        assert_eq!(reports.next(), None);
1082
1083        let reports =
1084            IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
1085                (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1086            }))
1087            .with_len_limits(ETH_MTU - ip_header)
1088            .unwrap();
1089        // 2 reports are generated with one extra source.
1090        assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
1091    }
1092
1093    #[test]
1094    fn membership_report_v1_parse_and_serialize() {
1095        use igmp_reports::v1;
1096        set_logger_for_test();
1097        test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
1098            assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
1099        });
1100    }
1101
1102    #[test]
1103    fn membership_report_v2_parse_and_serialize() {
1104        use igmp_reports::v2;
1105        set_logger_for_test();
1106        test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
1107            assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
1108        });
1109    }
1110
1111    #[test]
1112    fn leave_group_parse_and_serialize() {
1113        set_logger_for_test();
1114        test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
1115            igmp_leave_group::LEAVE_GROUP,
1116            |igmp| {
1117                assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
1118            },
1119        );
1120    }
1121
1122    #[test]
1123    fn test_unknown_type() {
1124        let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
1125        let mut buff = buff.as_mut_slice();
1126        let packet = buff.parse_with::<_, IgmpPacket<_>>(());
1127        // we don't use expect_err here because IgmpPacket does not implement
1128        // core::fmt::Debug
1129        assert_eq!(packet.is_err(), true);
1130    }
1131
1132    #[test]
1133    fn test_full_parses() {
1134        let mut bufs = ALL_BUFFERS.to_vec();
1135        for buff in bufs.iter_mut() {
1136            let orig_req = &buff[..];
1137            let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
1138            let msg_type = match packet {
1139                IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
1140                IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
1141                IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
1142                IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
1143                IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
1144                IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
1145            };
1146            assert_eq!(msg_type, orig_req[0]);
1147        }
1148    }
1149
1150    #[test]
1151    fn test_partial_parses() {
1152        // parsing a part of the buffer should always result in errors and
1153        // nothing panics.
1154        for buff in ALL_BUFFERS.iter() {
1155            for i in 0..buff.len() {
1156                let partial_buff = &mut &buff[0..i];
1157                let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
1158                assert_eq!(packet.is_err(), true)
1159            }
1160        }
1161    }
1162
1163    // Asserts that a `Message` without `VariableBody` should have the same length
1164    // as the given ground truth packet.
1165    fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
1166        mut ground_truth: &[u8],
1167    ) {
1168        let ground_truth_len = ground_truth.len();
1169        let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
1170        let builder_len = igmp.builder().bytes_len();
1171        assert_eq!(builder_len, ground_truth_len);
1172    }
1173
1174    #[test]
1175    fn test_igmp_packet_length() {
1176        assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
1177        assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
1178        assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
1179        assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
1180    }
1181}