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