Skip to main content

packet_formats/ipv6/
ext_hdrs.rs

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