Skip to main content

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