smoltcp/wire/
ipv6option.rs

1use super::{Error, Result};
2#[cfg(feature = "proto-rpl")]
3use super::{RplHopByHopPacket, RplHopByHopRepr};
4
5use byteorder::{ByteOrder, NetworkEndian};
6use core::fmt;
7
8enum_with_unknown! {
9    /// IPv6 Extension Header Option Type
10    pub enum Type(u8) {
11        /// 1 byte of padding
12        Pad1 = 0,
13        /// Multiple bytes of padding
14        PadN = 1,
15        /// Router Alert
16        RouterAlert = 5,
17        /// RPL Option
18        Rpl  = 0x63,
19    }
20}
21
22impl fmt::Display for Type {
23    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24        match *self {
25            Type::Pad1 => write!(f, "Pad1"),
26            Type::PadN => write!(f, "PadN"),
27            Type::Rpl => write!(f, "RPL"),
28            Type::RouterAlert => write!(f, "RouterAlert"),
29            Type::Unknown(id) => write!(f, "{id}"),
30        }
31    }
32}
33
34enum_with_unknown! {
35    /// A high-level representation of an IPv6 Router Alert Header Option.
36    ///
37    /// Router Alert options always contain exactly one `u16`; see [RFC 2711 § 2.1].
38    ///
39    /// [RFC 2711 § 2.1]: https://tools.ietf.org/html/rfc2711#section-2.1
40    pub enum RouterAlert(u16) {
41        MulticastListenerDiscovery = 0,
42        Rsvp = 1,
43        ActiveNetworks = 2,
44    }
45}
46
47impl RouterAlert {
48    /// Per [RFC 2711 § 2.1], Router Alert options always have 2 bytes of data.
49    ///
50    /// [RFC 2711 § 2.1]: https://tools.ietf.org/html/rfc2711#section-2.1
51    pub const DATA_LEN: u8 = 2;
52}
53
54/// Action required when parsing the given IPv6 Extension
55/// Header Option Type fails
56#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
57#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58pub enum FailureType {
59    /// Skip this option and continue processing the packet
60    Skip = 0b00000000,
61    /// Discard the containing packet
62    Discard = 0b01000000,
63    /// Discard the containing packet and notify the sender
64    DiscardSendAll = 0b10000000,
65    /// Discard the containing packet and only notify the sender
66    /// if the sender is a unicast address
67    DiscardSendUnicast = 0b11000000,
68}
69
70impl From<u8> for FailureType {
71    fn from(value: u8) -> FailureType {
72        match value & 0b11000000 {
73            0b00000000 => FailureType::Skip,
74            0b01000000 => FailureType::Discard,
75            0b10000000 => FailureType::DiscardSendAll,
76            0b11000000 => FailureType::DiscardSendUnicast,
77            _ => unreachable!(),
78        }
79    }
80}
81
82impl From<FailureType> for u8 {
83    fn from(value: FailureType) -> Self {
84        match value {
85            FailureType::Skip => 0b00000000,
86            FailureType::Discard => 0b01000000,
87            FailureType::DiscardSendAll => 0b10000000,
88            FailureType::DiscardSendUnicast => 0b11000000,
89        }
90    }
91}
92
93impl fmt::Display for FailureType {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        match *self {
96            FailureType::Skip => write!(f, "skip"),
97            FailureType::Discard => write!(f, "discard"),
98            FailureType::DiscardSendAll => write!(f, "discard and send error"),
99            FailureType::DiscardSendUnicast => write!(f, "discard and send error if unicast"),
100        }
101    }
102}
103
104impl From<Type> for FailureType {
105    fn from(other: Type) -> FailureType {
106        let raw: u8 = other.into();
107        Self::from(raw & 0b11000000u8)
108    }
109}
110
111/// A read/write wrapper around an IPv6 Extension Header Option.
112#[derive(Debug, PartialEq, Eq)]
113#[cfg_attr(feature = "defmt", derive(defmt::Format))]
114pub struct Ipv6Option<T: AsRef<[u8]>> {
115    buffer: T,
116}
117
118// Format of Option
119//
120// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
121// |  Option Type  |  Opt Data Len |  Option Data
122// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
123//
124//
125// See https://tools.ietf.org/html/rfc8200#section-4.2 for details.
126mod field {
127    #![allow(non_snake_case)]
128
129    use crate::wire::field::*;
130
131    // 8-bit identifier of the type of option.
132    pub const TYPE: usize = 0;
133    // 8-bit unsigned integer. Length of the DATA field of this option, in octets.
134    pub const LENGTH: usize = 1;
135    // Variable-length field. Option-Type-specific data.
136    pub const fn DATA(length: u8) -> Field {
137        2..length as usize + 2
138    }
139}
140
141impl<T: AsRef<[u8]>> Ipv6Option<T> {
142    /// Create a raw octet buffer with an IPv6 Extension Header Option structure.
143    pub const fn new_unchecked(buffer: T) -> Ipv6Option<T> {
144        Ipv6Option { buffer }
145    }
146
147    /// Shorthand for a combination of [new_unchecked] and [check_len].
148    ///
149    /// [new_unchecked]: #method.new_unchecked
150    /// [check_len]: #method.check_len
151    pub fn new_checked(buffer: T) -> Result<Ipv6Option<T>> {
152        let opt = Self::new_unchecked(buffer);
153        opt.check_len()?;
154        Ok(opt)
155    }
156
157    /// Ensure that no accessor method will panic if called.
158    /// Returns `Err(Error)` if the buffer is too short.
159    ///
160    /// The result of this check is invalidated by calling [set_data_len].
161    ///
162    /// [set_data_len]: #method.set_data_len
163    pub fn check_len(&self) -> Result<()> {
164        let data = self.buffer.as_ref();
165        let len = data.len();
166
167        if len < field::LENGTH {
168            return Err(Error);
169        }
170
171        if self.option_type() == Type::Pad1 {
172            return Ok(());
173        }
174
175        if len == field::LENGTH {
176            return Err(Error);
177        }
178
179        let df = field::DATA(data[field::LENGTH]);
180
181        if len < df.end {
182            return Err(Error);
183        }
184
185        Ok(())
186    }
187
188    /// Consume the ipv6 option, returning the underlying buffer.
189    pub fn into_inner(self) -> T {
190        self.buffer
191    }
192
193    /// Return the option type.
194    #[inline]
195    pub fn option_type(&self) -> Type {
196        let data = self.buffer.as_ref();
197        Type::from(data[field::TYPE])
198    }
199
200    /// Return the length of the data.
201    ///
202    /// # Panics
203    /// This function panics if this is an 1-byte padding option.
204    #[inline]
205    pub fn data_len(&self) -> u8 {
206        let data = self.buffer.as_ref();
207        data[field::LENGTH]
208    }
209}
210
211impl<'a, T: AsRef<[u8]> + ?Sized> Ipv6Option<&'a T> {
212    /// Return the option data.
213    ///
214    /// # Panics
215    /// This function panics if this is an 1-byte padding option.
216    #[inline]
217    pub fn data(&self) -> &'a [u8] {
218        let len = self.data_len();
219        let data = self.buffer.as_ref();
220        &data[field::DATA(len)]
221    }
222}
223
224impl<T: AsRef<[u8]> + AsMut<[u8]>> Ipv6Option<T> {
225    /// Set the option type.
226    #[inline]
227    pub fn set_option_type(&mut self, value: Type) {
228        let data = self.buffer.as_mut();
229        data[field::TYPE] = value.into();
230    }
231
232    /// Set the option data length.
233    ///
234    /// # Panics
235    /// This function panics if this is an 1-byte padding option.
236    #[inline]
237    pub fn set_data_len(&mut self, value: u8) {
238        let data = self.buffer.as_mut();
239        data[field::LENGTH] = value;
240    }
241}
242
243impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Ipv6Option<&'a mut T> {
244    /// Return a mutable pointer to the option data.
245    ///
246    /// # Panics
247    /// This function panics if this is an 1-byte padding option.
248    #[inline]
249    pub fn data_mut(&mut self) -> &mut [u8] {
250        let len = self.data_len();
251        let data = self.buffer.as_mut();
252        &mut data[field::DATA(len)]
253    }
254}
255
256impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Ipv6Option<&'a T> {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        match Repr::parse(self) {
259            Ok(repr) => write!(f, "{repr}"),
260            Err(err) => {
261                write!(f, "IPv6 Extension Option ({err})")?;
262                Ok(())
263            }
264        }
265    }
266}
267
268/// A high-level representation of an IPv6 Extension Header Option.
269#[derive(Debug, PartialEq, Eq, Clone, Copy)]
270#[cfg_attr(feature = "defmt", derive(defmt::Format))]
271#[non_exhaustive]
272pub enum Repr<'a> {
273    Pad1,
274    PadN(u8),
275    RouterAlert(RouterAlert),
276    #[cfg(feature = "proto-rpl")]
277    Rpl(RplHopByHopRepr),
278    Unknown {
279        type_: Type,
280        length: u8,
281        data: &'a [u8],
282    },
283}
284
285impl<'a> Repr<'a> {
286    /// Parse an IPv6 Extension Header Option and return a high-level representation.
287    pub fn parse<T>(opt: &Ipv6Option<&'a T>) -> Result<Repr<'a>>
288    where
289        T: AsRef<[u8]> + ?Sized,
290    {
291        opt.check_len()?;
292        match opt.option_type() {
293            Type::Pad1 => Ok(Repr::Pad1),
294            Type::PadN => Ok(Repr::PadN(opt.data_len())),
295            Type::RouterAlert => {
296                if opt.data_len() == RouterAlert::DATA_LEN {
297                    let raw = NetworkEndian::read_u16(opt.data());
298                    Ok(Repr::RouterAlert(RouterAlert::from(raw)))
299                } else {
300                    Err(Error)
301                }
302            }
303            #[cfg(feature = "proto-rpl")]
304            Type::Rpl => Ok(Repr::Rpl(RplHopByHopRepr::parse(
305                &RplHopByHopPacket::new_checked(opt.data())?,
306            ))),
307            #[cfg(not(feature = "proto-rpl"))]
308            Type::Rpl => Ok(Repr::Unknown {
309                type_: Type::Rpl,
310                length: opt.data_len(),
311                data: opt.data(),
312            }),
313
314            unknown_type @ Type::Unknown(_) => Ok(Repr::Unknown {
315                type_: unknown_type,
316                length: opt.data_len(),
317                data: opt.data(),
318            }),
319        }
320    }
321
322    /// Return the length of a header that will be emitted from this high-level representation.
323    pub const fn buffer_len(&self) -> usize {
324        match *self {
325            Repr::Pad1 => 1,
326            Repr::PadN(length) => field::DATA(length).end,
327            Repr::RouterAlert(_) => field::DATA(RouterAlert::DATA_LEN).end,
328            #[cfg(feature = "proto-rpl")]
329            Repr::Rpl(opt) => field::DATA(opt.buffer_len() as u8).end,
330            Repr::Unknown { length, .. } => field::DATA(length).end,
331        }
332    }
333
334    /// Emit a high-level representation into an IPv6 Extension Header Option.
335    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, opt: &mut Ipv6Option<&'a mut T>) {
336        match *self {
337            Repr::Pad1 => opt.set_option_type(Type::Pad1),
338            Repr::PadN(len) => {
339                opt.set_option_type(Type::PadN);
340                opt.set_data_len(len);
341                // Ensure all padding bytes are set to zero.
342                for x in opt.data_mut().iter_mut() {
343                    *x = 0
344                }
345            }
346            Repr::RouterAlert(router_alert) => {
347                opt.set_option_type(Type::RouterAlert);
348                opt.set_data_len(RouterAlert::DATA_LEN);
349                NetworkEndian::write_u16(opt.data_mut(), router_alert.into());
350            }
351            #[cfg(feature = "proto-rpl")]
352            Repr::Rpl(rpl) => {
353                opt.set_option_type(Type::Rpl);
354                opt.set_data_len(4);
355                rpl.emit(&mut crate::wire::RplHopByHopPacket::new_unchecked(
356                    opt.data_mut(),
357                ));
358            }
359            Repr::Unknown {
360                type_,
361                length,
362                data,
363            } => {
364                opt.set_option_type(type_);
365                opt.set_data_len(length);
366                opt.data_mut().copy_from_slice(&data[..length as usize]);
367            }
368        }
369    }
370}
371
372/// A iterator for IPv6 options.
373#[derive(Debug)]
374#[cfg_attr(feature = "defmt", derive(defmt::Format))]
375pub struct Ipv6OptionsIterator<'a> {
376    pos: usize,
377    length: usize,
378    data: &'a [u8],
379    hit_error: bool,
380}
381
382impl<'a> Ipv6OptionsIterator<'a> {
383    /// Create a new `Ipv6OptionsIterator`, used to iterate over the
384    /// options contained in a IPv6 Extension Header (e.g. the Hop-by-Hop
385    /// header).
386    pub fn new(data: &'a [u8]) -> Ipv6OptionsIterator<'a> {
387        let length = data.len();
388        Ipv6OptionsIterator {
389            pos: 0,
390            hit_error: false,
391            length,
392            data,
393        }
394    }
395}
396
397impl<'a> Iterator for Ipv6OptionsIterator<'a> {
398    type Item = Result<Repr<'a>>;
399
400    fn next(&mut self) -> Option<Self::Item> {
401        if self.pos < self.length && !self.hit_error {
402            // If we still have data to parse and we have not previously
403            // hit an error, attempt to parse the next option.
404            match Ipv6Option::new_checked(&self.data[self.pos..]) {
405                Ok(hdr) => match Repr::parse(&hdr) {
406                    Ok(repr) => {
407                        self.pos += repr.buffer_len();
408                        Some(Ok(repr))
409                    }
410                    Err(e) => {
411                        self.hit_error = true;
412                        Some(Err(e))
413                    }
414                },
415                Err(e) => {
416                    self.hit_error = true;
417                    Some(Err(e))
418                }
419            }
420        } else {
421            // If we failed to parse a previous option or hit the end of the
422            // buffer, we do not continue to iterate.
423            None
424        }
425    }
426}
427
428impl<'a> fmt::Display for Repr<'a> {
429    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
430        write!(f, "IPv6 Option ")?;
431        match *self {
432            Repr::Pad1 => write!(f, "{} ", Type::Pad1),
433            Repr::PadN(len) => write!(f, "{} length={} ", Type::PadN, len),
434            Repr::RouterAlert(alert) => write!(f, "{} value={:?}", Type::RouterAlert, alert),
435            #[cfg(feature = "proto-rpl")]
436            Repr::Rpl(rpl) => write!(f, "{} {rpl}", Type::Rpl),
437            Repr::Unknown { type_, length, .. } => write!(f, "{type_} length={length} "),
438        }
439    }
440}
441
442#[cfg(test)]
443mod test {
444    use super::*;
445
446    static IPV6OPTION_BYTES_PAD1: [u8; 1] = [0x0];
447    static IPV6OPTION_BYTES_PADN: [u8; 3] = [0x1, 0x1, 0x0];
448    static IPV6OPTION_BYTES_UNKNOWN: [u8; 5] = [0xff, 0x3, 0x0, 0x0, 0x0];
449    static IPV6OPTION_BYTES_ROUTER_ALERT_MLD: [u8; 4] = [0x05, 0x02, 0x00, 0x00];
450    static IPV6OPTION_BYTES_ROUTER_ALERT_RSVP: [u8; 4] = [0x05, 0x02, 0x00, 0x01];
451    static IPV6OPTION_BYTES_ROUTER_ALERT_ACTIVE_NETWORKS: [u8; 4] = [0x05, 0x02, 0x00, 0x02];
452    static IPV6OPTION_BYTES_ROUTER_ALERT_UNKNOWN: [u8; 4] = [0x05, 0x02, 0xbe, 0xef];
453    #[cfg(feature = "proto-rpl")]
454    static IPV6OPTION_BYTES_RPL: [u8; 6] = [0x63, 0x04, 0x00, 0x1e, 0x08, 0x00];
455
456    #[test]
457    fn test_check_len() {
458        let bytes = [0u8];
459        // zero byte buffer
460        assert_eq!(
461            Err(Error),
462            Ipv6Option::new_unchecked(&bytes[..0]).check_len()
463        );
464        // pad1
465        assert_eq!(
466            Ok(()),
467            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1).check_len()
468        );
469
470        // padn with truncated data
471        assert_eq!(
472            Err(Error),
473            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN[..2]).check_len()
474        );
475        // padn
476        assert_eq!(
477            Ok(()),
478            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN).check_len()
479        );
480
481        // router alert with truncated data
482        assert_eq!(
483            Err(Error),
484            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD[..3]).check_len()
485        );
486        // router alert
487        assert_eq!(
488            Ok(()),
489            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD).check_len()
490        );
491
492        // unknown option type with truncated data
493        assert_eq!(
494            Err(Error),
495            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..4]).check_len()
496        );
497        assert_eq!(
498            Err(Error),
499            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..1]).check_len()
500        );
501        // unknown type
502        assert_eq!(
503            Ok(()),
504            Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN).check_len()
505        );
506
507        #[cfg(feature = "proto-rpl")]
508        {
509            assert_eq!(
510                Ok(()),
511                Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL).check_len()
512            );
513        }
514    }
515
516    #[test]
517    #[should_panic(expected = "index out of bounds")]
518    fn test_data_len() {
519        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
520        opt.data_len();
521    }
522
523    #[test]
524    fn test_option_deconstruct() {
525        // one octet of padding
526        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
527        assert_eq!(opt.option_type(), Type::Pad1);
528
529        // two octets of padding
530        let bytes: [u8; 2] = [0x1, 0x0];
531        let opt = Ipv6Option::new_unchecked(&bytes);
532        assert_eq!(opt.option_type(), Type::PadN);
533        assert_eq!(opt.data_len(), 0);
534
535        // three octets of padding
536        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN);
537        assert_eq!(opt.option_type(), Type::PadN);
538        assert_eq!(opt.data_len(), 1);
539        assert_eq!(opt.data(), &[0]);
540
541        // extra bytes in buffer
542        let bytes: [u8; 10] = [0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff];
543        let opt = Ipv6Option::new_unchecked(&bytes);
544        assert_eq!(opt.option_type(), Type::PadN);
545        assert_eq!(opt.data_len(), 7);
546        assert_eq!(opt.data(), &[0, 0, 0, 0, 0, 0, 0]);
547
548        // router alert
549        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD);
550        assert_eq!(opt.option_type(), Type::RouterAlert);
551        assert_eq!(opt.data_len(), 2);
552        assert_eq!(opt.data(), &[0, 0]);
553
554        // unrecognized option
555        let bytes: [u8; 1] = [0xff];
556        let opt = Ipv6Option::new_unchecked(&bytes);
557        assert_eq!(opt.option_type(), Type::Unknown(255));
558
559        // unrecognized option without length and data
560        assert_eq!(Ipv6Option::new_checked(&bytes), Err(Error));
561
562        #[cfg(feature = "proto-rpl")]
563        {
564            let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
565            assert_eq!(opt.option_type(), Type::Rpl);
566            assert_eq!(opt.data_len(), 4);
567            assert_eq!(opt.data(), &[0x00, 0x1e, 0x08, 0x00]);
568        }
569    }
570
571    #[test]
572    fn test_option_parse() {
573        // one octet of padding
574        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
575        let pad1 = Repr::parse(&opt).unwrap();
576        assert_eq!(pad1, Repr::Pad1);
577        assert_eq!(pad1.buffer_len(), 1);
578
579        // two or more octets of padding
580        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN);
581        let padn = Repr::parse(&opt).unwrap();
582        assert_eq!(padn, Repr::PadN(1));
583        assert_eq!(padn.buffer_len(), 3);
584
585        // router alert (MLD)
586        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD);
587        let alert = Repr::parse(&opt).unwrap();
588        assert_eq!(
589            alert,
590            Repr::RouterAlert(RouterAlert::MulticastListenerDiscovery)
591        );
592        assert_eq!(alert.buffer_len(), 4);
593
594        // router alert (RSVP)
595        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_RSVP);
596        let alert = Repr::parse(&opt).unwrap();
597        assert_eq!(alert, Repr::RouterAlert(RouterAlert::Rsvp));
598        assert_eq!(alert.buffer_len(), 4);
599
600        // router alert (active networks)
601        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_ACTIVE_NETWORKS);
602        let alert = Repr::parse(&opt).unwrap();
603        assert_eq!(alert, Repr::RouterAlert(RouterAlert::ActiveNetworks));
604        assert_eq!(alert.buffer_len(), 4);
605
606        // router alert (unknown)
607        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_UNKNOWN);
608        let alert = Repr::parse(&opt).unwrap();
609        assert_eq!(alert, Repr::RouterAlert(RouterAlert::Unknown(0xbeef)));
610        assert_eq!(alert.buffer_len(), 4);
611
612        // router alert (incorrect data length)
613        let opt = Ipv6Option::new_unchecked(&[0x05, 0x03, 0x00, 0x00, 0x00]);
614        let alert = Repr::parse(&opt);
615        assert_eq!(alert, Err(Error));
616
617        // unrecognized option type
618        let data = [0u8; 3];
619        let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN);
620        let unknown = Repr::parse(&opt).unwrap();
621        assert_eq!(
622            unknown,
623            Repr::Unknown {
624                type_: Type::Unknown(255),
625                length: 3,
626                data: &data
627            }
628        );
629
630        #[cfg(feature = "proto-rpl")]
631        {
632            let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
633            let rpl = Repr::parse(&opt).unwrap();
634
635            assert_eq!(
636                rpl,
637                Repr::Rpl(crate::wire::RplHopByHopRepr {
638                    down: false,
639                    rank_error: false,
640                    forwarding_error: false,
641                    instance_id: crate::wire::RplInstanceId::from(0x1e),
642                    sender_rank: 0x0800,
643                })
644            );
645        }
646    }
647
648    #[test]
649    fn test_option_emit() {
650        let repr = Repr::Pad1;
651        let mut bytes = [255u8; 1]; // don't assume bytes are initialized to zero
652        let mut opt = Ipv6Option::new_unchecked(&mut bytes);
653        repr.emit(&mut opt);
654        assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PAD1);
655
656        let repr = Repr::PadN(1);
657        let mut bytes = [255u8; 3]; // don't assume bytes are initialized to zero
658        let mut opt = Ipv6Option::new_unchecked(&mut bytes);
659        repr.emit(&mut opt);
660        assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PADN);
661
662        let repr = Repr::RouterAlert(RouterAlert::MulticastListenerDiscovery);
663        let mut bytes = [255u8; 4]; // don't assume bytes are initialized to zero
664        let mut opt = Ipv6Option::new_unchecked(&mut bytes);
665        repr.emit(&mut opt);
666        assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_ROUTER_ALERT_MLD);
667
668        let data = [0u8; 3];
669        let repr = Repr::Unknown {
670            type_: Type::Unknown(255),
671            length: 3,
672            data: &data,
673        };
674        let mut bytes = [254u8; 5]; // don't assume bytes are initialized to zero
675        let mut opt = Ipv6Option::new_unchecked(&mut bytes);
676        repr.emit(&mut opt);
677        assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_UNKNOWN);
678
679        #[cfg(feature = "proto-rpl")]
680        {
681            let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
682            let rpl = Repr::parse(&opt).unwrap();
683            let mut bytes = [0u8; 6];
684            rpl.emit(&mut Ipv6Option::new_unchecked(&mut bytes));
685
686            assert_eq!(&bytes, &IPV6OPTION_BYTES_RPL);
687        }
688    }
689
690    #[test]
691    fn test_failure_type() {
692        let mut failure_type: FailureType = Type::Pad1.into();
693        assert_eq!(failure_type, FailureType::Skip);
694        failure_type = Type::PadN.into();
695        assert_eq!(failure_type, FailureType::Skip);
696        failure_type = Type::RouterAlert.into();
697        assert_eq!(failure_type, FailureType::Skip);
698        failure_type = Type::Unknown(0b01000001).into();
699        assert_eq!(failure_type, FailureType::Discard);
700        failure_type = Type::Unknown(0b10100000).into();
701        assert_eq!(failure_type, FailureType::DiscardSendAll);
702        failure_type = Type::Unknown(0b11000100).into();
703        assert_eq!(failure_type, FailureType::DiscardSendUnicast);
704    }
705
706    #[test]
707    fn test_options_iter() {
708        let options = [
709            0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x11, 0x00, 0x05,
710            0x02, 0x00, 0x01, 0x01, 0x08, 0x00,
711        ];
712
713        let iterator = Ipv6OptionsIterator::new(&options);
714        for (i, opt) in iterator.enumerate() {
715            match (i, opt) {
716                (0, Ok(Repr::Pad1)) => continue,
717                (1, Ok(Repr::PadN(1))) => continue,
718                (2, Ok(Repr::PadN(2))) => continue,
719                (3, Ok(Repr::PadN(0))) => continue,
720                (4, Ok(Repr::Pad1)) => continue,
721                (
722                    5,
723                    Ok(Repr::Unknown {
724                        type_: Type::Unknown(0x11),
725                        length: 0,
726                        ..
727                    }),
728                ) => continue,
729                (6, Ok(Repr::RouterAlert(RouterAlert::Rsvp))) => continue,
730                (7, Err(Error)) => continue,
731                (i, res) => panic!("Unexpected option `{res:?}` at index {i}"),
732            }
733        }
734    }
735}