Skip to main content

packet_formats/ipv6/
ext_hdrs.rs

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