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::{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            // When interpreted as a v2 query we should get the same values.
842            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            // look at first group record:
859            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            // look at second group record:
869            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 that no other records came in:
878            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 that our maximum sources accounting matches an equivalent example
923    // in RFC 3376 section 4.1.8:
924    //
925    //  For example, on an Ethernet with an MTU of 1500 octets, the IP header
926    //  including the Router Alert option consumes 24 octets, and the IGMP
927    //  fields up to including the Number of Sources (N) field consume 12
928    //  octets, leaving 1464 octets for source addresses, which limits the
929    //  number of source addresses to 366 (1464/4).
930    //
931    // This example is for queries, reports have 8 octets of IGMP headers + 8
932    // octets of group record header, hence (1500 - 24 - 16)/4 = 365.
933    #[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        // We can generate a report at exactly ETH_MTU.
971        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        // Only one report is generated.
988        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        // 2 reports are generated with one extra source.
1001        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    // Like membership_report_v3_split_many_sources but we calculate how many
1010    // groups with no sources specified we can have in the same 1500 Ethernet
1011    // MTU.
1012    //
1013    // * 24 bytes for IPv4 header + router alert option.
1014    // * 8 bytes for IGMP header + report up to number of groups.
1015    // * 8 bytes per group with no sources.
1016    //
1017    // So we should be able to fit (1500 - 24 - 8)/8 = 183.5 groups. 183
1018    // groups result in a a 1496 byte-long message.
1019    #[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        // We can generate a report at exactly ETH_MTU.
1055        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        // Only one report is generated.
1068        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        // 2 reports are generated with one extra source.
1077        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        // we don't use expect_err here because IgmpPacket does not implement
1115        // core::fmt::Debug
1116        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        // parsing a part of the buffer should always result in errors and
1140        // nothing panics.
1141        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    // Asserts that a `Message` without `VariableBody` should have the same length
1151    // as the given ground truth packet.
1152    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}