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        if *ctx == 0 {
413            return Ok(ParsedRecord::Done);
414        }
415        let header = data
416            .take_obj_front::<GroupRecordHeader>()
417            .ok_or_else(debug_err_fn!(ParseError::Format, "Can't take group record header"))?;
418        let sources = data
419            .take_slice_front::<Ipv4Addr>(header.number_of_sources().into())
420            .ok_or_else(debug_err_fn!(ParseError::Format, "Can't group record sources"))?;
421        // every record may have aux_data_len 32-bit words at the end.
422        // we need to update our buffer view to reflect that.
423        let _ = data
424            .take_front(usize::from(header.aux_data_len) * 4)
425            .ok_or_else(debug_err_fn!(ParseError::Format, "Can't skip auxiliary data"))?;
426
427        *ctx -= 1;
428        Ok(ParsedRecord::Parsed(Self::Record { header, sources }))
429    }
430}
431
432/// IGMPv1 Membership Report message.
433///
434/// `IgmpMembershipReportV1` implements `MessageType`, providing the intended
435/// behavior for IGMPv1 Membership Reports as defined in [RFC 1112].
436///
437/// In a Host Membership Report message, the group address field (expressed in
438/// `FixedHeader`) holds the IP host group address of the group being reported.
439///
440/// Hosts respond to a Query by generating Host Membership Reports, reporting
441/// each host group to which they belong on the network interface from which the
442/// Query was received.
443///
444/// [RFC 1112]: https://tools.ietf.org/html/rfc1112
445#[derive(Debug)]
446pub struct IgmpMembershipReportV1;
447
448impl_igmp_simple_message_type!(IgmpMembershipReportV1, MembershipReportV1, Ipv4Addr);
449
450/// IGMPv2 Membership Report message.
451///
452/// `IgmpMembershipReportV2` implements `MessageType`, providing the intended
453/// behavior for IGMPv2 Membership Reports as defined in [RFC 2236].
454///
455/// In a Membership Report message, the group address field (expressed in
456/// `FixedHeader`) holds the IP multicast group address of the group being
457/// reported.
458///
459/// Hosts respond to a Query by generating Host Membership Reports, reporting
460/// each host group to which they belong on the network interface from which the
461/// Query was received.
462///
463/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
464#[derive(Debug)]
465pub struct IgmpMembershipReportV2;
466
467impl_igmp_simple_message_type!(IgmpMembershipReportV2, MembershipReportV2, Ipv4Addr);
468
469/// IGMP Leave Group message.
470///
471/// `IgmpLeaveGroup` implements `MessageType`, providing the intended behavior
472/// for IGMP LeaveGroup as defined in [RFC 2236].
473///
474/// In a Leave Group message, the group address field (expressed in
475/// `FixedHeader`) holds the IP multicast group address of the group being
476/// left.
477///
478/// When a host leaves a multicast group, if it was the last host to reply to a
479/// Query with a Membership Report for that group, it SHOULD send a Leave Group
480/// message to the all-routers multicast group (224.0.0.2).
481///
482/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
483#[derive(Debug)]
484pub struct IgmpLeaveGroup;
485
486impl_igmp_simple_message_type!(IgmpLeaveGroup, LeaveGroup, Ipv4Addr);
487
488/// An IGMP packet with a dynamic message type.
489///
490/// Each enum variant contains an `IgmpMessage` of
491/// the appropriate static type, making it easier to call `parse` without
492/// knowing the message type ahead of time while still getting the benefits of a
493/// statically-typed packet struct after parsing is complete.
494#[allow(missing_docs)]
495#[derive(Debug)]
496pub enum IgmpPacket<B: SplitByteSlice> {
497    MembershipQueryV2(IgmpMessage<B, IgmpMembershipQueryV2>),
498    MembershipQueryV3(IgmpMessage<B, IgmpMembershipQueryV3>),
499    MembershipReportV1(IgmpMessage<B, IgmpMembershipReportV1>),
500    MembershipReportV2(IgmpMessage<B, IgmpMembershipReportV2>),
501    MembershipReportV3(IgmpMessage<B, IgmpMembershipReportV3>),
502    LeaveGroup(IgmpMessage<B, IgmpLeaveGroup>),
503}
504
505impl<B: SplitByteSlice> ParsablePacket<B, ()> for IgmpPacket<B> {
506    type Error = ParseError;
507
508    fn parse_metadata(&self) -> ParseMetadata {
509        use self::IgmpPacket::*;
510        match self {
511            MembershipQueryV2(p) => p.parse_metadata(),
512            MembershipQueryV3(p) => p.parse_metadata(),
513            MembershipReportV1(p) => p.parse_metadata(),
514            MembershipReportV2(p) => p.parse_metadata(),
515            MembershipReportV3(p) => p.parse_metadata(),
516            LeaveGroup(p) => p.parse_metadata(),
517        }
518    }
519
520    fn parse<BV: BufferView<B>>(buffer: BV, args: ()) -> Result<Self, ParseError> {
521        macro_rules! mtch {
522            ($buffer:expr, $args:expr, $( ($code:ident, $long:tt) => $type:ty, $variant:ident )*) => {
523                match peek_message_type($buffer.as_ref())? {
524                    $( (IgmpMessageType::$code, $long) => {
525                        let packet = <IgmpMessage<B,$type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
526                        IgmpPacket::$variant(packet)
527                    })*,
528                }
529            }
530        }
531
532        Ok(mtch!(
533            buffer,
534            args,
535            (MembershipQuery, false) => IgmpMembershipQueryV2, MembershipQueryV2
536            (MembershipQuery, true) => IgmpMembershipQueryV3, MembershipQueryV3
537            (MembershipReportV1, _) => IgmpMembershipReportV1, MembershipReportV1
538            (MembershipReportV2, _) => IgmpMembershipReportV2, MembershipReportV2
539            (MembershipReportV3, _) => IgmpMembershipReportV3, MembershipReportV3
540            (LeaveGroup, _) => IgmpLeaveGroup, LeaveGroup
541        ))
542    }
543}
544
545/// A builder for IGMPv3 membership queries.
546///
547/// This differs from [`IgmpPacketBuilder`] in that it implements
548/// [`InnerPacketBuilder`] directly, and provides a more ergonomic way of
549/// building queries than nesting builders.
550#[derive(Debug)]
551pub struct IgmpMembershipQueryV3Builder<I> {
552    max_resp_time: IgmpResponseTimeV3,
553    group_addr: Option<MulticastAddr<Ipv4Addr>>,
554    s_flag: bool,
555    qrv: Igmpv3QRV,
556    qqic: Igmpv3QQIC,
557    sources: I,
558}
559
560impl<I> IgmpMembershipQueryV3Builder<I> {
561    /// Creates a new [`IgmpMembershipQueryV3Builder`].
562    pub fn new(
563        max_resp_time: IgmpResponseTimeV3,
564        group_addr: Option<MulticastAddr<Ipv4Addr>>,
565        s_flag: bool,
566        qrv: Igmpv3QRV,
567        qqic: Igmpv3QQIC,
568        sources: I,
569    ) -> Self {
570        Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources }
571    }
572
573    const HEADER_SIZE: usize = super::total_header_size::<MembershipQueryData>();
574}
575
576impl<I> InnerPacketBuilder for IgmpMembershipQueryV3Builder<I>
577where
578    I: Iterator<Item = Ipv4Addr> + Clone,
579{
580    fn bytes_len(&self) -> usize {
581        Self::HEADER_SIZE + self.sources.clone().count() * core::mem::size_of::<Ipv4Addr>()
582    }
583
584    fn serialize(&self, buf: &mut [u8]) {
585        use packet::BufferViewMut;
586
587        let Self { max_resp_time, group_addr, s_flag, qrv, qqic, sources } = self;
588        let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
589        let mut bytes = &mut body[..];
590        let mut bytes = &mut bytes;
591        // Serialize the body first, counting the records.
592        let mut count: u16 = 0;
593        for src in sources.clone() {
594            count = count.checked_add(1).expect("overflowed number of sources");
595            bytes.write_obj_front(&src).expect("too few bytes for source");
596        }
597        let builder = IgmpPacketBuilder::<&mut [u8], IgmpMembershipQueryV3>::new_with_resp_time(
598            MembershipQueryData {
599                group_address: group_addr
600                    .as_ref()
601                    .map(|a| a.get())
602                    .unwrap_or(Ipv4::UNSPECIFIED_ADDRESS),
603                sqrv: (u8::from(*s_flag) << 3) | (MembershipQueryData::QRV_MSK & u8::from(*qrv)),
604                qqic: (*qqic).into(),
605                number_of_sources: count.into(),
606            },
607            *max_resp_time,
608        );
609        builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
610    }
611}
612
613/// A builder for IGMPv3 membership reports.
614///
615/// This differs from [`IgmpPacketBuilder`] in that it implements
616/// [`InnerPacketBuilder`\ directly, and provides a more ergonomic way of
617/// building reports than nesting builders.
618#[derive(Debug)]
619pub struct IgmpMembershipReportV3Builder<I> {
620    groups: I,
621}
622
623impl<I> IgmpMembershipReportV3Builder<I> {
624    /// Creates a new [`IgmpMembershipReportV3Builder`].
625    pub fn new(groups: I) -> Self {
626        Self { groups }
627    }
628
629    const HEADER_SIZE: usize = super::total_header_size::<MembershipReportV3Data>();
630}
631
632impl<I> IgmpMembershipReportV3Builder<I>
633where
634    I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr> + Clone> + Clone,
635{
636    /// Transform this builder into an iterator of builders with a given
637    /// `max_len` for each generated packet.
638    ///
639    /// `max_len` is the maximum length each builder yielded by the returned
640    /// iterator can have. The groups used to create this builder are split into
641    /// multiple reports in order to meet this length. Note that this length
642    /// does _not_ account for the IP header, but it accounts for the entire
643    /// IGMP packet.
644    ///
645    /// Returns `Err` if `max_len` is not large enough to meet minimal
646    /// constraints for each report.
647    pub fn with_len_limits(
648        self,
649        max_len: usize,
650    ) -> Result<
651        impl Iterator<
652            Item = IgmpMembershipReportV3Builder<
653                impl Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
654            >,
655        >,
656        InvalidConstraintsError,
657    > {
658        let Self { groups } = self;
659        crate::gmp::group_record_split_iterator(
660            max_len.saturating_sub(Self::HEADER_SIZE),
661            core::mem::size_of::<GroupRecordHeader>(),
662            groups,
663        )
664        .map(|iter| iter.map(|groups| IgmpMembershipReportV3Builder { groups }))
665    }
666}
667
668impl<I> InnerPacketBuilder for IgmpMembershipReportV3Builder<I>
669where
670    I: Iterator<Item: GmpReportGroupRecord<Ipv4Addr>> + Clone,
671{
672    fn bytes_len(&self) -> usize {
673        Self::HEADER_SIZE
674            + self
675                .groups
676                .clone()
677                .map(|g| {
678                    core::mem::size_of::<GroupRecordHeader>()
679                        + g.sources().count() * core::mem::size_of::<Ipv4Addr>()
680                })
681                .sum::<usize>()
682    }
683
684    fn serialize(&self, buf: &mut [u8]) {
685        use packet::BufferViewMut;
686
687        let Self { groups } = self;
688        let (header, body) = buf.split_at_mut(Self::HEADER_SIZE);
689        let mut bytes = &mut body[..];
690        let mut bytes = &mut bytes;
691        // Serialize the body first, counting the records.
692        let mut count: u16 = 0;
693        for group in groups.clone() {
694            count = count.checked_add(1).expect("multicast groups count overflows");
695            let mut header = bytes
696                .take_obj_front_zero::<GroupRecordHeader>()
697                .expect("too few bytes for record header");
698            let GroupRecordHeader {
699                record_type,
700                aux_data_len,
701                number_of_sources,
702                multicast_address,
703            } = &mut *header;
704            *record_type = group.record_type().into();
705            *aux_data_len = 0;
706            *multicast_address = group.group().into();
707            let mut source_count: u16 = 0;
708            for src in group.sources() {
709                source_count = source_count.checked_add(1).expect("sources count overflows");
710                bytes.write_obj_front(src.borrow()).expect("too few bytes for source");
711            }
712            *number_of_sources = source_count.into();
713        }
714
715        let builder =
716            IgmpPacketBuilder::<&mut [u8], IgmpMembershipReportV3>::new(MembershipReportV3Data {
717                _reserved: [0, 0],
718                number_of_group_records: count.into(),
719            });
720        builder.serialize_headers(header, FragmentedByteSlice::new(&mut [body][..]));
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use core::fmt::Debug;
727    use core::time::Duration;
728
729    use packet::{NestablePacketBuilder as _, NoOpSerializationContext, ParseBuffer, Serializer};
730
731    use super::*;
732    use crate::igmp::IgmpMaxRespCode;
733    use crate::igmp::testdata::*;
734    use crate::ip::Ipv4Proto;
735    use crate::ipv4::options::Ipv4Option;
736    use crate::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
737    use crate::testutil::set_logger_for_test;
738    use net_declare::net_ip_v4;
739
740    const ALL_BUFFERS: [&[u8]; 6] = [
741        igmp_router_queries::v2::QUERY,
742        igmp_router_queries::v3::QUERY,
743        igmp_reports::v1::MEMBER_REPORT,
744        igmp_reports::v2::MEMBER_REPORT,
745        igmp_reports::v3::MEMBER_REPORT,
746        igmp_leave_group::LEAVE_GROUP,
747    ];
748
749    fn serialize_to_bytes<B: SplitByteSlice + Debug, M: MessageType<B> + Debug>(
750        igmp: &IgmpMessage<B, M>,
751    ) -> Vec<u8>
752    where
753        M::VariableBody: IgmpNonEmptyBody,
754    {
755        igmp.builder()
756            .wrap_body(M::body_bytes(&igmp.body).into_serializer())
757            .serialize_vec_outer(&mut NoOpSerializationContext)
758            .unwrap()
759            .as_ref()
760            .to_vec()
761    }
762
763    fn serialize_to_bytes_inner<
764        B: SplitByteSlice + Debug,
765        M: MessageType<B, VariableBody = ()> + Debug,
766    >(
767        igmp: &IgmpMessage<B, M>,
768    ) -> Vec<u8> {
769        igmp.builder()
770            .into_serializer()
771            .serialize_vec_outer(&mut NoOpSerializationContext)
772            .unwrap()
773            .as_ref()
774            .to_vec()
775    }
776
777    fn test_parse_and_serialize<
778        B: SplitByteSlice + Debug,
779        BV: BufferView<B>,
780        M: MessageType<B> + Debug,
781        F: FnOnce(&IgmpMessage<B, M>),
782    >(
783        req: BV,
784        check: F,
785    ) where
786        M::VariableBody: IgmpNonEmptyBody,
787    {
788        let orig_req = req.as_ref().to_owned();
789
790        let igmp = IgmpMessage::<_, M>::parse(req, ()).unwrap();
791        check(&igmp);
792
793        let data = serialize_to_bytes(&igmp);
794        assert_eq!(data, orig_req);
795    }
796
797    fn test_parse_and_serialize_inner<
798        M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
799        F: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
800    >(
801        mut req: &[u8],
802        check: F,
803    ) {
804        let orig_req = req;
805
806        let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
807        check(&igmp);
808
809        let data = serialize_to_bytes_inner(&igmp);
810        assert_eq!(&data[..], orig_req);
811    }
812
813    #[test]
814    fn membership_query_v2_parse_and_serialize() {
815        set_logger_for_test();
816        test_parse_and_serialize_inner::<IgmpMembershipQueryV2, _>(
817            igmp_router_queries::v2::QUERY,
818            |igmp| {
819                assert_eq!(
820                    *igmp.header,
821                    Ipv4Addr::new(igmp_router_queries::v2::HOST_GROUP_ADDRESS)
822                );
823                assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v2::MAX_RESP_CODE);
824            },
825        );
826    }
827
828    #[test]
829    fn membership_query_v3_parse_and_serialize() {
830        set_logger_for_test();
831        let mut req = igmp_router_queries::v3::QUERY;
832        test_parse_and_serialize::<_, _, IgmpMembershipQueryV3, _>(&mut req, |igmp| {
833            assert_eq!(igmp.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
834            assert_eq!(
835                igmp.header.group_address,
836                Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)
837            );
838            assert_eq!(igmp.header.number_of_sources(), igmp_router_queries::v3::NUMBER_OF_SOURCES);
839            assert_eq!(
840                igmp.header.suppress_router_side_processing(),
841                igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE
842            );
843            assert_eq!(igmp.header.querier_robustness_variable(), igmp_router_queries::v3::QRV);
844            assert_eq!(
845                igmp.header.querier_query_interval().as_secs() as u32,
846                igmp_router_queries::v3::QQIC_SECS
847            );
848            assert_eq!(igmp.body.len(), igmp_router_queries::v3::NUMBER_OF_SOURCES as usize);
849            assert_eq!(igmp.body[0], Ipv4Addr::new(igmp_router_queries::v3::SOURCE));
850
851            // When interpreted as a v2 query we should get the same values.
852            let v2 = igmp.as_v2_query();
853            assert_eq!(v2.prefix.max_resp_code, igmp_router_queries::v3::MAX_RESP_CODE);
854            assert_eq!(*(v2.header), Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS));
855        });
856    }
857
858    #[test]
859    fn membership_report_v3_parse_and_serialize() {
860        use igmp_reports::v3::*;
861
862        set_logger_for_test();
863        let mut req = MEMBER_REPORT;
864        test_parse_and_serialize::<_, _, IgmpMembershipReportV3, _>(&mut req, |igmp| {
865            assert_eq!(igmp.header.number_of_group_records(), NUMBER_OF_RECORDS);
866            assert_eq!(igmp.prefix.max_resp_code, MAX_RESP_CODE);
867            let mut iter = igmp.body.iter();
868            // look at first group record:
869            let rec1 = iter.next().unwrap();
870            assert_eq!(rec1.header().number_of_sources(), NUMBER_OF_SOURCES_1);
871            assert_eq!(rec1.header().record_type, RECORD_TYPE_1);
872            assert_eq!(rec1.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_1));
873            assert_eq!(rec1.header().record_type(), Ok(IgmpGroupRecordType::ModeIsInclude));
874            assert_eq!(rec1.sources().len(), NUMBER_OF_SOURCES_1 as usize);
875            assert_eq!(rec1.sources()[0], Ipv4Addr::new(SRC_1_1));
876            assert_eq!(rec1.sources()[1], Ipv4Addr::new(SRC_1_2));
877
878            // look at second group record:
879            let rec2 = iter.next().unwrap();
880            assert_eq!(rec2.header().number_of_sources(), NUMBER_OF_SOURCES_2);
881            assert_eq!(rec2.header().record_type, RECORD_TYPE_2);
882            assert_eq!(rec2.header().multicast_address, Ipv4Addr::new(MULTICAST_ADDR_2));
883            assert_eq!(rec2.header().record_type(), Ok(IgmpGroupRecordType::ModeIsExclude));
884            assert_eq!(rec2.sources().len(), NUMBER_OF_SOURCES_2 as usize);
885            assert_eq!(rec2.sources()[0], Ipv4Addr::new(SRC_2_1));
886
887            // assert that no other records came in:
888            assert_eq!(iter.next().is_none(), true);
889        });
890    }
891
892    #[test]
893    fn membership_query_v3_builder() {
894        set_logger_for_test();
895        let builder = IgmpMembershipQueryV3Builder::new(
896            IgmpResponseTimeV3::from_code(igmp_router_queries::v3::MAX_RESP_CODE),
897            Some(
898                MulticastAddr::new(Ipv4Addr::new(igmp_router_queries::v3::GROUP_ADDRESS)).unwrap(),
899            ),
900            igmp_router_queries::v3::SUPPRESS_ROUTER_SIDE,
901            Igmpv3QRV::new(igmp_router_queries::v3::QRV),
902            Igmpv3QQIC::new_exact(Duration::from_secs(igmp_router_queries::v3::QQIC_SECS.into()))
903                .unwrap(),
904            [Ipv4Addr::new(igmp_router_queries::v3::SOURCE)].into_iter(),
905        );
906        let serialized = builder
907            .into_serializer()
908            .serialize_vec_outer(&mut NoOpSerializationContext)
909            .unwrap()
910            .unwrap_b();
911        assert_eq!(serialized.as_ref(), igmp_router_queries::v3::QUERY);
912    }
913
914    #[test]
915    fn membership_report_v3_builder() {
916        set_logger_for_test();
917        use igmp_reports::v3::*;
918        let builder = IgmpMembershipReportV3Builder::new(
919            [
920                (MULTICAST_ADDR_1, RECORD_TYPE_1, &[SRC_1_1, SRC_1_2][..]),
921                (MULTICAST_ADDR_2, RECORD_TYPE_2, &[SRC_2_1][..]),
922            ]
923            .into_iter()
924            .map(|(addr, rec_type, sources)| {
925                (
926                    MulticastAddr::new(Ipv4Addr::new(addr)).unwrap(),
927                    IgmpGroupRecordType::try_from(rec_type).unwrap(),
928                    sources.iter().copied().map(Ipv4Addr::new),
929                )
930            }),
931        );
932        let serialized = builder
933            .into_serializer()
934            .serialize_vec_outer(&mut NoOpSerializationContext)
935            .unwrap()
936            .unwrap_b();
937        assert_eq!(serialized.as_ref(), MEMBER_REPORT);
938    }
939
940    // Test that our maximum sources accounting matches an equivalent example
941    // in RFC 3376 section 4.1.8:
942    //
943    //  For example, on an Ethernet with an MTU of 1500 octets, the IP header
944    //  including the Router Alert option consumes 24 octets, and the IGMP
945    //  fields up to including the Number of Sources (N) field consume 12
946    //  octets, leaving 1464 octets for source addresses, which limits the
947    //  number of source addresses to 366 (1464/4).
948    //
949    // This example is for queries, reports have 8 octets of IGMP headers + 8
950    // octets of group record header, hence (1500 - 24 - 16)/4 = 365.
951    #[test]
952    fn membership_report_v3_split_many_sources() {
953        use igmp_reports::v3::*;
954        use packet::{NestablePacketBuilder as _, NestableSerializer as _};
955        const ETH_MTU: usize = 1500;
956        const MAX_SOURCES: usize = 365;
957
958        let src = Ipv4Addr::new(SRC_1_1);
959        let ip_builder = Ipv4PacketBuilderWithOptions::new(
960            Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
961            &[Ipv4Option::RouterAlert { data: 0 }],
962        )
963        .unwrap();
964        let ip_header = ip_builder.constraints().header_len();
965
966        let src_ip = |i: usize| Ipv4Addr::new([10, 0, (i >> 8) as u8, i as u8]);
967        let group_addr = MulticastAddr::new(Ipv4Addr::new(MULTICAST_ADDR_1)).unwrap();
968        let reports = IgmpMembershipReportV3Builder::new(
969            [(
970                group_addr,
971                IgmpGroupRecordType::ModeIsInclude,
972                (0..MAX_SOURCES).into_iter().map(|i| src_ip(i)),
973            )]
974            .into_iter(),
975        )
976        .with_len_limits(ETH_MTU - ip_header)
977        .unwrap();
978
979        let mut reports = reports.map(|builder| {
980            builder
981                .into_serializer()
982                .wrap_in(ip_builder.clone())
983                .serialize_vec_outer(&mut NoOpSerializationContext)
984                .unwrap_or_else(|(err, _)| panic!("{err:?}"))
985                .unwrap_b()
986                .into_inner()
987        });
988        // We can generate a report at exactly ETH_MTU.
989        let serialized = reports.next().unwrap();
990        assert_eq!(serialized.len(), ETH_MTU);
991
992        let mut buffer = &serialized[..];
993        let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
994        let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
995        let mut groups = igmp.body.iter();
996        let group = groups.next().expect("has group");
997        assert_eq!(group.header.multicast_address, group_addr.get());
998        assert_eq!(usize::from(group.header.number_of_sources()), MAX_SOURCES);
999        assert_eq!(group.sources().len(), MAX_SOURCES);
1000        for (i, addr) in group.sources().iter().enumerate() {
1001            assert_eq!(*addr, src_ip(i));
1002        }
1003        assert_eq!(groups.next().map(|r| r.header.multicast_address), None);
1004
1005        // Only one report is generated.
1006        assert_eq!(reports.next(), None);
1007
1008        let reports = IgmpMembershipReportV3Builder::new(
1009            [(
1010                group_addr,
1011                IgmpGroupRecordType::ModeIsInclude,
1012                core::iter::repeat(src).take(MAX_SOURCES + 1),
1013            )]
1014            .into_iter(),
1015        )
1016        .with_len_limits(ETH_MTU - ip_header)
1017        .unwrap();
1018        // 2 reports are generated with one extra source.
1019        assert_eq!(
1020            reports
1021                .map(|r| r.groups.map(|group| group.sources().count()).collect::<Vec<_>>())
1022                .collect::<Vec<_>>(),
1023            vec![vec![MAX_SOURCES], vec![1]]
1024        );
1025    }
1026
1027    // Like membership_report_v3_split_many_sources but we calculate how many
1028    // groups with no sources specified we can have in the same 1500 Ethernet
1029    // MTU.
1030    //
1031    // * 24 bytes for IPv4 header + router alert option.
1032    // * 8 bytes for IGMP header + report up to number of groups.
1033    // * 8 bytes per group with no sources.
1034    //
1035    // So we should be able to fit (1500 - 24 - 8)/8 = 183.5 groups. 183
1036    // groups result in a a 1496 byte-long message.
1037    #[test]
1038    fn membership_report_v3_split_many_groups() {
1039        use igmp_reports::v3::*;
1040        use packet::{NestablePacketBuilder as _, NestableSerializer as _};
1041
1042        const ETH_MTU: usize = 1500;
1043        const EXPECT_SERIALIZED: usize = 1496;
1044        const MAX_GROUPS: usize = 183;
1045
1046        let src = Ipv4Addr::new(SRC_1_1);
1047        let ip_builder = Ipv4PacketBuilderWithOptions::new(
1048            Ipv4PacketBuilder::new(src, src, 1, Ipv4Proto::Igmp),
1049            &[Ipv4Option::RouterAlert { data: 0 }],
1050        )
1051        .unwrap();
1052        let ip_header = ip_builder.constraints().header_len();
1053
1054        let group_ip = |i: usize| {
1055            MulticastAddr::new(Ipv4Addr::new([224, 0, (i >> 8) as u8, i as u8])).unwrap()
1056        };
1057        let reports = IgmpMembershipReportV3Builder::new((0..MAX_GROUPS).into_iter().map(|i| {
1058            (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1059        }))
1060        .with_len_limits(ETH_MTU - ip_header)
1061        .unwrap();
1062
1063        let mut reports = reports.map(|builder| {
1064            builder
1065                .into_serializer()
1066                .wrap_in(ip_builder.clone())
1067                .serialize_vec_outer(&mut NoOpSerializationContext)
1068                .unwrap_or_else(|(err, _)| panic!("{err:?}"))
1069                .unwrap_b()
1070                .into_inner()
1071        });
1072        // We can generate a report at exactly ETH_MTU.
1073        let serialized = reports.next().unwrap();
1074        assert_eq!(serialized.len(), EXPECT_SERIALIZED);
1075
1076        let mut buffer = &serialized[..];
1077        let _ip = buffer.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
1078        let igmp = buffer.parse::<IgmpMessage<_, IgmpMembershipReportV3>>().unwrap();
1079        assert_eq!(usize::from(igmp.header.number_of_group_records.get()), MAX_GROUPS);
1080        for (i, group) in igmp.body.iter().enumerate() {
1081            assert_eq!(group.header.multicast_address, group_ip(i).get());
1082            assert_eq!(group.header.number_of_sources.get(), 0);
1083        }
1084
1085        // Only one report is generated.
1086        assert_eq!(reports.next(), None);
1087
1088        let reports =
1089            IgmpMembershipReportV3Builder::new((0..MAX_GROUPS + 1).into_iter().map(|i| {
1090                (group_ip(i), IgmpGroupRecordType::ModeIsExclude, core::iter::empty::<Ipv4Addr>())
1091            }))
1092            .with_len_limits(ETH_MTU - ip_header)
1093            .unwrap();
1094        // 2 reports are generated with one extra source.
1095        assert_eq!(reports.map(|r| r.groups.count()).collect::<Vec<_>>(), vec![MAX_GROUPS, 1]);
1096    }
1097
1098    #[test]
1099    fn membership_report_v1_parse_and_serialize() {
1100        use igmp_reports::v1;
1101        set_logger_for_test();
1102        test_parse_and_serialize_inner::<IgmpMembershipReportV1, _>(v1::MEMBER_REPORT, |igmp| {
1103            assert_eq!(*igmp.header, Ipv4Addr::new(v1::GROUP_ADDRESS));
1104        });
1105    }
1106
1107    #[test]
1108    fn membership_report_v2_parse_and_serialize() {
1109        use igmp_reports::v2;
1110        set_logger_for_test();
1111        test_parse_and_serialize_inner::<IgmpMembershipReportV2, _>(v2::MEMBER_REPORT, |igmp| {
1112            assert_eq!(*igmp.header, Ipv4Addr::new(v2::GROUP_ADDRESS));
1113        });
1114    }
1115
1116    #[test]
1117    fn leave_group_parse_and_serialize() {
1118        set_logger_for_test();
1119        test_parse_and_serialize_inner::<IgmpLeaveGroup, _>(
1120            igmp_leave_group::LEAVE_GROUP,
1121            |igmp| {
1122                assert_eq!(*igmp.header, Ipv4Addr::new(igmp_leave_group::GROUP_ADDRESS));
1123            },
1124        );
1125    }
1126
1127    #[test]
1128    fn test_unknown_type() {
1129        let mut buff = igmp_invalid_buffers::UNKNOWN_TYPE.to_vec();
1130        let mut buff = buff.as_mut_slice();
1131        let packet = buff.parse_with::<_, IgmpPacket<_>>(());
1132        // we don't use expect_err here because IgmpPacket does not implement
1133        // core::fmt::Debug
1134        assert_eq!(packet.is_err(), true);
1135    }
1136
1137    #[test]
1138    fn test_full_parses() {
1139        let mut bufs = ALL_BUFFERS.to_vec();
1140        for buff in bufs.iter_mut() {
1141            let orig_req = &buff[..];
1142            let packet = buff.parse_with::<_, IgmpPacket<_>>(()).unwrap();
1143            let msg_type = match packet {
1144                IgmpPacket::MembershipQueryV2(p) => p.prefix.msg_type,
1145                IgmpPacket::MembershipQueryV3(p) => p.prefix.msg_type,
1146                IgmpPacket::MembershipReportV1(p) => p.prefix.msg_type,
1147                IgmpPacket::MembershipReportV2(p) => p.prefix.msg_type,
1148                IgmpPacket::MembershipReportV3(p) => p.prefix.msg_type,
1149                IgmpPacket::LeaveGroup(p) => p.prefix.msg_type,
1150            };
1151            assert_eq!(msg_type, orig_req[0]);
1152        }
1153    }
1154
1155    #[test]
1156    fn test_partial_parses() {
1157        // parsing a part of the buffer should always result in errors and
1158        // nothing panics.
1159        for buff in ALL_BUFFERS.iter() {
1160            for i in 0..buff.len() {
1161                let partial_buff = &mut &buff[0..i];
1162                let packet = partial_buff.parse_with::<_, IgmpPacket<_>>(());
1163                assert_eq!(packet.is_err(), true)
1164            }
1165        }
1166    }
1167
1168    // Asserts that a `Message` without `VariableBody` should have the same length
1169    // as the given ground truth packet.
1170    fn assert_message_length<Message: for<'a> MessageType<&'a [u8], VariableBody = ()>>(
1171        mut ground_truth: &[u8],
1172    ) {
1173        let ground_truth_len = ground_truth.len();
1174        let igmp = ground_truth.parse_with::<_, IgmpMessage<&[u8], Message>>(()).unwrap();
1175        let builder_len = igmp.builder().bytes_len();
1176        assert_eq!(builder_len, ground_truth_len);
1177    }
1178
1179    #[test]
1180    fn test_igmp_packet_length() {
1181        assert_message_length::<IgmpMembershipQueryV2>(igmp_router_queries::v2::QUERY);
1182        assert_message_length::<IgmpMembershipReportV1>(igmp_reports::v1::MEMBER_REPORT);
1183        assert_message_length::<IgmpMembershipReportV2>(igmp_reports::v2::MEMBER_REPORT);
1184        assert_message_length::<IgmpLeaveGroup>(igmp_leave_group::LEAVE_GROUP);
1185    }
1186
1187    #[test]
1188    fn test_igmp_too_few_records() {
1189        let src = net_ip_v4!("192.168.0.1");
1190        let group_addr = MulticastAddr::new(net_ip_v4!("224.0.0.1")).unwrap();
1191
1192        // Serialize 1 record.
1193        let builder = IgmpMembershipReportV3Builder::new(
1194            [(group_addr, IgmpGroupRecordType::ModeIsInclude, core::iter::once(src))].into_iter(),
1195        );
1196        let serialized = builder
1197            .into_serializer()
1198            .serialize_vec_outer(&mut NoOpSerializationContext)
1199            .unwrap()
1200            .unwrap_b()
1201            .into_inner();
1202
1203        // Skip IGMPv3 report header to get only the record bytes.
1204        let header_size = crate::igmp::total_header_size::<MembershipReportV3Data>();
1205        let record_bytes = &serialized[header_size..];
1206
1207        let mut ctx = 2; // Expect 2 records, but only 1 is present in record_bytes
1208        let res =
1209            Records::<_, IgmpMembershipReportV3>::parse_with_mut_context(record_bytes, &mut ctx);
1210        assert_eq!(res.unwrap_err(), ParseError::Format);
1211        assert_eq!(ctx, 1); // Parsed one record, so counter decremented to 1
1212    }
1213}