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