1use bitflags::bitflags;
2use byteorder::{ByteOrder, NetworkEndian};
3use core::fmt;
4
5use super::{Error, Result};
6use crate::time::Duration;
7use crate::wire::{Ipv6Address, Ipv6AddressExt, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN};
8
9use crate::wire::RawHardwareAddress;
10
11enum_with_unknown! {
12 pub enum Type(u8) {
14 SourceLinkLayerAddr = 0x1,
16 TargetLinkLayerAddr = 0x2,
18 PrefixInformation = 0x3,
20 RedirectedHeader = 0x4,
22 Mtu = 0x5
24 }
25}
26
27impl fmt::Display for Type {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self {
30 Type::SourceLinkLayerAddr => write!(f, "source link-layer address"),
31 Type::TargetLinkLayerAddr => write!(f, "target link-layer address"),
32 Type::PrefixInformation => write!(f, "prefix information"),
33 Type::RedirectedHeader => write!(f, "redirected header"),
34 Type::Mtu => write!(f, "mtu"),
35 Type::Unknown(id) => write!(f, "{id}"),
36 }
37 }
38}
39
40bitflags! {
41 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
42 pub struct PrefixInfoFlags: u8 {
43 const ON_LINK = 0b10000000;
44 const ADDRCONF = 0b01000000;
45 }
46}
47
48#[derive(Debug, PartialEq, Eq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53pub struct NdiscOption<T: AsRef<[u8]>> {
54 buffer: T,
55}
56
57mod field {
67 #![allow(non_snake_case)]
68
69 use crate::wire::field::*;
70
71 pub const TYPE: usize = 0;
73 pub const LENGTH: usize = 1;
75 pub const MIN_OPT_LEN: usize = 8;
77 pub const fn DATA(length: u8) -> Field {
79 2..length as usize * 8
80 }
81
82 pub const PREFIX_LEN: usize = 2;
108 pub const FLAGS: usize = 3;
110 pub const VALID_LT: Field = 4..8;
112 pub const PREF_LT: Field = 8..12;
114 pub const PREF_RESERVED: Field = 12..16;
116 pub const PREFIX: Field = 16..32;
118
119 pub const REDIRECTED_RESERVED: Field = 2..8;
132 pub const REDIR_MIN_SZ: usize = 48;
133
134 pub const MTU: Field = 4..8;
143}
144
145impl<T: AsRef<[u8]>> NdiscOption<T> {
147 pub const fn new_unchecked(buffer: T) -> NdiscOption<T> {
149 NdiscOption { buffer }
150 }
151
152 pub fn new_checked(buffer: T) -> Result<NdiscOption<T>> {
157 let opt = Self::new_unchecked(buffer);
158 opt.check_len()?;
159
160 if opt.data_len() == 0 {
162 return Err(Error);
163 }
164
165 Ok(opt)
166 }
167
168 pub fn check_len(&self) -> Result<()> {
175 let data = self.buffer.as_ref();
176 let len = data.len();
177
178 if len < field::MIN_OPT_LEN {
179 Err(Error)
180 } else {
181 let data_range = field::DATA(data[field::LENGTH]);
182 if len < data_range.end {
183 Err(Error)
184 } else {
185 match self.option_type() {
186 Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()),
187 Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()),
188 Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()),
189 Type::Unknown(_) => Ok(()),
190 _ => Err(Error),
191 }
192 }
193 }
194 }
195
196 pub fn into_inner(self) -> T {
198 self.buffer
199 }
200
201 #[inline]
203 pub fn option_type(&self) -> Type {
204 let data = self.buffer.as_ref();
205 Type::from(data[field::TYPE])
206 }
207
208 #[inline]
210 pub fn data_len(&self) -> u8 {
211 let data = self.buffer.as_ref();
212 data[field::LENGTH]
213 }
214}
215
216impl<T: AsRef<[u8]>> NdiscOption<T> {
218 #[inline]
220 pub fn link_layer_addr(&self) -> RawHardwareAddress {
221 let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2);
222 let data = self.buffer.as_ref();
223 RawHardwareAddress::from_bytes(&data[2..len + 2])
224 }
225}
226
227impl<T: AsRef<[u8]>> NdiscOption<T> {
229 #[inline]
231 pub fn mtu(&self) -> u32 {
232 let data = self.buffer.as_ref();
233 NetworkEndian::read_u32(&data[field::MTU])
234 }
235}
236
237impl<T: AsRef<[u8]>> NdiscOption<T> {
239 #[inline]
241 pub fn prefix_len(&self) -> u8 {
242 self.buffer.as_ref()[field::PREFIX_LEN]
243 }
244
245 #[inline]
247 pub fn prefix_flags(&self) -> PrefixInfoFlags {
248 PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS])
249 }
250
251 #[inline]
253 pub fn valid_lifetime(&self) -> Duration {
254 let data = self.buffer.as_ref();
255 Duration::from_secs(NetworkEndian::read_u32(&data[field::VALID_LT]) as u64)
256 }
257
258 #[inline]
260 pub fn preferred_lifetime(&self) -> Duration {
261 let data = self.buffer.as_ref();
262 Duration::from_secs(NetworkEndian::read_u32(&data[field::PREF_LT]) as u64)
263 }
264
265 #[inline]
267 pub fn prefix(&self) -> Ipv6Address {
268 let data = self.buffer.as_ref();
269 Ipv6Address::from_octets(data[field::PREFIX].try_into().unwrap())
270 }
271}
272
273impl<'a, T: AsRef<[u8]> + ?Sized> NdiscOption<&'a T> {
274 #[inline]
276 pub fn data(&self) -> &'a [u8] {
277 let len = self.data_len();
278 let data = self.buffer.as_ref();
279 &data[field::DATA(len)]
280 }
281}
282
283impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
285 #[inline]
287 pub fn set_option_type(&mut self, value: Type) {
288 let data = self.buffer.as_mut();
289 data[field::TYPE] = value.into();
290 }
291
292 #[inline]
294 pub fn set_data_len(&mut self, value: u8) {
295 let data = self.buffer.as_mut();
296 data[field::LENGTH] = value;
297 }
298}
299
300impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
302 #[inline]
304 pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) {
305 let data = self.buffer.as_mut();
306 data[2..2 + addr.len()].copy_from_slice(addr.as_bytes())
307 }
308}
309
310impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
312 #[inline]
314 pub fn set_mtu(&mut self, value: u32) {
315 let data = self.buffer.as_mut();
316 NetworkEndian::write_u32(&mut data[field::MTU], value);
317 }
318}
319
320impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
322 #[inline]
324 pub fn set_prefix_len(&mut self, value: u8) {
325 self.buffer.as_mut()[field::PREFIX_LEN] = value;
326 }
327
328 #[inline]
330 pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) {
331 self.buffer.as_mut()[field::FLAGS] = flags.bits();
332 }
333
334 #[inline]
336 pub fn set_valid_lifetime(&mut self, time: Duration) {
337 let data = self.buffer.as_mut();
338 NetworkEndian::write_u32(&mut data[field::VALID_LT], time.secs() as u32);
339 }
340
341 #[inline]
343 pub fn set_preferred_lifetime(&mut self, time: Duration) {
344 let data = self.buffer.as_mut();
345 NetworkEndian::write_u32(&mut data[field::PREF_LT], time.secs() as u32);
346 }
347
348 #[inline]
350 pub fn clear_prefix_reserved(&mut self) {
351 let data = self.buffer.as_mut();
352 NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0);
353 }
354
355 #[inline]
357 pub fn set_prefix(&mut self, addr: Ipv6Address) {
358 let data = self.buffer.as_mut();
359 data[field::PREFIX].copy_from_slice(&addr.octets());
360 }
361}
362
363impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
365 #[inline]
367 pub fn clear_redirected_reserved(&mut self) {
368 let data = self.buffer.as_mut();
369 data[field::REDIRECTED_RESERVED].fill_with(|| 0);
370 }
371}
372
373impl<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&mut T> {
374 #[inline]
376 pub fn data_mut(&mut self) -> &mut [u8] {
377 let len = self.data_len();
378 let data = self.buffer.as_mut();
379 &mut data[field::DATA(len)]
380 }
381}
382
383impl<T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&T> {
384 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
385 match Repr::parse(self) {
386 Ok(repr) => write!(f, "{repr}"),
387 Err(err) => {
388 write!(f, "NDISC Option ({err})")?;
389 Ok(())
390 }
391 }
392 }
393}
394
395#[derive(Debug, PartialEq, Eq, Clone, Copy)]
396#[cfg_attr(feature = "defmt", derive(defmt::Format))]
397pub struct PrefixInformation {
398 pub prefix_len: u8,
399 pub flags: PrefixInfoFlags,
400 pub valid_lifetime: Duration,
401 pub preferred_lifetime: Duration,
402 pub prefix: Ipv6Address,
403}
404
405impl PrefixInformation {
406 pub fn is_valid_prefix_info(&self) -> bool {
409 self.flags.contains(PrefixInfoFlags::ADDRCONF)
410 && !self.prefix.is_link_local()
411 && self.preferred_lifetime <= self.valid_lifetime
412 }
413}
414
415#[derive(Debug, PartialEq, Eq, Clone, Copy)]
416#[cfg_attr(feature = "defmt", derive(defmt::Format))]
417pub struct RedirectedHeader<'a> {
418 pub header: Ipv6Repr,
419 pub data: &'a [u8],
420}
421
422#[derive(Debug, PartialEq, Eq, Clone, Copy)]
424#[cfg_attr(feature = "defmt", derive(defmt::Format))]
425pub enum Repr<'a> {
426 SourceLinkLayerAddr(RawHardwareAddress),
427 TargetLinkLayerAddr(RawHardwareAddress),
428 PrefixInformation(PrefixInformation),
429 RedirectedHeader(RedirectedHeader<'a>),
430 Mtu(u32),
431 Unknown {
432 type_: u8,
433 length: u8,
434 data: &'a [u8],
435 },
436}
437
438impl<'a> Repr<'a> {
439 pub fn parse<T>(opt: &NdiscOption<&'a T>) -> Result<Repr<'a>>
441 where
442 T: AsRef<[u8]> + ?Sized,
443 {
444 opt.check_len()?;
445
446 match opt.option_type() {
447 Type::SourceLinkLayerAddr => {
448 if opt.data_len() >= 1 {
449 Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr()))
450 } else {
451 Err(Error)
452 }
453 }
454 Type::TargetLinkLayerAddr => {
455 if opt.data_len() >= 1 {
456 Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr()))
457 } else {
458 Err(Error)
459 }
460 }
461 Type::PrefixInformation => {
462 if opt.data_len() == 4 {
463 Ok(Repr::PrefixInformation(PrefixInformation {
464 prefix_len: opt.prefix_len(),
465 flags: opt.prefix_flags(),
466 valid_lifetime: opt.valid_lifetime(),
467 preferred_lifetime: opt.preferred_lifetime(),
468 prefix: opt.prefix(),
469 }))
470 } else {
471 Err(Error)
472 }
473 }
474 Type::RedirectedHeader => {
475 if opt.data_len() < 6 {
479 Err(Error)
480 } else {
481 let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..];
482
483 let ip_packet = Ipv6Packet::new_checked(redirected_packet)?;
484 let ip_repr = Ipv6Repr::parse(&ip_packet)?;
485
486 Ok(Repr::RedirectedHeader(RedirectedHeader {
487 header: ip_repr,
488 data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len],
489 }))
490 }
491 }
492 Type::Mtu => {
493 if opt.data_len() == 1 {
494 Ok(Repr::Mtu(opt.mtu()))
495 } else {
496 Err(Error)
497 }
498 }
499 Type::Unknown(id) => {
500 if opt.data_len() != 0 {
502 Ok(Repr::Unknown {
503 type_: id,
504 length: opt.data_len(),
505 data: opt.data(),
506 })
507 } else {
508 Err(Error)
509 }
510 }
511 }
512 }
513
514 pub const fn buffer_len(&self) -> usize {
516 match self {
517 &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
518 let len = 2 + addr.len();
519 len.div_ceil(8) * 8
521 }
522 &Repr::PrefixInformation(_) => field::PREFIX.end,
523 &Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
524 (8 + header.buffer_len() + data.len()).div_ceil(8) * 8
525 }
526 &Repr::Mtu(_) => field::MTU.end,
527 &Repr::Unknown { length, .. } => field::DATA(length).end,
528 }
529 }
530
531 pub fn emit<T>(&self, opt: &mut NdiscOption<&'a mut T>)
533 where
534 T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
535 {
536 match *self {
537 Repr::SourceLinkLayerAddr(addr) => {
538 opt.set_option_type(Type::SourceLinkLayerAddr);
539 let opt_len = addr.len() + 2;
540 opt.set_data_len(opt_len.div_ceil(8) as u8); opt.set_link_layer_addr(addr);
542 }
543 Repr::TargetLinkLayerAddr(addr) => {
544 opt.set_option_type(Type::TargetLinkLayerAddr);
545 let opt_len = addr.len() + 2;
546 opt.set_data_len(opt_len.div_ceil(8) as u8); opt.set_link_layer_addr(addr);
548 }
549 Repr::PrefixInformation(PrefixInformation {
550 prefix_len,
551 flags,
552 valid_lifetime,
553 preferred_lifetime,
554 prefix,
555 }) => {
556 opt.clear_prefix_reserved();
557 opt.set_option_type(Type::PrefixInformation);
558 opt.set_data_len(4);
559 opt.set_prefix_len(prefix_len);
560 opt.set_prefix_flags(flags);
561 opt.set_valid_lifetime(valid_lifetime);
562 opt.set_preferred_lifetime(preferred_lifetime);
563 opt.set_prefix(prefix);
564 }
565 Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
566 opt.clear_redirected_reserved();
569 opt.set_option_type(Type::RedirectedHeader);
570 opt.set_data_len((8 + header.buffer_len() + data.len()).div_ceil(8) as u8);
571 let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..];
572 let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet);
573 header.emit(&mut ip_packet);
574 ip_packet.payload_mut().copy_from_slice(data);
575 }
576 Repr::Mtu(mtu) => {
577 opt.set_option_type(Type::Mtu);
578 opt.set_data_len(1);
579 opt.set_mtu(mtu);
580 }
581 Repr::Unknown {
582 type_: id,
583 length,
584 data,
585 } => {
586 opt.set_option_type(Type::Unknown(id));
587 opt.set_data_len(length);
588 opt.data_mut().copy_from_slice(data);
589 }
590 }
591 }
592}
593
594impl<'a> fmt::Display for Repr<'a> {
595 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
596 write!(f, "NDISC Option: ")?;
597 match *self {
598 Repr::SourceLinkLayerAddr(addr) => {
599 write!(f, "SourceLinkLayer addr={addr}")
600 }
601 Repr::TargetLinkLayerAddr(addr) => {
602 write!(f, "TargetLinkLayer addr={addr}")
603 }
604 Repr::PrefixInformation(PrefixInformation {
605 prefix, prefix_len, ..
606 }) => {
607 write!(f, "PrefixInformation prefix={prefix}/{prefix_len}")
608 }
609 Repr::RedirectedHeader(RedirectedHeader { header, .. }) => {
610 write!(f, "RedirectedHeader header={header}")
611 }
612 Repr::Mtu(mtu) => {
613 write!(f, "MTU mtu={mtu}")
614 }
615 Repr::Unknown {
616 type_: id, length, ..
617 } => {
618 write!(f, "Unknown({id}) length={length}")
619 }
620 }
621 }
622}
623
624use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
625
626impl<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
627 fn pretty_print(
628 buffer: &dyn AsRef<[u8]>,
629 f: &mut fmt::Formatter,
630 indent: &mut PrettyIndent,
631 ) -> fmt::Result {
632 match NdiscOption::new_checked(buffer) {
633 Err(err) => write!(f, "{indent}({err})"),
634 Ok(ndisc) => match Repr::parse(&ndisc) {
635 Err(_) => Ok(()),
636 Ok(repr) => {
637 write!(f, "{indent}{repr}")
638 }
639 },
640 }
641 }
642}
643
644#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
645#[cfg(test)]
646mod test {
647 use super::Error;
648 use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type};
649 use crate::time::Duration;
650 use crate::wire::Ipv6Address;
651
652 #[cfg(feature = "medium-ethernet")]
653 use crate::wire::EthernetAddress;
654 #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
655 use crate::wire::Ieee802154Address;
656
657 static PREFIX_OPT_BYTES: [u8; 32] = [
658 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00,
659 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
660 0x00, 0x01,
661 ];
662
663 #[test]
664 fn test_deconstruct() {
665 let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]);
666 assert_eq!(opt.option_type(), Type::PrefixInformation);
667 assert_eq!(opt.data_len(), 4);
668 assert_eq!(opt.prefix_len(), 64);
669 assert_eq!(
670 opt.prefix_flags(),
671 PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF
672 );
673 assert_eq!(opt.valid_lifetime(), Duration::from_secs(900));
674 assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000));
675 assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
676 }
677
678 #[test]
679 fn test_construct() {
680 let mut bytes = [0x00; 32];
681 let mut opt = NdiscOption::new_unchecked(&mut bytes[..]);
682 opt.set_option_type(Type::PrefixInformation);
683 opt.set_data_len(4);
684 opt.set_prefix_len(64);
685 opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF);
686 opt.set_valid_lifetime(Duration::from_secs(900));
687 opt.set_preferred_lifetime(Duration::from_secs(1000));
688 opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
689 assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner());
690 }
691
692 #[test]
693 fn test_short_packet() {
694 assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error));
695 let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
696 assert_eq!(NdiscOption::new_checked(&bytes), Err(Error));
697 }
698
699 #[cfg(feature = "medium-ethernet")]
700 #[test]
701 fn test_repr_parse_link_layer_opt_ethernet() {
702 let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34];
703 let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]);
704 {
705 assert_eq!(
706 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
707 Ok(Repr::SourceLinkLayerAddr(addr.into()))
708 );
709 }
710 bytes[0] = 0x02;
711 {
712 assert_eq!(
713 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
714 Ok(Repr::TargetLinkLayerAddr(addr.into()))
715 );
716 }
717 }
718
719 #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
720 #[test]
721 fn test_repr_parse_link_layer_opt_ieee802154() {
722 let mut bytes = [
723 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
724 0x00, 0x00,
725 ];
726 let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
727 {
728 assert_eq!(
729 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
730 Ok(Repr::SourceLinkLayerAddr(addr.into()))
731 );
732 }
733 bytes[0] = 0x02;
734 {
735 assert_eq!(
736 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
737 Ok(Repr::TargetLinkLayerAddr(addr.into()))
738 );
739 }
740 }
741
742 #[test]
743 fn test_repr_parse_prefix_info() {
744 let repr = Repr::PrefixInformation(PrefixInformation {
745 prefix_len: 64,
746 flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
747 valid_lifetime: Duration::from_secs(900),
748 preferred_lifetime: Duration::from_secs(1000),
749 prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
750 });
751 assert_eq!(
752 Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)),
753 Ok(repr)
754 );
755 }
756
757 #[test]
758 fn test_repr_emit_prefix_info() {
759 let mut bytes = [0x2a; 32];
760 let repr = Repr::PrefixInformation(PrefixInformation {
761 prefix_len: 64,
762 flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
763 valid_lifetime: Duration::from_secs(900),
764 preferred_lifetime: Duration::from_secs(1000),
765 prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
766 });
767 let mut opt = NdiscOption::new_unchecked(&mut bytes);
768 repr.emit(&mut opt);
769 assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]);
770 }
771
772 #[test]
773 fn test_repr_parse_mtu() {
774 let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc];
775 assert_eq!(
776 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
777 Ok(Repr::Mtu(1500))
778 );
779 }
780}