packet_formats/ipv6/
ext_hdrs.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//! Parsing and serialization of IPv6 extension headers.
6//!
7//! The IPv6 extension header format is defined in [RFC 8200 Section 4].
8//!
9//! [RFC 8200 Section 4]: https://datatracker.ietf.org/doc/html/rfc8200#section-4
10
11use core::convert::Infallible as Never;
12use core::marker::PhantomData;
13
14use byteorder::{ByteOrder, NetworkEndian};
15use packet::records::options::{
16    AlignedOptionBuilder, LengthEncoding, OptionBuilder, OptionLayout, OptionParseErr,
17    OptionParseLayout,
18};
19use packet::records::{
20    ParsedRecord, RecordParseResult, Records, RecordsContext, RecordsImpl, RecordsImplLayout,
21    RecordsRawImpl,
22};
23use packet::{BufferView, BufferViewMut};
24use zerocopy::byteorder::network_endian::U16;
25
26use crate::ip::{FragmentOffset, IpProto, Ipv6ExtHdrType, Ipv6Proto};
27
28/// The length of an IPv6 Fragment Extension Header.
29pub(crate) const IPV6_FRAGMENT_EXT_HDR_LEN: usize = 8;
30
31/// An IPv6 Extension Header.
32#[derive(Debug)]
33pub struct Ipv6ExtensionHeader<'a> {
34    // Marked as `pub(super)` because it is only used in tests within
35    // the `crate::ipv6` (`super`) module.
36    pub(super) next_header: u8,
37    data: Ipv6ExtensionHeaderData<'a>,
38}
39
40impl<'a> Ipv6ExtensionHeader<'a> {
41    /// Returns the extension header-specific data.
42    pub fn data(&self) -> &Ipv6ExtensionHeaderData<'a> {
43        &self.data
44    }
45
46    /// Consumes `self` returning only the containing data.
47    pub fn into_data(self) -> Ipv6ExtensionHeaderData<'a> {
48        self.data
49    }
50}
51
52/// The data associated with an IPv6 Extension Header.
53#[allow(missing_docs)]
54#[derive(Debug)]
55pub enum Ipv6ExtensionHeaderData<'a> {
56    HopByHopOptions { options: HopByHopOptionsData<'a> },
57    Fragment { fragment_data: FragmentData<'a> },
58    DestinationOptions { options: DestinationOptionsData<'a> },
59}
60
61//
62// Records parsing for IPv6 Extension Header
63//
64
65/// Possible errors that can happen when parsing IPv6 Extension Headers.
66#[allow(missing_docs)]
67#[derive(Debug, PartialEq, Eq)]
68pub(super) enum Ipv6ExtensionHeaderParsingError {
69    // `pointer` is the offset from the beginning of the first extension header
70    // to the point of error. `must_send_icmp` is a flag that requires us to send
71    // an ICMP response if true. `header_len` is the size of extension headers before
72    // encountering an error (number of bytes from successfully parsed
73    // extension headers).
74    ErroneousHeaderField {
75        pointer: u32,
76        must_send_icmp: bool,
77        header_len: usize,
78    },
79    UnrecognizedNextHeader {
80        pointer: u32,
81        must_send_icmp: bool,
82        header_len: usize,
83    },
84    UnrecognizedOption {
85        pointer: u32,
86        must_send_icmp: bool,
87        header_len: usize,
88        action: ExtensionHeaderOptionAction,
89    },
90    BufferExhausted,
91    MalformedData,
92}
93
94impl From<Never> for Ipv6ExtensionHeaderParsingError {
95    fn from(err: Never) -> Ipv6ExtensionHeaderParsingError {
96        match err {}
97    }
98}
99
100/// Context that gets passed around when parsing IPv6 Extension Headers.
101#[derive(Debug, Clone)]
102pub(super) struct Ipv6ExtensionHeaderParsingContext {
103    // Next expected header.
104    // Marked as `pub(super)` because it is inly used in tests within
105    // the `crate::ipv6` (`super`) module.
106    pub(super) next_header: u8,
107
108    // Whether context is being used for iteration or not.
109    iter: bool,
110
111    // Counter for number of extension headers parsed.
112    headers_parsed: usize,
113
114    // Byte count of successfully parsed extension headers.
115    pub(super) bytes_parsed: usize,
116}
117
118impl Ipv6ExtensionHeaderParsingContext {
119    /// Returns a new `Ipv6ExtensionHeaderParsingContext` which expects the
120    /// first header to have the ID specified by `next_header`.
121    pub(super) fn new(next_header: u8) -> Ipv6ExtensionHeaderParsingContext {
122        Ipv6ExtensionHeaderParsingContext {
123            iter: false,
124            headers_parsed: 0,
125            next_header,
126            bytes_parsed: 0,
127        }
128    }
129}
130
131impl RecordsContext for Ipv6ExtensionHeaderParsingContext {
132    type Counter = ();
133
134    fn clone_for_iter(&self) -> Self {
135        let mut ret = self.clone();
136        ret.iter = true;
137        ret
138    }
139
140    fn counter_mut(&mut self) -> &mut () {
141        get_empty_tuple_mut_ref()
142    }
143}
144
145/// Implement the actual parsing of IPv6 Extension Headers.
146#[derive(Debug)]
147pub(super) struct Ipv6ExtensionHeaderImpl;
148
149impl Ipv6ExtensionHeaderImpl {
150    /// Make sure a Next Header value in an extension header is valid.
151    fn valid_next_header(next_header: u8) -> bool {
152        // Passing false to `is_valid_next_header`'s `for_fixed_header` parameter because
153        // this function will never be called when checking the Next Header field
154        // of the fixed header (which would be the first Next Header).
155        is_valid_next_header(next_header, false)
156    }
157
158    /// Get the first two bytes if possible and return them.
159    ///
160    /// `get_next_hdr_and_len` takes the first two bytes from `data` and
161    /// treats them as the Next Header and Hdr Ext Len fields. With the
162    /// Next Header field, `get_next_hdr_and_len` makes sure it is a valid
163    /// value before returning the Next Header and Hdr Ext Len fields.
164    fn get_next_hdr_and_len<'a, BV: BufferView<&'a [u8]>>(
165        data: &mut BV,
166        context: &Ipv6ExtensionHeaderParsingContext,
167    ) -> Result<(u8, u8), Ipv6ExtensionHeaderParsingError> {
168        let next_header =
169            data.take_byte_front().ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
170
171        // Make sure we recognize the next header.
172        // When parsing headers, if we encounter a next header value we don't
173        // recognize, we SHOULD send back an ICMP response. Since we only SHOULD,
174        // we set `must_send_icmp` to `false`.
175        if !Self::valid_next_header(next_header) {
176            return Err(Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
177                pointer: context.bytes_parsed as u32,
178                must_send_icmp: false,
179                header_len: context.bytes_parsed,
180            });
181        }
182
183        let hdr_ext_len =
184            data.take_byte_front().ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
185
186        Ok((next_header, hdr_ext_len))
187    }
188
189    /// Parse Hop By Hop Options Extension Header.
190    // TODO(ghanan): Look into implementing the IPv6 Jumbo Payload option
191    //               (https://tools.ietf.org/html/rfc2675) and the router
192    //               alert option (https://tools.ietf.org/html/rfc2711).
193    fn parse_hop_by_hop_options<'a, BV: BufferView<&'a [u8]>>(
194        data: &mut BV,
195        context: &mut Ipv6ExtensionHeaderParsingContext,
196    ) -> Result<ParsedRecord<Ipv6ExtensionHeader<'a>>, Ipv6ExtensionHeaderParsingError> {
197        let (next_header, hdr_ext_len) = Self::get_next_hdr_and_len(data, context)?;
198
199        // As per RFC 8200 section 4.3, Hdr Ext Len is the length of this extension
200        // header in  8-octect units, not including the first 8 octets (where 2 of
201        // them are the Next Header and the Hdr Ext Len fields). Since we already
202        // 'took' the Next Header and Hdr Ext Len octets, we need to make sure
203        // we have (Hdr Ext Len) * 8 + 6 bytes bytes in `data`.
204        let expected_len = (hdr_ext_len as usize) * 8 + 6;
205
206        let options = data
207            .take_front(expected_len)
208            .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
209
210        let options_context = ExtensionHeaderOptionContext::new();
211        let options = Records::parse_with_context(options, options_context).map_err(|e| {
212            // We know the below `try_from` call will not result in a `None` value because
213            // the maximum size of an IPv6 packet's payload (extension headers + body) is
214            // `core::u32::MAX`. This maximum size is only possible when using IPv6
215            // jumbograms as defined by RFC 2675, which uses a 32 bit field for the payload
216            // length. If we receive such a hypothetical packet with the maximum possible
217            // payload length which only contains extension headers, we know that the offset
218            // of any location within the payload must fit within an `u32`. If the packet is
219            // a normal IPv6 packet (not a jumbogram), the maximum size of the payload is
220            // `core::u16::MAX` (as the normal payload length field is only 16 bits), which
221            // is significantly less than the maximum possible size of a jumbogram.
222            ext_hdr_opt_err_to_ext_hdr_err(
223                u32::try_from(context.bytes_parsed + 2).unwrap(),
224                context.bytes_parsed,
225                e,
226            )
227        })?;
228        let options = HopByHopOptionsData::new(options);
229
230        // Update context
231        context.next_header = next_header;
232        context.headers_parsed += 1;
233        context.bytes_parsed += 2 + expected_len;
234
235        Ok(ParsedRecord::Parsed(Ipv6ExtensionHeader {
236            next_header,
237            data: Ipv6ExtensionHeaderData::HopByHopOptions { options },
238        }))
239    }
240
241    /// Parse Routing Extension Header.
242    fn parse_routing<'a, BV: BufferView<&'a [u8]>>(
243        data: &mut BV,
244        context: &mut Ipv6ExtensionHeaderParsingContext,
245    ) -> Result<ParsedRecord<Ipv6ExtensionHeader<'a>>, Ipv6ExtensionHeaderParsingError> {
246        // All routing extension headers (regardless of type) will have
247        // 4 bytes worth of data we need to look at.
248        let (next_header, hdr_ext_len) = Self::get_next_hdr_and_len(data, context)?;
249        let routing_data =
250            data.take_front(2).ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
251        let segments_left = routing_data[1];
252
253        // Currently we do not support any routing type.
254        //
255        // Note, this includes routing type 0 which is defined in RFC 2460 as it has been
256        // deprecated as of RFC 5095 for security reasons.
257
258        // If we receive a routing header with an unrecognized routing type,
259        // what we do depends on the segments left. If segments left is
260        // 0, we must ignore the routing header and continue processing
261        // other headers. If segments left is not 0, we need to discard
262        // this packet and send an ICMP Parameter Problem, Code 0 with a
263        // pointer to this unrecognized routing type.
264        if segments_left == 0 {
265            // Take the next 4 and 8 * `hdr_ext_len` bytes to exhaust this extension header's
266            // data so that that `data` will be at the front of the next header when this
267            // function returns.
268            let expected_len = (hdr_ext_len as usize) * 8 + 4;
269            let _: &[u8] = data
270                .take_front(expected_len)
271                .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
272
273            // Update context
274            context.next_header = next_header;
275            context.headers_parsed += 1;
276            context.bytes_parsed += expected_len;
277
278            Ok(ParsedRecord::Skipped)
279        } else {
280            // As per RFC 8200, if we encounter a routing header with an unrecognized
281            // routing type, and segments left is non-zero, we MUST discard the packet
282            // and send and ICMP Parameter Problem response.
283            Err(Ipv6ExtensionHeaderParsingError::ErroneousHeaderField {
284                pointer: (context.bytes_parsed as u32) + 2,
285                must_send_icmp: true,
286                header_len: context.bytes_parsed,
287            })
288        }
289    }
290
291    /// Parse Fragment Extension Header.
292    fn parse_fragment<'a, BV: BufferView<&'a [u8]>>(
293        data: &mut BV,
294        context: &mut Ipv6ExtensionHeaderParsingContext,
295    ) -> Result<ParsedRecord<Ipv6ExtensionHeader<'a>>, Ipv6ExtensionHeaderParsingError> {
296        // Fragment Extension Header requires exactly 8 bytes so make sure
297        // `data` has at least 8 bytes left. If `data` has at least 8 bytes left,
298        // we are guaranteed that all `take_front` calls done by this
299        // method will succeed since we will never attempt to call `take_front`
300        // with more than 8 bytes total.
301        if data.len() < 8 {
302            return Err(Ipv6ExtensionHeaderParsingError::BufferExhausted);
303        }
304
305        // For Fragment headers, we do not actually have a HdrExtLen field. Instead,
306        // the second byte in the header (where HdrExtLen would normally exist), is
307        // a reserved field, so we can simply ignore it for now.
308        let (next_header, _) = Self::get_next_hdr_and_len(data, context)?;
309
310        // Update context
311        context.next_header = next_header;
312        context.headers_parsed += 1;
313        context.bytes_parsed += 8;
314
315        Ok(ParsedRecord::Parsed(Ipv6ExtensionHeader {
316            next_header,
317            data: Ipv6ExtensionHeaderData::Fragment {
318                fragment_data: FragmentData { bytes: data.take_front(6).unwrap() },
319            },
320        }))
321    }
322
323    /// Parse Destination Options Extension Header.
324    fn parse_destination_options<'a, BV: BufferView<&'a [u8]>>(
325        data: &mut BV,
326        context: &mut Ipv6ExtensionHeaderParsingContext,
327    ) -> Result<ParsedRecord<Ipv6ExtensionHeader<'a>>, Ipv6ExtensionHeaderParsingError> {
328        let (next_header, hdr_ext_len) = Self::get_next_hdr_and_len(data, context)?;
329
330        // As per RFC 8200 section 4.6, Hdr Ext Len is the length of this extension
331        // header in  8-octet units, not including the first 8 octets (where 2 of
332        // them are the Next Header and the Hdr Ext Len fields).
333        let expected_len = (hdr_ext_len as usize) * 8 + 6;
334
335        let options = data
336            .take_front(expected_len)
337            .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
338
339        let options_context = ExtensionHeaderOptionContext::new();
340        let options = Records::parse_with_context(options, options_context).map_err(|e| {
341            // We know the below `try_from` call will not result in a `None` value because
342            // the maximum size of an IPv6 packet's payload (extension headers + body) is
343            // `core::u32::MAX`. This maximum size is only possible when using IPv6
344            // jumbograms as defined by RFC 2675, which uses a 32 bit field for the payload
345            // length. If we receive such a hypothetical packet with the maximum possible
346            // payload length which only contains extension headers, we know that the offset
347            // of any location within the payload must fit within an `u32`. If the packet is
348            // a normal IPv6 packet (not a jumbogram), the maximum size of the payload is
349            // `core::u16::MAX` (as the normal payload length field is only 16 bits), which
350            // is significantly less than the maximum possible size of a jumbogram.
351            ext_hdr_opt_err_to_ext_hdr_err(
352                u32::try_from(context.bytes_parsed + 2).unwrap(),
353                context.bytes_parsed,
354                e,
355            )
356        })?;
357        let options = DestinationOptionsData::new(options);
358
359        // Update context
360        context.next_header = next_header;
361        context.headers_parsed += 1;
362        context.bytes_parsed += 2 + expected_len;
363
364        Ok(ParsedRecord::Parsed(Ipv6ExtensionHeader {
365            next_header,
366            data: Ipv6ExtensionHeaderData::DestinationOptions { options },
367        }))
368    }
369}
370
371impl RecordsImplLayout for Ipv6ExtensionHeaderImpl {
372    type Context = Ipv6ExtensionHeaderParsingContext;
373    type Error = Ipv6ExtensionHeaderParsingError;
374}
375
376impl RecordsImpl for Ipv6ExtensionHeaderImpl {
377    type Record<'a> = Ipv6ExtensionHeader<'a>;
378
379    fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
380        data: &mut BV,
381        context: &mut Self::Context,
382    ) -> RecordParseResult<Self::Record<'a>, Self::Error> {
383        let expected_hdr = context.next_header;
384
385        match Ipv6ExtHdrType::from(expected_hdr) {
386            Ipv6ExtHdrType::HopByHopOptions => Self::parse_hop_by_hop_options(data, context),
387            Ipv6ExtHdrType::Routing => Self::parse_routing(data, context),
388            Ipv6ExtHdrType::Fragment => Self::parse_fragment(data, context),
389            Ipv6ExtHdrType::DestinationOptions => Self::parse_destination_options(data, context),
390            Ipv6ExtHdrType::EncapsulatingSecurityPayload | Ipv6ExtHdrType::Authentication => {
391                // We don't implement these extension header types.
392                //
393                // Per RFC 2460:
394                //   If, as a result of processing a header, a node is required to
395                //   proceed to the next header but the Next Header value in the
396                //   current header is unrecognized by the node, it should discard
397                //   the packet and send an ICMP Parameter Problem message to the
398                //   source of the packet, with an ICMP Code value of 1
399                //   ("unrecognized Next Header type encountered") and the ICMP
400                //   Pointer field containing the offset of the unrecognized value
401                //   within the original packet.
402                Err(Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
403                    // TODO(https://fxbug.dev/42158223): When overhauling packet
404                    // validation, return the right values for `pointer` and
405                    // `header_len`.
406                    pointer: u32::MAX,
407                    header_len: 0,
408                    // This is false because of the "should" in the quoted RFC
409                    // text.
410                    must_send_icmp: false,
411                })
412            }
413            Ipv6ExtHdrType::Other(_) => {
414                if is_valid_next_header_upper_layer(expected_hdr) {
415                    // Stop parsing extension headers when we find a Next Header value
416                    // for a higher level protocol.
417                    Ok(ParsedRecord::Done)
418                } else {
419                    // Should never end up here because we guarantee that if we hit an
420                    // invalid Next Header field while parsing extension headers, we will
421                    // return an error when we see it right away. Since the only other time
422                    // `context.next_header` can get an invalid value assigned is when we parse
423                    // the fixed IPv6 header, but we check if the next header is valid before
424                    // parsing extension headers.
425
426                    unreachable!(
427                        "Should never try parsing an extension header with an unrecognized type"
428                    );
429                }
430            }
431        }
432    }
433}
434
435impl<'a> RecordsRawImpl<'a> for Ipv6ExtensionHeaderImpl {
436    fn parse_raw_with_context<BV: BufferView<&'a [u8]>>(
437        data: &mut BV,
438        context: &mut Self::Context,
439    ) -> Result<bool, Self::Error> {
440        if is_valid_next_header_upper_layer(context.next_header) {
441            Ok(false)
442        } else {
443            let (next, skip) = match Ipv6ExtHdrType::from(context.next_header) {
444                Ipv6ExtHdrType::HopByHopOptions
445                | Ipv6ExtHdrType::Routing
446                | Ipv6ExtHdrType::DestinationOptions
447                | Ipv6ExtHdrType::Other(_) => {
448                    // take next header and header len, and skip the next 6
449                    // octets + the number of 64 bit words in header len.
450                    // NOTE: we can assume that Other will be parsed
451                    //  as such based on the extensibility note in
452                    //  RFC 8200 Section-4.8
453                    data.take_front(2)
454                        .map(|x| (x[0], (x[1] as usize) * 8 + 6))
455                        .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?
456                }
457                Ipv6ExtHdrType::Fragment => {
458                    // take next header from first, then skip next 7
459                    (
460                        data.take_byte_front()
461                            .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?,
462                        7,
463                    )
464                }
465                Ipv6ExtHdrType::EncapsulatingSecurityPayload => {
466                    // TODO(brunodalbo): We don't support ESP yet, so return
467                    //  an error instead of panicking "unimplemented" to avoid
468                    //  having a panic-path that can be remotely triggered.
469                    return debug_err!(
470                        Err(Ipv6ExtensionHeaderParsingError::MalformedData),
471                        "ESP extension header not supported"
472                    );
473                }
474                Ipv6ExtHdrType::Authentication => {
475                    // take next header and payload len, and skip the next
476                    // (payload_len + 2) 32 bit words, minus the 2 octets
477                    // already consumed.
478                    data.take_front(2)
479                        .map(|x| (x[0], (x[1] as usize + 2) * 4 - 2))
480                        .ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?
481                }
482            };
483            let _: &[u8] =
484                data.take_front(skip).ok_or(Ipv6ExtensionHeaderParsingError::BufferExhausted)?;
485            context.next_header = next;
486            Ok(true)
487        }
488    }
489}
490
491//
492// Hop-By-Hop Options
493//
494
495/// Hop By Hop Options extension header data.
496#[derive(Debug)]
497pub struct HopByHopOptionsData<'a> {
498    options: Records<&'a [u8], HopByHopOptionsImpl>,
499}
500
501impl<'a> HopByHopOptionsData<'a> {
502    /// Returns a new `HopByHopOptionsData` with `options`.
503    fn new(options: Records<&'a [u8], HopByHopOptionsImpl>) -> HopByHopOptionsData<'a> {
504        HopByHopOptionsData { options }
505    }
506
507    /// Returns an iterator over the [`HopByHopOptions`] in this
508    /// `HopByHopOptionsData`.
509    pub fn iter(&'a self) -> impl Iterator<Item = HopByHopOption<'a>> {
510        self.options.iter()
511    }
512}
513
514/// An option found in a Hop By Hop Options extension header.
515pub type HopByHopOption<'a> = ExtensionHeaderOption<HopByHopOptionData<'a>>;
516
517/// An implementation of [`OptionsImpl`] for options found in a Hop By Hop Options
518/// extension header.
519pub(super) type HopByHopOptionsImpl = ExtensionHeaderOptionImpl<HopByHopOptionDataImpl>;
520
521/// Hop-By-Hop Option Type number as per [RFC 2711 section-2.1]
522///
523/// [RFC 2711 section-2.1]: https://tools.ietf.org/html/rfc2711#section-2.1
524const HBH_OPTION_KIND_RTRALRT: u8 = 5;
525
526/// Length for RouterAlert as per [RFC 2711 section-2.1]
527///
528/// [RFC 2711 section-2.1]: https://tools.ietf.org/html/rfc2711#section-2.1
529const HBH_OPTION_RTRALRT_LEN: usize = 2;
530
531/// HopByHop Options Extension header data.
532#[allow(missing_docs)]
533#[derive(Debug, PartialEq, Eq, Clone)]
534pub enum HopByHopOptionData<'a> {
535    Unrecognized { kind: u8, len: u8, data: &'a [u8] },
536    RouterAlert { data: u16 },
537}
538
539/// Impl for Hop By Hop Options parsing.
540#[derive(Debug)]
541pub(super) struct HopByHopOptionDataImpl;
542
543impl ExtensionHeaderOptionDataImplLayout for HopByHopOptionDataImpl {
544    type Context = ();
545}
546
547impl ExtensionHeaderOptionDataImpl for HopByHopOptionDataImpl {
548    type OptionData<'a> = HopByHopOptionData<'a>;
549
550    fn parse_option<'a>(
551        kind: u8,
552        data: &'a [u8],
553        _context: &mut Self::Context,
554        allow_unrecognized: bool,
555    ) -> ExtensionHeaderOptionDataParseResult<Self::OptionData<'a>> {
556        match kind {
557            HBH_OPTION_KIND_RTRALRT => {
558                if data.len() == HBH_OPTION_RTRALRT_LEN {
559                    ExtensionHeaderOptionDataParseResult::Ok(HopByHopOptionData::RouterAlert {
560                        data: NetworkEndian::read_u16(data),
561                    })
562                } else {
563                    // Since the length is wrong, and the length is indicated at the second byte within
564                    // the option itself. We count from 0 of course.
565                    ExtensionHeaderOptionDataParseResult::ErrorAt(1)
566                }
567            }
568            _ => {
569                if allow_unrecognized {
570                    ExtensionHeaderOptionDataParseResult::Ok(HopByHopOptionData::Unrecognized {
571                        kind,
572                        len: data.len() as u8,
573                        data,
574                    })
575                } else {
576                    ExtensionHeaderOptionDataParseResult::UnrecognizedKind
577                }
578            }
579        }
580    }
581}
582
583impl OptionLayout for HopByHopOptionsImpl {
584    type KindLenField = u8;
585    const LENGTH_ENCODING: LengthEncoding = LengthEncoding::ValueOnly;
586}
587
588impl OptionParseLayout for HopByHopOptionsImpl {
589    type Error = OptionParseErr;
590    const END_OF_OPTIONS: Option<u8> = Some(0);
591    const NOP: Option<u8> = Some(1);
592}
593
594/// Provides an implementation of `OptionLayout` for Hop-by-Hop options.
595///
596/// Use this instead of `HopByHopOptionsImpl` for `<HopByHopOption as
597/// OptionBuilder>::Layout` in order to avoid having to make a ton of other
598/// things `pub` which are reachable from `HopByHopOptionsImpl`.
599#[doc(hidden)]
600pub enum HopByHopOptionLayout {}
601
602impl OptionLayout for HopByHopOptionLayout {
603    type KindLenField = u8;
604    const LENGTH_ENCODING: LengthEncoding = LengthEncoding::ValueOnly;
605}
606
607impl<'a> OptionBuilder for HopByHopOption<'a> {
608    type Layout = HopByHopOptionLayout;
609    fn serialized_len(&self) -> usize {
610        match self.data {
611            HopByHopOptionData::RouterAlert { .. } => HBH_OPTION_RTRALRT_LEN,
612            HopByHopOptionData::Unrecognized { len, .. } => len as usize,
613        }
614    }
615
616    fn option_kind(&self) -> u8 {
617        let action: u8 = self.action.into();
618        let mutable = self.mutable as u8;
619        let type_number = match self.data {
620            HopByHopOptionData::Unrecognized { kind, .. } => kind,
621            HopByHopOptionData::RouterAlert { .. } => HBH_OPTION_KIND_RTRALRT,
622        };
623        (action << 6) | (mutable << 5) | type_number
624    }
625
626    fn serialize_into(&self, mut buffer: &mut [u8]) {
627        match self.data {
628            HopByHopOptionData::Unrecognized { data, .. } => buffer.copy_from_slice(data),
629            HopByHopOptionData::RouterAlert { data } => {
630                // If the buffer doesn't contain enough space, it is a
631                // contract violation, panic here.
632                (&mut buffer).write_obj_front(&U16::new(data)).unwrap()
633            }
634        }
635    }
636}
637
638impl<'a> AlignedOptionBuilder for HopByHopOption<'a> {
639    fn alignment_requirement(&self) -> (usize, usize) {
640        match self.data {
641            // RouterAlert must be aligned at 2 * n + 0 bytes.
642            // See: https://tools.ietf.org/html/rfc2711#section-2.1
643            HopByHopOptionData::RouterAlert { .. } => (2, 0),
644            _ => (1, 0),
645        }
646    }
647
648    fn serialize_padding(buf: &mut [u8], length: usize) {
649        assert!(length <= buf.len());
650        assert!(length <= (core::u8::MAX as usize) + 2);
651
652        #[allow(clippy::comparison_chain)]
653        if length == 1 {
654            // Use Pad1
655            buf[0] = 0
656        } else if length > 1 {
657            // Use PadN
658            buf[0] = 1;
659            buf[1] = (length - 2) as u8;
660            #[allow(clippy::needless_range_loop)]
661            for i in 2..length {
662                buf[i] = 0
663            }
664        }
665    }
666}
667
668//
669// Fragment
670//
671
672/// Fragment Extension header data.
673///
674/// As per RFC 8200, section 4.5 the fragment header is structured as:
675/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
676/// |  Next Header  |   Reserved    |      Fragment Offset    |Res|M|
677/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
678/// |                         Identification                        |
679/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
680///
681/// where Fragment Offset is 13 bits, Res is a reserved 2 bits and M
682/// is a 1 bit flag. Identification is a 32bit value.
683#[derive(Debug)]
684pub struct FragmentData<'a> {
685    bytes: &'a [u8],
686}
687
688impl<'a> FragmentData<'a> {
689    /// Returns the fragment offset.
690    pub fn fragment_offset(&self) -> FragmentOffset {
691        debug_assert!(self.bytes.len() == 6);
692        FragmentOffset::new_with_msb(U16::from_bytes([self.bytes[0], self.bytes[1]]).get())
693    }
694
695    /// Returns the more fragments flags.
696    pub fn m_flag(&self) -> bool {
697        debug_assert!(self.bytes.len() == 6);
698        (self.bytes[1] & 0x1) == 0x01
699    }
700
701    /// Returns the identification value.
702    pub fn identification(&self) -> u32 {
703        debug_assert!(self.bytes.len() == 6);
704        NetworkEndian::read_u32(&self.bytes[2..6])
705    }
706}
707
708//
709// Destination Options
710//
711
712/// Destination Options extension header data.
713#[derive(Debug)]
714pub struct DestinationOptionsData<'a> {
715    options: Records<&'a [u8], DestinationOptionsImpl>,
716}
717
718impl<'a> DestinationOptionsData<'a> {
719    /// Returns a new `DestinationOptionsData` with `options`.
720    fn new(options: Records<&'a [u8], DestinationOptionsImpl>) -> DestinationOptionsData<'a> {
721        DestinationOptionsData { options }
722    }
723
724    /// Returns an iterator over the [`DestinationOptions`] in this
725    /// `DestinationOptionsData`.
726    pub fn iter(&'a self) -> impl Iterator<Item = DestinationOption<'a>> {
727        self.options.iter()
728    }
729}
730
731/// An option found in a Destination Options extension header.
732pub type DestinationOption<'a> = ExtensionHeaderOption<DestinationOptionData<'a>>;
733
734/// An implementation of [`OptionsImpl`] for options found in a Destination Options
735/// extension header.
736pub(super) type DestinationOptionsImpl = ExtensionHeaderOptionImpl<DestinationOptionDataImpl>;
737
738/// Destination Options extension header data.
739#[allow(missing_docs)]
740#[derive(Debug)]
741pub enum DestinationOptionData<'a> {
742    Unrecognized { kind: u8, len: u8, data: &'a [u8] },
743}
744
745/// Impl for Destination Options parsing.
746#[derive(Debug)]
747pub(super) struct DestinationOptionDataImpl;
748
749impl ExtensionHeaderOptionDataImplLayout for DestinationOptionDataImpl {
750    type Context = ();
751}
752
753impl ExtensionHeaderOptionDataImpl for DestinationOptionDataImpl {
754    type OptionData<'a> = DestinationOptionData<'a>;
755
756    fn parse_option<'a>(
757        kind: u8,
758        data: &'a [u8],
759        _context: &mut Self::Context,
760        allow_unrecognized: bool,
761    ) -> ExtensionHeaderOptionDataParseResult<Self::OptionData<'a>> {
762        if allow_unrecognized {
763            ExtensionHeaderOptionDataParseResult::Ok(DestinationOptionData::Unrecognized {
764                kind,
765                len: data.len() as u8,
766                data,
767            })
768        } else {
769            ExtensionHeaderOptionDataParseResult::UnrecognizedKind
770        }
771    }
772}
773
774//
775// Generic Extension Header who's data are options.
776//
777
778/// Context that gets passed around when parsing IPv6 Extension Header options.
779#[derive(Debug, Clone)]
780pub(super) struct ExtensionHeaderOptionContext<C: Sized + Clone> {
781    // Counter for number of options parsed.
782    options_parsed: usize,
783
784    // Byte count of successfully parsed options.
785    bytes_parsed: usize,
786
787    // Extension header specific context data.
788    specific_context: C,
789}
790
791impl<C: Sized + Clone + Default> ExtensionHeaderOptionContext<C> {
792    fn new() -> Self {
793        ExtensionHeaderOptionContext {
794            options_parsed: 0,
795            bytes_parsed: 0,
796            specific_context: C::default(),
797        }
798    }
799}
800
801impl<C: Sized + Clone> RecordsContext for ExtensionHeaderOptionContext<C> {
802    type Counter = ();
803
804    fn counter_mut(&mut self) -> &mut () {
805        get_empty_tuple_mut_ref()
806    }
807}
808
809/// Basic associated types required by `ExtensionHeaderOptionDataImpl`.
810pub(super) trait ExtensionHeaderOptionDataImplLayout {
811    /// A context type that can be used to maintain state while parsing multiple
812    /// records.
813    type Context: RecordsContext;
814}
815
816/// The result of parsing an extension header option data.
817#[derive(PartialEq, Eq, Debug)]
818pub enum ExtensionHeaderOptionDataParseResult<D> {
819    /// Successfully parsed data.
820    Ok(D),
821
822    /// An error occurred at the indicated offset within the option.
823    ///
824    /// For example, if the data length goes wrong, you should probably
825    /// make the offset to be 1 because in most (almost all) cases, the
826    /// length is at the second byte of the option.
827    ErrorAt(u32),
828
829    /// The option kind is not recognized.
830    UnrecognizedKind,
831}
832
833/// An implementation of an extension header specific option data parser.
834pub(super) trait ExtensionHeaderOptionDataImpl: ExtensionHeaderOptionDataImplLayout {
835    /// Extension header specific option data.
836    ///
837    /// Note, `OptionData` does not need to hold general option data as defined by
838    /// RFC 8200 section 4.2. It should only hold extension header specific option
839    /// data.
840    type OptionData<'a>: Sized;
841
842    /// Parse an option of a given `kind` from `data`.
843    ///
844    /// When `kind` is recognized returns `Ok(o)` where `o` is a successfully parsed
845    /// option. When `kind` is not recognized, returns `UnrecognizedKind` if `allow_unrecognized`
846    /// is `false`. If `kind` is not recognized but `allow_unrecognized` is `true`,
847    /// returns an `Ok(o)` where `o` holds option data without actually parsing it
848    /// (i.e. an unrecognized type that simply keeps track of the `kind` and `data`
849    /// that was passed to `parse_option`). A recognized option `kind` with incorrect
850    /// `data` must return `ErrorAt(offset)`, where the offset indicates where the
851    /// erroneous field is within the option data buffer.
852    fn parse_option<'a>(
853        kind: u8,
854        data: &'a [u8],
855        context: &mut Self::Context,
856        allow_unrecognized: bool,
857    ) -> ExtensionHeaderOptionDataParseResult<Self::OptionData<'a>>;
858}
859
860/// Generic implementation of extension header options parsing.
861///
862/// `ExtensionHeaderOptionImpl` handles the common implementation details
863/// of extension header options and lets `O` (which implements
864/// `ExtensionHeaderOptionDataImpl`) handle the extension header specific
865/// option parsing.
866#[derive(Debug)]
867pub(super) struct ExtensionHeaderOptionImpl<O>(PhantomData<O>);
868
869impl<O> ExtensionHeaderOptionImpl<O> {
870    const PAD1: u8 = 0;
871    const PADN: u8 = 1;
872}
873
874impl<O> RecordsImplLayout for ExtensionHeaderOptionImpl<O>
875where
876    O: ExtensionHeaderOptionDataImplLayout,
877{
878    type Error = ExtensionHeaderOptionParsingError;
879    type Context = ExtensionHeaderOptionContext<O::Context>;
880}
881
882impl<O> RecordsImpl for ExtensionHeaderOptionImpl<O>
883where
884    O: ExtensionHeaderOptionDataImpl,
885{
886    type Record<'a> = ExtensionHeaderOption<O::OptionData<'a>>;
887
888    fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
889        data: &mut BV,
890        context: &mut Self::Context,
891    ) -> RecordParseResult<Self::Record<'a>, Self::Error> {
892        // If we have no more bytes left, we are done.
893        let kind = match data.take_byte_front() {
894            None => return Ok(ParsedRecord::Done),
895            Some(k) => k,
896        };
897
898        // Will never get an error because we only use the 2 least significant bits which
899        // can only have a max value of 3 and all values in [0, 3] are valid values of
900        // `ExtensionHeaderOptionAction`.
901        let action =
902            ExtensionHeaderOptionAction::try_from((kind >> 6) & 0x3).expect("Unexpected error");
903        let mutable = ((kind >> 5) & 0x1) == 0x1;
904        let kind = kind & 0x1F;
905
906        // If our kind is a PAD1, consider it a NOP.
907        if kind == Self::PAD1 {
908            // Update context.
909            context.options_parsed += 1;
910            context.bytes_parsed += 1;
911
912            return Ok(ParsedRecord::Skipped);
913        }
914
915        let len =
916            data.take_byte_front().ok_or(ExtensionHeaderOptionParsingError::BufferExhausted)?;
917
918        let data = data
919            .take_front(len as usize)
920            .ok_or(ExtensionHeaderOptionParsingError::BufferExhausted)?;
921
922        // If our kind is a PADN, consider it a NOP as well.
923        if kind == Self::PADN {
924            // Update context.
925            context.options_parsed += 1;
926            context.bytes_parsed += 2 + (len as usize);
927
928            return Ok(ParsedRecord::Skipped);
929        }
930
931        // Parse the actual option data.
932        match O::parse_option(
933            kind,
934            data,
935            &mut context.specific_context,
936            action == ExtensionHeaderOptionAction::SkipAndContinue,
937        ) {
938            ExtensionHeaderOptionDataParseResult::Ok(o) => {
939                // Update context.
940                context.options_parsed += 1;
941                context.bytes_parsed += 2 + (len as usize);
942
943                Ok(ParsedRecord::Parsed(ExtensionHeaderOption { action, mutable, data: o }))
944            }
945            ExtensionHeaderOptionDataParseResult::ErrorAt(offset) => {
946                // The precondition here is that `bytes_parsed + offset` must point inside the
947                // packet. So as reasoned in the next match arm, it is not possible to exceed
948                // `core::u32::max`. Given this reasoning, we know the call to `unwrap` should not
949                // panic.
950                Err(ExtensionHeaderOptionParsingError::ErroneousOptionField {
951                    pointer: u32::try_from(context.bytes_parsed + offset as usize).unwrap(),
952                })
953            }
954            ExtensionHeaderOptionDataParseResult::UnrecognizedKind => {
955                // Unrecognized option type.
956                match action {
957                    // `O::parse_option` should never return
958                    // `ExtensionHeaderOptionDataParseResult::UnrecognizedKind` when the
959                    // action is `ExtensionHeaderOptionAction::SkipAndContinue` because
960                    // we expect `O::parse_option` to return something that holds the
961                    // option data without actually parsing it since we pass `true` for its
962                    // `allow_unrecognized` parameter.
963                    ExtensionHeaderOptionAction::SkipAndContinue => unreachable!(
964                        "Should never end up here since action was set to skip and continue"
965                    ),
966                    // We know the below `try_from` call will not result in a `None` value because
967                    // the maximum size of an IPv6 packet's payload (extension headers + body) is
968                    // `core::u32::MAX`. This maximum size is only possible when using IPv6
969                    // jumbograms as defined by RFC 2675, which uses a 32 bit field for the payload
970                    // length. If we receive such a hypothetical packet with the maximum possible
971                    // payload length which only contains extension headers, we know that the offset
972                    // of any location within the payload must fit within an `u32`. If the packet is
973                    // a normal IPv6 packet (not a jumbogram), the maximum size of the payload is
974                    // `core::u16::MAX` (as the normal payload length field is only 16 bits), which
975                    // is significantly less than the maximum possible size of a jumbogram.
976                    _ => Err(ExtensionHeaderOptionParsingError::UnrecognizedOption {
977                        pointer: u32::try_from(context.bytes_parsed).unwrap(),
978                        action,
979                    }),
980                }
981            }
982        }
983    }
984}
985
986/// Possible errors when parsing extension header options.
987#[allow(missing_docs)]
988#[derive(Debug, PartialEq, Eq)]
989pub(crate) enum ExtensionHeaderOptionParsingError {
990    ErroneousOptionField { pointer: u32 },
991    UnrecognizedOption { pointer: u32, action: ExtensionHeaderOptionAction },
992    BufferExhausted,
993}
994
995impl From<Never> for ExtensionHeaderOptionParsingError {
996    fn from(err: Never) -> ExtensionHeaderOptionParsingError {
997        match err {}
998    }
999}
1000
1001/// Action to take when an unrecognized option type is encountered.
1002///
1003/// `ExtensionHeaderOptionAction` is an action that MUST be taken (according
1004/// to RFC 8200 section 4.2) when an IPv6 processing node does not
1005/// recognize an option's type.
1006#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1007pub enum ExtensionHeaderOptionAction {
1008    /// Skip over the option and continue processing the header.
1009    /// value = 0.
1010    SkipAndContinue,
1011
1012    /// Just discard the packet.
1013    /// value = 1.
1014    DiscardPacket,
1015
1016    /// Discard the packet and, regardless of whether or not the packet's
1017    /// destination address was a multicast address, send an ICMP parameter
1018    /// problem, code 2 (unrecognized option), message to the packet's source
1019    /// address, pointing to the unrecognized type.
1020    /// value = 2.
1021    DiscardPacketSendIcmp,
1022
1023    /// Discard the packet and, and only if the packet's destination address
1024    /// was not a multicast address, send an ICMP parameter problem, code 2
1025    /// (unrecognized option), message to the packet's source address, pointing
1026    /// to the unrecognized type.
1027    /// value = 3.
1028    DiscardPacketSendIcmpNoMulticast,
1029}
1030
1031impl TryFrom<u8> for ExtensionHeaderOptionAction {
1032    type Error = ();
1033
1034    fn try_from(value: u8) -> Result<Self, ()> {
1035        match value {
1036            0 => Ok(ExtensionHeaderOptionAction::SkipAndContinue),
1037            1 => Ok(ExtensionHeaderOptionAction::DiscardPacket),
1038            2 => Ok(ExtensionHeaderOptionAction::DiscardPacketSendIcmp),
1039            3 => Ok(ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast),
1040            _ => Err(()),
1041        }
1042    }
1043}
1044
1045impl From<ExtensionHeaderOptionAction> for u8 {
1046    fn from(a: ExtensionHeaderOptionAction) -> u8 {
1047        match a {
1048            ExtensionHeaderOptionAction::SkipAndContinue => 0,
1049            ExtensionHeaderOptionAction::DiscardPacket => 1,
1050            ExtensionHeaderOptionAction::DiscardPacketSendIcmp => 2,
1051            ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast => 3,
1052        }
1053    }
1054}
1055
1056/// Extension header option.
1057///
1058/// Generic Extension header option type that has extension header specific
1059/// option data (`data`) defined by an `O`. The common option format is defined in
1060/// section 4.2 of RFC 8200, outlining actions and mutability for option types.
1061#[derive(PartialEq, Eq, Debug, Clone)]
1062pub struct ExtensionHeaderOption<O> {
1063    /// Action to take if the option type is unrecognized.
1064    pub action: ExtensionHeaderOptionAction,
1065
1066    /// Whether or not the option data of the option can change en route to the
1067    /// packet's final destination. When an Authentication header is present in
1068    /// the packet, the option data must be treated as 0s when computing or
1069    /// verifying the packet's authenticating value when the option data can change
1070    /// en route.
1071    pub mutable: bool,
1072
1073    /// Option data associated with a specific extension header.
1074    pub data: O,
1075}
1076
1077//
1078// Helper functions
1079//
1080
1081/// Make sure a Next Header is valid.
1082///
1083/// Check if the provided `next_header` is a valid Next Header value. Note,
1084/// we are intentionally not allowing HopByHopOptions after the first Next
1085/// Header as per section 4.1 of RFC 8200 which restricts the HopByHop extension
1086/// header to only appear as the very first extension header. `is_valid_next_header`.
1087/// If a caller specifies `for_fixed_header` as true, then it is assumed `next_header` is
1088/// the Next Header value in the fixed header, where a HopbyHopOptions extension
1089/// header number is allowed.
1090pub(super) fn is_valid_next_header(next_header: u8, for_fixed_header: bool) -> bool {
1091    // Make sure the Next Header in the fixed header is a valid extension
1092    // header or a valid upper layer protocol.
1093
1094    match Ipv6ExtHdrType::from(next_header) {
1095        // HopByHop Options Extension header as a next header value
1096        // is only valid if it is in the fixed header.
1097        Ipv6ExtHdrType::HopByHopOptions => for_fixed_header,
1098
1099        // Not an IPv6 Extension header number, so make sure it is
1100        // a valid upper layer protocol.
1101        Ipv6ExtHdrType::Other(next_header) => is_valid_next_header_upper_layer(next_header),
1102
1103        // All valid Extension Header numbers
1104        _ => true,
1105    }
1106}
1107
1108/// Make sure a Next Header is a valid upper layer protocol.
1109///
1110/// Make sure a Next Header is a valid upper layer protocol in an IPv6 packet. Note,
1111/// we intentionally are not allowing ICMP(v4) since we are working on IPv6 packets.
1112pub(super) fn is_valid_next_header_upper_layer(next_header: u8) -> bool {
1113    match Ipv6Proto::from(next_header) {
1114        Ipv6Proto::Proto(IpProto::Tcp)
1115        | Ipv6Proto::Proto(IpProto::Udp)
1116        | Ipv6Proto::Icmpv6
1117        | Ipv6Proto::NoNextHeader => true,
1118        Ipv6Proto::Proto(IpProto::Reserved) | Ipv6Proto::Other(_) => false,
1119    }
1120}
1121
1122/// Convert an `ExtensionHeaderOptionParsingError` to an
1123/// `Ipv6ExtensionHeaderParsingError`.
1124///
1125/// `offset` is the offset of the start of the options containing the error, `err`,
1126/// from the end of the fixed header in an IPv6 packet. `header_len` is the
1127/// length of the IPv6 header (including extension headers) that we know about up
1128/// to the point of the error, `err`. Note, any data in a packet after the first
1129/// `header_len` bytes is not parsed, so its context is unknown.
1130fn ext_hdr_opt_err_to_ext_hdr_err(
1131    offset: u32,
1132    header_len: usize,
1133    err: ExtensionHeaderOptionParsingError,
1134) -> Ipv6ExtensionHeaderParsingError {
1135    match err {
1136        ExtensionHeaderOptionParsingError::ErroneousOptionField { pointer } => {
1137            Ipv6ExtensionHeaderParsingError::ErroneousHeaderField {
1138                pointer: offset + pointer,
1139                // TODO: RFC only suggests we SHOULD generate an ICMP message,
1140                // and ideally, we should generate ICMP messages only when the problem
1141                // is severe enough, we do not want to flood the network. So we
1142                // should investigate the criteria for this field to become true.
1143                must_send_icmp: false,
1144                header_len,
1145            }
1146        }
1147        ExtensionHeaderOptionParsingError::UnrecognizedOption { pointer, action } => {
1148            Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1149                pointer: offset + pointer,
1150                must_send_icmp: true,
1151                header_len,
1152                action,
1153            }
1154        }
1155        ExtensionHeaderOptionParsingError::BufferExhausted => {
1156            Ipv6ExtensionHeaderParsingError::BufferExhausted
1157        }
1158    }
1159}
1160
1161fn get_empty_tuple_mut_ref<'a>() -> &'a mut () {
1162    // This is a hack since `&mut ()` is invalid.
1163    let bytes: &mut [u8] = &mut [];
1164    zerocopy::Ref::into_mut(zerocopy::Ref::<_, ()>::from_bytes(bytes).unwrap())
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169    use packet::records::{AlignedRecordSequenceBuilder, RecordBuilder};
1170
1171    use crate::ip::Ipv4Proto;
1172
1173    use super::*;
1174
1175    #[test]
1176    fn test_is_valid_next_header_upper_layer() {
1177        // Make sure upper layer protocols like TCP are valid
1178        assert!(is_valid_next_header_upper_layer(IpProto::Tcp.into()));
1179        assert!(is_valid_next_header_upper_layer(IpProto::Tcp.into()));
1180
1181        // Make sure upper layer protocol ICMP(v4) is not valid
1182        assert!(!is_valid_next_header_upper_layer(Ipv4Proto::Icmp.into()));
1183        assert!(!is_valid_next_header_upper_layer(Ipv4Proto::Icmp.into()));
1184
1185        // Make sure any other value is not valid.
1186        // Note, if 255 becomes a valid value, we should fix this test
1187        assert!(!is_valid_next_header(255, true));
1188        assert!(!is_valid_next_header(255, false));
1189    }
1190
1191    #[test]
1192    fn test_is_valid_next_header() {
1193        // Make sure HopByHop Options is only valid if it is in the first Next Header
1194        // (In the fixed header).
1195        assert!(is_valid_next_header(Ipv6ExtHdrType::HopByHopOptions.into(), true));
1196        assert!(!is_valid_next_header(Ipv6ExtHdrType::HopByHopOptions.into(), false));
1197
1198        // Make sure other extension headers (like routing) can be in any
1199        // Next Header
1200        assert!(is_valid_next_header(Ipv6ExtHdrType::Routing.into(), true));
1201        assert!(is_valid_next_header(Ipv6ExtHdrType::Routing.into(), false));
1202
1203        // Make sure upper layer protocols like TCP can be in any Next Header
1204        assert!(is_valid_next_header(IpProto::Tcp.into(), true));
1205        assert!(is_valid_next_header(IpProto::Tcp.into(), false));
1206
1207        // Make sure upper layer protocol ICMP(v4) cannot be in any Next Header
1208        assert!(!is_valid_next_header(Ipv4Proto::Icmp.into(), true));
1209        assert!(!is_valid_next_header(Ipv4Proto::Icmp.into(), false));
1210
1211        // Make sure any other value is not valid.
1212        // Note, if 255 becomes a valid value, we should fix this test
1213        assert!(!is_valid_next_header(255, true));
1214        assert!(!is_valid_next_header(255, false));
1215    }
1216
1217    #[test]
1218    fn test_hop_by_hop_options() {
1219        // Test parsing of Pad1 (marked as NOP)
1220        let buffer = [0; 10];
1221        let mut context = ExtensionHeaderOptionContext::new();
1222        let options =
1223            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1224                .unwrap();
1225        assert_eq!(options.iter().count(), 0);
1226        assert_eq!(context.bytes_parsed, 10);
1227        assert_eq!(context.options_parsed, 10);
1228
1229        // Test parsing of Pad1 w/ PadN (treated as NOP)
1230        #[rustfmt::skip]
1231        let buffer = [
1232            0,                            // Pad1
1233            1, 0,                         // Pad2
1234            1, 8, 0, 0, 0, 0, 0, 0, 0, 0, // Pad10
1235        ];
1236        let mut context = ExtensionHeaderOptionContext::new();
1237        let options =
1238            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1239                .unwrap();
1240        assert_eq!(options.iter().count(), 0);
1241        assert_eq!(context.bytes_parsed, 13);
1242        assert_eq!(context.options_parsed, 3);
1243
1244        // Test parsing with an unknown option type but its action is
1245        // skip/continue
1246        #[rustfmt::skip]
1247        let buffer = [
1248            0,                            // Pad1
1249            63, 1, 0,                     // Unrecognized Option Type but can skip/continue
1250            1,  6, 0, 0, 0, 0, 0, 0,      // Pad8
1251        ];
1252        let mut context = ExtensionHeaderOptionContext::new();
1253        let options =
1254            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1255                .unwrap();
1256        let options: Vec<HopByHopOption<'_>> = options.iter().collect();
1257        assert_eq!(options.len(), 1);
1258        assert_eq!(options[0].action, ExtensionHeaderOptionAction::SkipAndContinue);
1259        assert_eq!(context.bytes_parsed, 12);
1260        assert_eq!(context.options_parsed, 3);
1261    }
1262
1263    #[test]
1264    fn test_hop_by_hop_options_err() {
1265        // Test parsing but missing last 2 bytes
1266        #[rustfmt::skip]
1267        let buffer = [
1268            0,                            // Pad1
1269            1, 0,                         // Pad2
1270            1, 8, 0, 0, 0, 0, 0, 0,       // Pad10 (but missing 2 bytes)
1271        ];
1272        let mut context = ExtensionHeaderOptionContext::new();
1273        assert_eq!(
1274            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1275                .expect_err("Parsed successfully when we were short 2 bytes"),
1276            ExtensionHeaderOptionParsingError::BufferExhausted
1277        );
1278        assert_eq!(context.bytes_parsed, 3);
1279        assert_eq!(context.options_parsed, 2);
1280
1281        // Test parsing with unknown option type but action set to discard
1282        #[rustfmt::skip]
1283        let buffer = [
1284            1,   1, 0,                    // Pad3
1285            127, 0,                       // Unrecognized Option Type w/ action to discard
1286            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1287        ];
1288        let mut context = ExtensionHeaderOptionContext::new();
1289        assert_eq!(
1290            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1291                .expect_err("Parsed successfully when we had an unrecognized option type"),
1292            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1293                pointer: 3,
1294                action: ExtensionHeaderOptionAction::DiscardPacket,
1295            }
1296        );
1297        assert_eq!(context.bytes_parsed, 3);
1298        assert_eq!(context.options_parsed, 1);
1299
1300        // Test parsing with unknown option type but action set to discard and
1301        // send ICMP.
1302        #[rustfmt::skip]
1303        let buffer = [
1304            1,   1, 0,                    // Pad3
1305            191, 0,                       // Unrecognized Option Type w/ action to discard
1306                                          // & send icmp
1307            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1308        ];
1309        let mut context = ExtensionHeaderOptionContext::new();
1310        assert_eq!(
1311            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1312                .expect_err("Parsed successfully when we had an unrecognized option type"),
1313            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1314                pointer: 3,
1315                action: ExtensionHeaderOptionAction::DiscardPacketSendIcmp,
1316            }
1317        );
1318        assert_eq!(context.bytes_parsed, 3);
1319        assert_eq!(context.options_parsed, 1);
1320
1321        // Test parsing with unknown option type but action set to discard and
1322        // send ICMP if not sending to a multicast address
1323        #[rustfmt::skip]
1324        let buffer = [
1325            1,   1, 0,                    // Pad3
1326            255, 0,                       // Unrecognized Option Type w/ action to discard
1327                                          // & send icmp if no multicast
1328            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1329        ];
1330        let mut context = ExtensionHeaderOptionContext::new();
1331        assert_eq!(
1332            Records::<_, HopByHopOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1333                .expect_err("Parsed successfully when we had an unrecognized option type"),
1334            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1335                pointer: 3,
1336                action: ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast,
1337            }
1338        );
1339        assert_eq!(context.bytes_parsed, 3);
1340        assert_eq!(context.options_parsed, 1);
1341    }
1342
1343    #[test]
1344    fn test_destination_options() {
1345        // Test parsing of Pad1 (marked as NOP)
1346        let buffer = [0; 10];
1347        let mut context = ExtensionHeaderOptionContext::new();
1348        let options =
1349            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1350                .unwrap();
1351        assert_eq!(options.iter().count(), 0);
1352        assert_eq!(context.bytes_parsed, 10);
1353        assert_eq!(context.options_parsed, 10);
1354
1355        // Test parsing of Pad1 w/ PadN (treated as NOP)
1356        #[rustfmt::skip]
1357        let buffer = [
1358            0,                            // Pad1
1359            1, 0,                         // Pad2
1360            1, 8, 0, 0, 0, 0, 0, 0, 0, 0, // Pad10
1361        ];
1362        let mut context = ExtensionHeaderOptionContext::new();
1363        let options =
1364            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1365                .unwrap();
1366        assert_eq!(options.iter().count(), 0);
1367        assert_eq!(context.bytes_parsed, 13);
1368        assert_eq!(context.options_parsed, 3);
1369
1370        // Test parsing with an unknown option type but its action is
1371        // skip/continue
1372        #[rustfmt::skip]
1373        let buffer = [
1374            0,                            // Pad1
1375            63, 1, 0,                     // Unrecognized Option Type but can skip/continue
1376            1,  6, 0, 0, 0, 0, 0, 0,      // Pad8
1377        ];
1378        let mut context = ExtensionHeaderOptionContext::new();
1379        let options =
1380            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1381                .unwrap();
1382        let options: Vec<DestinationOption<'_>> = options.iter().collect();
1383        assert_eq!(options.len(), 1);
1384        assert_eq!(options[0].action, ExtensionHeaderOptionAction::SkipAndContinue);
1385        assert_eq!(context.bytes_parsed, 12);
1386        assert_eq!(context.options_parsed, 3);
1387    }
1388
1389    #[test]
1390    fn test_destination_options_err() {
1391        // Test parsing but missing last 2 bytes
1392        #[rustfmt::skip]
1393        let buffer = [
1394            0,                            // Pad1
1395            1, 0,                         // Pad2
1396            1, 8, 0, 0, 0, 0, 0, 0,       // Pad10 (but missing 2 bytes)
1397        ];
1398        let mut context = ExtensionHeaderOptionContext::new();
1399        assert_eq!(
1400            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1401                .expect_err("Parsed successfully when we were short 2 bytes"),
1402            ExtensionHeaderOptionParsingError::BufferExhausted
1403        );
1404        assert_eq!(context.bytes_parsed, 3);
1405        assert_eq!(context.options_parsed, 2);
1406
1407        // Test parsing with unknown option type but action set to discard
1408        #[rustfmt::skip]
1409        let buffer = [
1410            1,   1, 0,                    // Pad3
1411            127, 0,                       // Unrecognized Option Type w/ action to discard
1412            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1413        ];
1414        let mut context = ExtensionHeaderOptionContext::new();
1415        assert_eq!(
1416            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1417                .expect_err("Parsed successfully when we had an unrecognized option type"),
1418            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1419                pointer: 3,
1420                action: ExtensionHeaderOptionAction::DiscardPacket,
1421            }
1422        );
1423        assert_eq!(context.bytes_parsed, 3);
1424        assert_eq!(context.options_parsed, 1);
1425
1426        // Test parsing with unknown option type but action set to discard and
1427        // send ICMP.
1428        #[rustfmt::skip]
1429        let buffer = [
1430            1,   1, 0,                    // Pad3
1431            191, 0,                       // Unrecognized Option Type w/ action to discard
1432                                          // & send icmp
1433            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1434        ];
1435        let mut context = ExtensionHeaderOptionContext::new();
1436        assert_eq!(
1437            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1438                .expect_err("Parsed successfully when we had an unrecognized option type"),
1439            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1440                pointer: 3,
1441                action: ExtensionHeaderOptionAction::DiscardPacketSendIcmp,
1442            }
1443        );
1444        assert_eq!(context.bytes_parsed, 3);
1445        assert_eq!(context.options_parsed, 1);
1446
1447        // Test parsing with unknown option type but action set to discard and
1448        // send ICMP if not sending to a multicast address
1449        #[rustfmt::skip]
1450        let buffer = [
1451            1,   1, 0,                    // Pad3
1452            255, 0,                       // Unrecognized Option Type w/ action to discard
1453                                          // & send icmp if no multicast
1454            1,   6, 0, 0, 0, 0, 0, 0,     // Pad8
1455        ];
1456        let mut context = ExtensionHeaderOptionContext::new();
1457        assert_eq!(
1458            Records::<_, DestinationOptionsImpl>::parse_with_mut_context(&buffer[..], &mut context)
1459                .expect_err("Parsed successfully when we had an unrecognized option type"),
1460            ExtensionHeaderOptionParsingError::UnrecognizedOption {
1461                pointer: 3,
1462                action: ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast,
1463            }
1464        );
1465        assert_eq!(context.bytes_parsed, 3);
1466        assert_eq!(context.options_parsed, 1);
1467    }
1468
1469    #[test]
1470    fn test_hop_by_hop_options_ext_hdr() {
1471        // Test parsing of just a single Hop By Hop Extension Header.
1472        // The hop by hop options will only be pad options.
1473        let context =
1474            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1475        #[rustfmt::skip]
1476        let buffer = [
1477            IpProto::Tcp.into(),     // Next Header
1478            1,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1479            1,  4, 0, 0, 0, 0,       // Pad6
1480            63, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action set to skip/continue
1481        ];
1482        let ext_hdrs =
1483            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1484                .unwrap();
1485        let ext_hdrs: Vec<Ipv6ExtensionHeader<'_>> = ext_hdrs.iter().collect();
1486        assert_eq!(ext_hdrs.len(), 1);
1487        assert_eq!(ext_hdrs[0].next_header, IpProto::Tcp.into());
1488        if let Ipv6ExtensionHeaderData::HopByHopOptions { options } = ext_hdrs[0].data() {
1489            // Everything should have been a NOP/ignore except for the unrecognized type
1490            let options: Vec<HopByHopOption<'_>> = options.iter().collect();
1491            assert_eq!(options.len(), 1);
1492            assert_eq!(options[0].action, ExtensionHeaderOptionAction::SkipAndContinue);
1493        } else {
1494            panic!("Should have matched HopByHopOptions {:?}", ext_hdrs[0].data());
1495        }
1496    }
1497
1498    #[test]
1499    fn test_hop_by_hop_options_ext_hdr_err() {
1500        // Test parsing of just a single Hop By Hop Extension Header with errors.
1501
1502        // Test with invalid Next Header
1503        let context =
1504            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1505        #[rustfmt::skip]
1506        let buffer = [
1507            255,                  // Next Header (Invalid)
1508            0,                    // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1509            1, 4, 0, 0, 0, 0,     // Pad6
1510        ];
1511        let error =
1512            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1513                .expect_err("Parsed successfully when the next header was invalid");
1514        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
1515            pointer,
1516            must_send_icmp,
1517            header_len,
1518        } = error
1519        {
1520            assert_eq!(pointer, 0);
1521            assert!(!must_send_icmp);
1522            assert_eq!(header_len, 0);
1523        } else {
1524            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
1525        }
1526
1527        // Test with invalid option type w/ action = discard.
1528        let context =
1529            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1530        #[rustfmt::skip]
1531        let buffer = [
1532            IpProto::Tcp.into(),      // Next Header
1533            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1534            1,   4, 0, 0, 0, 0,       // Pad6
1535            127, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard
1536        ];
1537        let error =
1538            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1539                .expect_err("Parsed successfully with an unrecognized option type");
1540        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1541            pointer,
1542            must_send_icmp,
1543            header_len,
1544            action,
1545        } = error
1546        {
1547            assert_eq!(pointer, 8);
1548            assert!(must_send_icmp);
1549            assert_eq!(header_len, 0);
1550            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacket);
1551        } else {
1552            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1553        }
1554
1555        // Test with invalid option type w/ action = discard & send icmp
1556        let context =
1557            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1558        #[rustfmt::skip]
1559        let buffer = [
1560            IpProto::Tcp.into(),      // Next Header
1561            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1562            1,   4, 0, 0, 0, 0,       // Pad6
1563            191, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard & send icmp
1564        ];
1565        let error =
1566            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1567                .expect_err("Parsed successfully with an unrecognized option type");
1568        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1569            pointer,
1570            must_send_icmp,
1571            header_len,
1572            action,
1573        } = error
1574        {
1575            assert_eq!(pointer, 8);
1576            assert!(must_send_icmp);
1577            assert_eq!(header_len, 0);
1578            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacketSendIcmp);
1579        } else {
1580            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1581        }
1582
1583        // Test with invalid option type w/ action = discard & send icmp if not multicast
1584        let context =
1585            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1586        #[rustfmt::skip]
1587        let buffer = [
1588            IpProto::Tcp.into(),      // Next Header
1589            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1590            1,   4, 0, 0, 0, 0,       // Pad6
1591            255, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard & send icmp
1592                                      // if destination address is not a multicast
1593        ];
1594        let error =
1595            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1596                .expect_err("Parsed successfully with an unrecognized option type");
1597        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1598            pointer,
1599            must_send_icmp,
1600            header_len,
1601            action,
1602        } = error
1603        {
1604            assert_eq!(pointer, 8);
1605            assert!(must_send_icmp);
1606            assert_eq!(header_len, 0);
1607            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast);
1608        } else {
1609            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1610        }
1611
1612        // Test with valid option type and invalid data w/ action = skip & continue
1613        let context =
1614            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1615        #[rustfmt::skip]
1616            let buffer = [
1617            IpProto::Tcp.into(),      // Next Header
1618            0,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1619            5,   3, 0, 0, 0,          // RouterAlert, but with a wrong data length.
1620            0,                        // Pad1
1621        ];
1622        let error =
1623            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1624                .expect_err(
1625                    "Should fail to parse the header because one of the option is malformed",
1626                );
1627        if let Ipv6ExtensionHeaderParsingError::ErroneousHeaderField {
1628            pointer, header_len, ..
1629        } = error
1630        {
1631            assert_eq!(pointer, 3);
1632            assert_eq!(header_len, 0);
1633        } else {
1634            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1635        }
1636    }
1637
1638    #[test]
1639    fn test_routing_ext_hdr() {
1640        // Test parsing of just a single Routing Extension Header.
1641        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Routing.into());
1642        #[rustfmt::skip]
1643        let buffer = [
1644            IpProto::Tcp.into(), // Next Header
1645            4,                   // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1646            0,                   // Routing Type
1647            0,                   // Segments Left (0 so no error)
1648            0, 0, 0, 0,          // Reserved
1649            // Addresses for Routing Header w/ Type 0
1650            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
1651            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
1652
1653        ];
1654        let ext_hdrs =
1655            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1656                .unwrap();
1657        assert_eq!(ext_hdrs.iter().count(), 0);
1658    }
1659
1660    #[test]
1661    fn test_routing_ext_hdr_err() {
1662        // Test parsing of just a single Routing Extension Header with errors.
1663
1664        // Explicitly test to make sure we do not support routing type 0 as per RFC 5095
1665        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Routing.into());
1666        #[rustfmt::skip]
1667        let buffer = [
1668            IpProto::Tcp.into(), // Next Header
1669            4,                   // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1670            0,                   // Routing Type (0 which we should not support)
1671            1,                   // Segments Left
1672            0, 0, 0, 0,          // Reserved
1673            // Addresses for Routing Header w/ Type 0
1674            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
1675            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
1676        ];
1677        let error =
1678            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1679                .expect_err("Parsed successfully when the routing type was set to 0");
1680        if let Ipv6ExtensionHeaderParsingError::ErroneousHeaderField {
1681            pointer,
1682            must_send_icmp,
1683            header_len,
1684        } = error
1685        {
1686            assert_eq!(pointer, 2);
1687            assert!(must_send_icmp);
1688            assert_eq!(header_len, 0);
1689        } else {
1690            panic!("Should have matched with ErroneousHeaderField: {:?}", error);
1691        }
1692
1693        // Test Invalid Next Header
1694        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Routing.into());
1695        #[rustfmt::skip]
1696        let buffer = [
1697            255,                 // Next Header (Invalid)
1698            4,                   // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1699            0,                   // Routing Type
1700            1,                   // Segments Left
1701            0, 0, 0, 0,          // Reserved
1702            // Addresses for Routing Header w/ Type 0
1703            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
1704            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
1705
1706        ];
1707        let error =
1708            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1709                .expect_err("Parsed successfully when the next header was invalid");
1710        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
1711            pointer,
1712            must_send_icmp,
1713            header_len,
1714        } = error
1715        {
1716            assert_eq!(pointer, 0);
1717            assert!(!must_send_icmp);
1718            assert_eq!(header_len, 0);
1719        } else {
1720            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
1721        }
1722
1723        // Test Unrecognized Routing Type
1724        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Routing.into());
1725        #[rustfmt::skip]
1726        let buffer = [
1727            IpProto::Tcp.into(), // Next Header
1728            4,                   // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1729            255,                 // Routing Type (Invalid)
1730            1,                   // Segments Left
1731            0, 0, 0, 0,          // Reserved
1732            // Addresses for Routing Header w/ Type 0
1733            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
1734            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
1735
1736        ];
1737        let error =
1738            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1739                .expect_err("Parsed successfully with an unrecognized routing type");
1740        if let Ipv6ExtensionHeaderParsingError::ErroneousHeaderField {
1741            pointer,
1742            must_send_icmp,
1743            header_len,
1744        } = error
1745        {
1746            // Should point to the location of the routing type.
1747            assert_eq!(pointer, 2);
1748            assert!(must_send_icmp);
1749            assert_eq!(header_len, 0);
1750        } else {
1751            panic!("Should have matched with ErroneousHeaderField: {:?}", error);
1752        }
1753    }
1754
1755    #[test]
1756    fn test_fragment_ext_hdr() {
1757        // Test parsing of just a single Fragment Extension Header.
1758        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Fragment.into());
1759        let frag_offset_res_m_flag: u16 = (5063 << 3) | 1;
1760        let identification: u32 = 3266246449;
1761        #[rustfmt::skip]
1762        let buffer = [
1763            IpProto::Tcp.into(),                   // Next Header
1764            0,                                     // Reserved
1765            (frag_offset_res_m_flag >> 8) as u8,   // Fragment Offset MSB
1766            (frag_offset_res_m_flag & 0xFF) as u8, // Fragment Offset LS5bits w/ Res w/ M Flag
1767            // Identification
1768            (identification >> 24) as u8,
1769            ((identification >> 16) & 0xFF) as u8,
1770            ((identification >> 8) & 0xFF) as u8,
1771            (identification & 0xFF) as u8,
1772        ];
1773        let ext_hdrs =
1774            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1775                .unwrap();
1776        let ext_hdrs: Vec<Ipv6ExtensionHeader<'_>> = ext_hdrs.iter().collect();
1777        assert_eq!(ext_hdrs.len(), 1);
1778        assert_eq!(ext_hdrs[0].next_header, IpProto::Tcp.into());
1779
1780        if let Ipv6ExtensionHeaderData::Fragment { fragment_data } = ext_hdrs[0].data() {
1781            assert_eq!(fragment_data.fragment_offset().into_raw(), 5063);
1782            assert_eq!(fragment_data.m_flag(), true);
1783            assert_eq!(fragment_data.identification(), 3266246449);
1784        } else {
1785            panic!("Should have matched Fragment: {:?}", ext_hdrs[0].data());
1786        }
1787    }
1788
1789    #[test]
1790    fn test_fragment_ext_hdr_err() {
1791        // Test parsing of just a single Fragment Extension Header with errors.
1792
1793        // Test invalid Next Header
1794        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::Fragment.into());
1795        let frag_offset_res_m_flag: u16 = (5063 << 3) | 1;
1796        let identification: u32 = 3266246449;
1797        #[rustfmt::skip]
1798        let buffer = [
1799            255,                                   // Next Header (Invalid)
1800            0,                                     // Reserved
1801            (frag_offset_res_m_flag >> 8) as u8,   // Fragment Offset MSB
1802            (frag_offset_res_m_flag & 0xFF) as u8, // Fragment Offset LS5bits w/ Res w/ M Flag
1803            // Identification
1804            (identification >> 24) as u8,
1805            ((identification >> 16) & 0xFF) as u8,
1806            ((identification >> 8) & 0xFF) as u8,
1807            (identification & 0xFF) as u8,
1808        ];
1809        let error =
1810            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1811                .expect_err("Parsed successfully when the next header was invalid");
1812        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
1813            pointer,
1814            must_send_icmp,
1815            header_len,
1816        } = error
1817        {
1818            assert_eq!(pointer, 0);
1819            assert!(!must_send_icmp);
1820            assert_eq!(header_len, 0);
1821        } else {
1822            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
1823        }
1824    }
1825
1826    #[test]
1827    fn test_no_next_header_ext_hdr() {
1828        // Test parsing of just a single NoNextHeader Extension Header.
1829        let context = Ipv6ExtensionHeaderParsingContext::new(Ipv6Proto::NoNextHeader.into());
1830        #[rustfmt::skip]
1831        let buffer = [0, 0, 0, 0,];
1832        let ext_hdrs =
1833            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1834                .unwrap();
1835        assert_eq!(ext_hdrs.iter().count(), 0);
1836    }
1837
1838    #[test]
1839    fn test_destination_options_ext_hdr() {
1840        // Test parsing of just a single Destination options Extension Header.
1841        // The destination options will only be pad options.
1842        let context =
1843            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::DestinationOptions.into());
1844        #[rustfmt::skip]
1845        let buffer = [
1846            IpProto::Tcp.into(),     // Next Header
1847            1,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1848            1, 4, 0, 0, 0, 0,        // Pad6
1849            63, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action set to skip/continue
1850        ];
1851        let ext_hdrs =
1852            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1853                .unwrap();
1854        let ext_hdrs: Vec<Ipv6ExtensionHeader<'_>> = ext_hdrs.iter().collect();
1855        assert_eq!(ext_hdrs.len(), 1);
1856        assert_eq!(ext_hdrs[0].next_header, IpProto::Tcp.into());
1857        if let Ipv6ExtensionHeaderData::DestinationOptions { options } = ext_hdrs[0].data() {
1858            // Everything should have been a NOP/ignore except for the unrecognized type
1859            let options: Vec<DestinationOption<'_>> = options.iter().collect();
1860            assert_eq!(options.len(), 1);
1861            assert_eq!(options[0].action, ExtensionHeaderOptionAction::SkipAndContinue);
1862        } else {
1863            panic!("Should have matched DestinationOptions: {:?}", ext_hdrs[0].data());
1864        }
1865    }
1866
1867    #[test]
1868    fn test_destination_options_ext_hdr_err() {
1869        // Test parsing of just a single Destination Options Extension Header with errors.
1870        let context =
1871            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::DestinationOptions.into());
1872
1873        // Test with invalid Next Header
1874        #[rustfmt::skip]
1875        let buffer = [
1876            255,                  // Next Header (Invalid)
1877            0,                    // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1878            1, 4, 0, 0, 0, 0,     // Pad6
1879        ];
1880        let error =
1881            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1882                .expect_err("Parsed successfully when the next header was invalid");
1883        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
1884            pointer,
1885            must_send_icmp,
1886            header_len,
1887        } = error
1888        {
1889            assert_eq!(pointer, 0);
1890            assert!(!must_send_icmp);
1891            assert_eq!(header_len, 0);
1892        } else {
1893            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
1894        }
1895
1896        // Test with invalid option type w/ action = discard.
1897        let context =
1898            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::DestinationOptions.into());
1899        #[rustfmt::skip]
1900        let buffer = [
1901            IpProto::Tcp.into(),      // Next Header
1902            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1903            1,   4, 0, 0, 0, 0,       // Pad6
1904            127, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard
1905        ];
1906        let error =
1907            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1908                .expect_err("Parsed successfully with an unrecognized option type");
1909        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1910            pointer,
1911            must_send_icmp,
1912            header_len,
1913            action,
1914        } = error
1915        {
1916            assert_eq!(pointer, 8);
1917            assert!(must_send_icmp);
1918            assert_eq!(header_len, 0);
1919            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacket);
1920        } else {
1921            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1922        }
1923
1924        // Test with invalid option type w/ action = discard & send icmp
1925        let context =
1926            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::DestinationOptions.into());
1927        #[rustfmt::skip]
1928        let buffer = [
1929            IpProto::Tcp.into(),      // Next Header
1930            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1931            1,   4, 0, 0, 0, 0,       // Pad6
1932            191, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard & send icmp
1933        ];
1934        let error =
1935            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1936                .expect_err("Parsed successfully with an unrecognized option type");
1937        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1938            pointer,
1939            must_send_icmp,
1940            header_len,
1941            action,
1942        } = error
1943        {
1944            assert_eq!(pointer, 8);
1945            assert!(must_send_icmp);
1946            assert_eq!(header_len, 0);
1947            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacketSendIcmp);
1948        } else {
1949            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1950        }
1951
1952        // Test with invalid option type w/ action = discard & send icmp if not multicast
1953        let context =
1954            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::DestinationOptions.into());
1955        #[rustfmt::skip]
1956        let buffer = [
1957            IpProto::Tcp.into(),      // Next Header
1958            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1959            1,   4, 0, 0, 0, 0,       // Pad6
1960            255, 6, 0, 0, 0, 0, 0, 0, // Unrecognized option type w/ action = discard & send icmp
1961                                      // if destination address is not a multicast
1962        ];
1963        let error =
1964            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
1965                .expect_err("Parsed successfully with an unrecognized option type");
1966        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
1967            pointer,
1968            must_send_icmp,
1969            header_len,
1970            action,
1971        } = error
1972        {
1973            assert_eq!(pointer, 8);
1974            assert!(must_send_icmp);
1975            assert_eq!(header_len, 0);
1976            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast);
1977        } else {
1978            panic!("Should have matched with UnrecognizedOption: {:?}", error);
1979        }
1980    }
1981
1982    #[test]
1983    fn test_multiple_ext_hdrs() {
1984        // Test parsing of multiple extension headers.
1985        let context =
1986            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
1987        #[rustfmt::skip]
1988        let buffer = [
1989            // HopByHop Options Extension Header
1990            Ipv6ExtHdrType::Routing.into(), // Next Header
1991            0,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1992            0,                       // Pad1
1993            1, 0,                    // Pad2
1994            1, 1, 0,                 // Pad3
1995
1996            // Routing Extension Header
1997            Ipv6ExtHdrType::DestinationOptions.into(), // Next Header
1998            4,                                  // Hdr Ext Len (In 8-octet units, not including first 8 octets)
1999            0,                                  // Routing Type
2000            0,                                  // Segments Left
2001            0, 0, 0, 0,                         // Reserved
2002            // Addresses for Routing Header w/ Type 0
2003            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
2004            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
2005
2006            // Destination Options Extension Header
2007            IpProto::Tcp.into(),     // Next Header
2008            1,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2009            0,                       // Pad1
2010            1,  0,                   // Pad2
2011            1,  1, 0,                // Pad3
2012            63, 6, 0, 0, 0, 0, 0, 0, // Unrecognized type w/ action = discard
2013        ];
2014        let ext_hdrs =
2015            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
2016                .unwrap();
2017
2018        let ext_hdrs: Vec<Ipv6ExtensionHeader<'_>> = ext_hdrs.iter().collect();
2019        assert_eq!(ext_hdrs.len(), 2);
2020
2021        // Check first extension header (hop-by-hop options)
2022        assert_eq!(ext_hdrs[0].next_header, Ipv6ExtHdrType::Routing.into());
2023        if let Ipv6ExtensionHeaderData::HopByHopOptions { options } = ext_hdrs[0].data() {
2024            // Everything should have been a NOP/ignore
2025            assert_eq!(options.iter().count(), 0);
2026        } else {
2027            panic!("Should have matched HopByHopOptions: {:?}", ext_hdrs[0].data());
2028        }
2029
2030        // Note the second extension header (routing) should have been skipped because
2031        // its routing type is unrecognized, but segments left is 0.
2032
2033        // Check the third extension header (destination options)
2034        assert_eq!(ext_hdrs[1].next_header, IpProto::Tcp.into());
2035        if let Ipv6ExtensionHeaderData::DestinationOptions { options } = ext_hdrs[1].data() {
2036            // Everything should have been a NOP/ignore except for the unrecognized type
2037            let options: Vec<DestinationOption<'_>> = options.iter().collect();
2038            assert_eq!(options.len(), 1);
2039            assert_eq!(options[0].action, ExtensionHeaderOptionAction::SkipAndContinue);
2040        } else {
2041            panic!("Should have matched DestinationOptions: {:?}", ext_hdrs[2].data());
2042        }
2043    }
2044
2045    #[test]
2046    fn test_multiple_ext_hdrs_errs() {
2047        // Test parsing of multiple extension headers with erros.
2048
2049        // Test Invalid next header in the second extension header.
2050        let context =
2051            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
2052        #[rustfmt::skip]
2053        let buffer = [
2054            // HopByHop Options Extension Header
2055            Ipv6ExtHdrType::Routing.into(), // Next Header
2056            0,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2057            0,                       // Pad1
2058            1, 0,                    // Pad2
2059            1, 1, 0,                 // Pad3
2060
2061            // Routing Extension Header
2062            255,                                // Next Header (Invalid)
2063            4,                                  // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2064            0,                                  // Routing Type
2065            1,                                  // Segments Left
2066            0, 0, 0, 0,                         // Reserved
2067            // Addresses for Routing Header w/ Type 0
2068            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
2069            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
2070
2071            // Destination Options Extension Header
2072            IpProto::Tcp.into(),    // Next Header
2073            1,                      // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2074            0,                      // Pad1
2075            1, 0,                   // Pad2
2076            1, 1, 0,                // Pad3
2077            1, 6, 0, 0, 0, 0, 0, 0, // Pad8
2078        ];
2079        let error =
2080            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
2081                .expect_err("Parsed successfully when the next header was invalid");
2082        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
2083            pointer,
2084            must_send_icmp,
2085            header_len,
2086        } = error
2087        {
2088            assert_eq!(pointer, 8);
2089            assert!(!must_send_icmp);
2090            assert_eq!(header_len, 8);
2091        } else {
2092            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
2093        }
2094
2095        // Test HopByHop extension header not being the very first extension header
2096        let context =
2097            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
2098        #[rustfmt::skip]
2099        let buffer = [
2100            // Routing Extension Header
2101            Ipv6ExtHdrType::HopByHopOptions.into(),    // Next Header (Valid but HopByHop restricted to first extension header)
2102            4,                                  // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2103            0,                                  // Routing Type
2104            1,                                  // Segments Left
2105            0, 0, 0, 0,                         // Reserved
2106            // Addresses for Routing Header w/ Type 0
2107            0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
2108            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
2109
2110            // HopByHop Options Extension Header
2111            Ipv6ExtHdrType::DestinationOptions.into(), // Next Header
2112            0,                                  // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2113            0,                                  // Pad1
2114            1, 0,                               // Pad2
2115            1, 1, 0,                            // Pad3
2116
2117            // Destination Options Extension Header
2118            IpProto::Tcp.into(),    // Next Header
2119            1,                      // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2120            0,                      // Pad1
2121            1, 0,                   // Pad2
2122            1, 1, 0,                // Pad3
2123            1, 6, 0, 0, 0, 0, 0, 0, // Pad8
2124        ];
2125        let error =
2126            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
2127                .expect_err("Parsed successfully when a hop by hop extension header was not the fist extension header");
2128        if let Ipv6ExtensionHeaderParsingError::UnrecognizedNextHeader {
2129            pointer,
2130            must_send_icmp,
2131            header_len,
2132        } = error
2133        {
2134            assert_eq!(pointer, 0);
2135            assert!(!must_send_icmp);
2136            assert_eq!(header_len, 0);
2137        } else {
2138            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
2139        }
2140
2141        // Test parsing of destination options with an unrecognized option type w/ action
2142        // set to discard and send icmp
2143        let context =
2144            Ipv6ExtensionHeaderParsingContext::new(Ipv6ExtHdrType::HopByHopOptions.into());
2145        #[rustfmt::skip]
2146        let buffer = [
2147            // HopByHop Options Extension Header
2148            Ipv6ExtHdrType::DestinationOptions.into(), // Next Header
2149            0,                       // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2150            0,                       // Pad1
2151            1, 0,                    // Pad2
2152            1, 1, 0,                 // Pad3
2153
2154            // Destination Options Extension Header
2155            IpProto::Tcp.into(),      // Next Header
2156            1,                        // Hdr Ext Len (In 8-octet units, not including first 8 octets)
2157            0,                        // Pad1
2158            1,   0,                   // Pad2
2159            1,   1, 0,                // Pad3
2160            191, 6, 0, 0, 0, 0, 0, 0, // Unrecognized type w/ action = discard
2161        ];
2162        let error =
2163            Records::<&[u8], Ipv6ExtensionHeaderImpl>::parse_with_context(&buffer[..], context)
2164                .expect_err("Parsed successfully with an unrecognized destination option type");
2165        if let Ipv6ExtensionHeaderParsingError::UnrecognizedOption {
2166            pointer,
2167            must_send_icmp,
2168            header_len,
2169            action,
2170        } = error
2171        {
2172            assert_eq!(pointer, 16);
2173            assert!(must_send_icmp);
2174            assert_eq!(header_len, 8);
2175            assert_eq!(action, ExtensionHeaderOptionAction::DiscardPacketSendIcmp);
2176        } else {
2177            panic!("Should have matched with UnrecognizedNextHeader: {:?}", error);
2178        }
2179    }
2180
2181    #[test]
2182    fn test_serialize_hbh_router_alert() {
2183        let mut buffer = [0u8; 4];
2184        let option = HopByHopOption {
2185            action: ExtensionHeaderOptionAction::SkipAndContinue,
2186            mutable: false,
2187            data: HopByHopOptionData::RouterAlert { data: 0 },
2188        };
2189        <HopByHopOption<'_> as RecordBuilder>::serialize_into(&option, &mut buffer);
2190        assert_eq!(&buffer[..], &[5, 2, 0, 0]);
2191    }
2192
2193    #[test]
2194    fn test_parse_hbh_router_alert() {
2195        // Test RouterAlert with correct data length.
2196        let context = ExtensionHeaderOptionContext::new();
2197        let buffer = [5, 2, 0, 0];
2198
2199        let options =
2200            Records::<_, HopByHopOptionsImpl>::parse_with_context(&buffer[..], context).unwrap();
2201        let rtralrt = options.iter().next().unwrap();
2202        assert!(!rtralrt.mutable);
2203        assert_eq!(rtralrt.action, ExtensionHeaderOptionAction::SkipAndContinue);
2204        assert_eq!(rtralrt.data, HopByHopOptionData::RouterAlert { data: 0 });
2205
2206        // Test RouterAlert with wrong data length.
2207        let result = <HopByHopOptionDataImpl as ExtensionHeaderOptionDataImpl>::parse_option(
2208            5,
2209            &buffer[1..],
2210            &mut (),
2211            false,
2212        );
2213        assert_eq!(result, ExtensionHeaderOptionDataParseResult::ErrorAt(1));
2214
2215        let context = ExtensionHeaderOptionContext::new();
2216        let buffer = [5, 3, 0, 0, 0];
2217
2218        let error = Records::<_, HopByHopOptionsImpl>::parse_with_context(&buffer[..], context)
2219            .expect_err(
2220                "Parsing a malformed option with recognized kind but with wrong data should fail",
2221            );
2222        assert_eq!(error, ExtensionHeaderOptionParsingError::ErroneousOptionField { pointer: 1 });
2223    }
2224
2225    // Construct a bunch of `HopByHopOption`s according to lengths:
2226    // if `length` is
2227    //   - `None`: RouterAlert is generated.
2228    //   - `Some(l)`: the Unrecognized option with length `l - 2` is constructed.
2229    //     It is `l - 2` so that the whole record has size l.
2230    // This function is used so that the alignment of RouterAlert can be tested.
2231    fn trivial_hbh_options(lengths: &[Option<usize>]) -> Vec<HopByHopOption<'static>> {
2232        static ZEROES: [u8; 16] = [0u8; 16];
2233        lengths
2234            .iter()
2235            .map(|l| HopByHopOption {
2236                mutable: false,
2237                action: ExtensionHeaderOptionAction::SkipAndContinue,
2238                data: match l {
2239                    Some(l) => HopByHopOptionData::Unrecognized {
2240                        kind: 1,
2241                        len: (*l - 2) as u8,
2242                        data: &ZEROES[0..*l - 2],
2243                    },
2244                    None => HopByHopOptionData::RouterAlert { data: 0 },
2245                },
2246            })
2247            .collect()
2248    }
2249
2250    #[test]
2251    fn test_aligned_records_serializer() {
2252        // Test whether we can serialize our RouterAlert at 2-byte boundary
2253        for i in 2..12 {
2254            let options = trivial_hbh_options(&[Some(i), None]);
2255            let ser = AlignedRecordSequenceBuilder::<
2256                ExtensionHeaderOption<HopByHopOptionData<'_>>,
2257                _,
2258            >::new(2, options.iter());
2259            let mut buf = [0u8; 16];
2260            ser.serialize_into(&mut buf[0..16]);
2261            let base = (i + 1) & !1;
2262            // we want to make sure that our RouterAlert is aligned at 2-byte boundary.
2263            assert_eq!(&buf[base..base + 4], &[5, 2, 0, 0]);
2264        }
2265    }
2266}