Skip to main content

smoltcp/wire/
ndiscoption.rs

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    /// NDISC Option Type
13    pub enum Type(u8) {
14        /// Source Link-layer Address
15        SourceLinkLayerAddr = 0x1,
16        /// Target Link-layer Address
17        TargetLinkLayerAddr = 0x2,
18        /// Prefix Information
19        PrefixInformation   = 0x3,
20        /// Redirected Header
21        RedirectedHeader    = 0x4,
22        /// MTU
23        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/// A read/write wrapper around an [NDISC Option].
49///
50/// [NDISC Option]: https://tools.ietf.org/html/rfc4861#section-4.6
51#[derive(Debug, PartialEq, Eq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53pub struct NdiscOption<T: AsRef<[u8]>> {
54    buffer: T,
55}
56
57// Format of an NDISC Option
58//
59// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60// |     Type      |    Length     |              ...              |
61// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62// ~                              ...                              ~
63// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
64//
65// See https://tools.ietf.org/html/rfc4861#section-4.6 for details.
66mod field {
67    #![allow(non_snake_case)]
68
69    use crate::wire::field::*;
70
71    // 8-bit identifier of the type of option.
72    pub const TYPE: usize = 0;
73    // 8-bit unsigned integer. Length of the option, in units of 8 octets.
74    pub const LENGTH: usize = 1;
75    // Minimum length of an option.
76    pub const MIN_OPT_LEN: usize = 8;
77    // Variable-length field. Option-Type-specific data.
78    pub const fn DATA(length: u8) -> Field {
79        2..length as usize * 8
80    }
81
82    // Source/Target Link-layer Option fields.
83    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
84    // |     Type      |    Length     |    Link-Layer Address ...
85    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
86
87    // Prefix Information Option fields.
88    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89    //  |     Type      |    Length     | Prefix Length |L|A| Reserved1 |
90    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91    //  |                         Valid Lifetime                        |
92    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93    //  |                       Preferred Lifetime                      |
94    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
95    //  |                           Reserved2                           |
96    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97    //  |                                                               |
98    //  +                                                               +
99    //  |                                                               |
100    //  +                            Prefix                             +
101    //  |                                                               |
102    //  +                                                               +
103    //  |                                                               |
104    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
105
106    // Prefix length.
107    pub const PREFIX_LEN: usize = 2;
108    // Flags field of prefix header.
109    pub const FLAGS: usize = 3;
110    // Valid lifetime.
111    pub const VALID_LT: Field = 4..8;
112    // Preferred lifetime.
113    pub const PREF_LT: Field = 8..12;
114    // Reserved bits
115    pub const PREF_RESERVED: Field = 12..16;
116    // Prefix
117    pub const PREFIX: Field = 16..32;
118
119    // Redirected Header Option fields.
120    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
121    //  |     Type      |    Length     |            Reserved           |
122    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
123    //  |                           Reserved                            |
124    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125    //  |                                                               |
126    //  ~                       IP header + data                        ~
127    //  |                                                               |
128    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
129
130    // Reserved bits.
131    pub const REDIRECTED_RESERVED: Field = 2..8;
132    pub const REDIR_MIN_SZ: usize = 48;
133
134    // MTU Option fields
135    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
136    //  |     Type      |    Length     |           Reserved            |
137    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
138    //  |                              MTU                              |
139    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
140
141    //  MTU
142    pub const MTU: Field = 4..8;
143}
144
145/// Core getter methods relevant to any type of NDISC option.
146impl<T: AsRef<[u8]>> NdiscOption<T> {
147    /// Create a raw octet buffer with an NDISC Option structure.
148    pub const fn new_unchecked(buffer: T) -> NdiscOption<T> {
149        NdiscOption { buffer }
150    }
151
152    /// Shorthand for a combination of [new_unchecked] and [check_len].
153    ///
154    /// [new_unchecked]: #method.new_unchecked
155    /// [check_len]: #method.check_len
156    pub fn new_checked(buffer: T) -> Result<NdiscOption<T>> {
157        let opt = Self::new_unchecked(buffer);
158        opt.check_len()?;
159
160        // A data length field of 0 is invalid.
161        if opt.data_len() == 0 {
162            return Err(Error);
163        }
164
165        Ok(opt)
166    }
167
168    /// Ensure that no accessor method will panic if called.
169    /// Returns `Err(Error)` if the buffer is too short.
170    ///
171    /// The result of this check is invalidated by calling [set_data_len].
172    ///
173    /// [set_data_len]: #method.set_data_len
174    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    /// Consume the NDISC option, returning the underlying buffer.
197    pub fn into_inner(self) -> T {
198        self.buffer
199    }
200
201    /// Return the option type.
202    #[inline]
203    pub fn option_type(&self) -> Type {
204        let data = self.buffer.as_ref();
205        Type::from(data[field::TYPE])
206    }
207
208    /// Return the length of the data.
209    #[inline]
210    pub fn data_len(&self) -> u8 {
211        let data = self.buffer.as_ref();
212        data[field::LENGTH]
213    }
214}
215
216/// Getter methods only relevant for Source/Target Link-layer Address options.
217impl<T: AsRef<[u8]>> NdiscOption<T> {
218    /// Return the Source/Target Link-layer Address.
219    #[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
227/// Getter methods only relevant for the MTU option.
228impl<T: AsRef<[u8]>> NdiscOption<T> {
229    /// Return the MTU value.
230    #[inline]
231    pub fn mtu(&self) -> u32 {
232        let data = self.buffer.as_ref();
233        NetworkEndian::read_u32(&data[field::MTU])
234    }
235}
236
237/// Getter methods only relevant for the Prefix Information option.
238impl<T: AsRef<[u8]>> NdiscOption<T> {
239    /// Return the prefix length.
240    #[inline]
241    pub fn prefix_len(&self) -> u8 {
242        self.buffer.as_ref()[field::PREFIX_LEN]
243    }
244
245    /// Return the prefix information flags.
246    #[inline]
247    pub fn prefix_flags(&self) -> PrefixInfoFlags {
248        PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS])
249    }
250
251    /// Return the valid lifetime of the prefix.
252    #[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    /// Return the preferred lifetime of the prefix.
259    #[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    /// Return the prefix.
266    #[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    /// Return the option data.
275    #[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
283/// Core setter methods relevant to any type of NDISC option.
284impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
285    /// Set the option type.
286    #[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    /// Set the option data length.
293    #[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
300/// Setter methods only relevant for Source/Target Link-layer Address options.
301impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
302    /// Set the Source/Target Link-layer Address.
303    #[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
310/// Setter methods only relevant for the MTU option.
311impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
312    /// Set the MTU value.
313    #[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
320/// Setter methods only relevant for the Prefix Information option.
321impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
322    /// Set the prefix length.
323    #[inline]
324    pub fn set_prefix_len(&mut self, value: u8) {
325        self.buffer.as_mut()[field::PREFIX_LEN] = value;
326    }
327
328    /// Set the prefix information flags.
329    #[inline]
330    pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) {
331        self.buffer.as_mut()[field::FLAGS] = flags.bits();
332    }
333
334    /// Set the valid lifetime of the prefix.
335    #[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    /// Set the preferred lifetime of the prefix.
342    #[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    /// Clear the reserved bits.
349    #[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    /// Set the prefix.
356    #[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
363/// Setter methods only relevant for the Redirected Header option.
364impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
365    /// Clear the reserved bits.
366    #[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    /// Return a mutable pointer to the option data.
375    #[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    /// Validates the prefix information option against check a, b, c in
407    /// <https://www.rfc-editor.org/rfc/rfc4862#section-5.5.3>
408    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/// A high-level representation of an NDISC Option.
423#[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    /// Parse an NDISC Option and return a high-level representation.
440    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 the options data length is less than 6, the option
476                // does not have enough data to fill out the IP header
477                // and common option fields.
478                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                // A length of 0 is invalid.
501                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    /// Return the length of a header that will be emitted from this high-level representation.
515    pub const fn buffer_len(&self) -> usize {
516        match self {
517            &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
518                let len = 2 + addr.len();
519                // Round up to next multiple of 8
520                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    /// Emit a high-level representation into an NDISC Option.
532    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); // round to next multiple of 8.
541                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); // round to next multiple of 8.
547                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                // TODO(thvdveld): I think we need to check if the data we are sending is not
567                // exceeding the MTU.
568                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}