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_bytes(&data[field::PREFIX])
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<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&'a 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<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a 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
405#[derive(Debug, PartialEq, Eq, Clone, Copy)]
406#[cfg_attr(feature = "defmt", derive(defmt::Format))]
407pub struct RedirectedHeader<'a> {
408    pub header: Ipv6Repr,
409    pub data: &'a [u8],
410}
411
412/// A high-level representation of an NDISC Option.
413#[derive(Debug, PartialEq, Eq, Clone, Copy)]
414#[cfg_attr(feature = "defmt", derive(defmt::Format))]
415pub enum Repr<'a> {
416    SourceLinkLayerAddr(RawHardwareAddress),
417    TargetLinkLayerAddr(RawHardwareAddress),
418    PrefixInformation(PrefixInformation),
419    RedirectedHeader(RedirectedHeader<'a>),
420    Mtu(u32),
421    Unknown {
422        type_: u8,
423        length: u8,
424        data: &'a [u8],
425    },
426}
427
428impl<'a> Repr<'a> {
429    /// Parse an NDISC Option and return a high-level representation.
430    pub fn parse<T>(opt: &NdiscOption<&'a T>) -> Result<Repr<'a>>
431    where
432        T: AsRef<[u8]> + ?Sized,
433    {
434        opt.check_len()?;
435
436        match opt.option_type() {
437            Type::SourceLinkLayerAddr => {
438                if opt.data_len() >= 1 {
439                    Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr()))
440                } else {
441                    Err(Error)
442                }
443            }
444            Type::TargetLinkLayerAddr => {
445                if opt.data_len() >= 1 {
446                    Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr()))
447                } else {
448                    Err(Error)
449                }
450            }
451            Type::PrefixInformation => {
452                if opt.data_len() == 4 {
453                    Ok(Repr::PrefixInformation(PrefixInformation {
454                        prefix_len: opt.prefix_len(),
455                        flags: opt.prefix_flags(),
456                        valid_lifetime: opt.valid_lifetime(),
457                        preferred_lifetime: opt.preferred_lifetime(),
458                        prefix: opt.prefix(),
459                    }))
460                } else {
461                    Err(Error)
462                }
463            }
464            Type::RedirectedHeader => {
465                // If the options data length is less than 6, the option
466                // does not have enough data to fill out the IP header
467                // and common option fields.
468                if opt.data_len() < 6 {
469                    Err(Error)
470                } else {
471                    let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..];
472
473                    let ip_packet = Ipv6Packet::new_checked(redirected_packet)?;
474                    let ip_repr = Ipv6Repr::parse(&ip_packet)?;
475
476                    Ok(Repr::RedirectedHeader(RedirectedHeader {
477                        header: ip_repr,
478                        data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len],
479                    }))
480                }
481            }
482            Type::Mtu => {
483                if opt.data_len() == 1 {
484                    Ok(Repr::Mtu(opt.mtu()))
485                } else {
486                    Err(Error)
487                }
488            }
489            Type::Unknown(id) => {
490                // A length of 0 is invalid.
491                if opt.data_len() != 0 {
492                    Ok(Repr::Unknown {
493                        type_: id,
494                        length: opt.data_len(),
495                        data: opt.data(),
496                    })
497                } else {
498                    Err(Error)
499                }
500            }
501        }
502    }
503
504    /// Return the length of a header that will be emitted from this high-level representation.
505    pub const fn buffer_len(&self) -> usize {
506        match self {
507            &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
508                let len = 2 + addr.len();
509                // Round up to next multiple of 8
510                (len + 7) / 8 * 8
511            }
512            &Repr::PrefixInformation(_) => field::PREFIX.end,
513            &Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
514                (8 + header.buffer_len() + data.len() + 7) / 8 * 8
515            }
516            &Repr::Mtu(_) => field::MTU.end,
517            &Repr::Unknown { length, .. } => field::DATA(length).end,
518        }
519    }
520
521    /// Emit a high-level representation into an NDISC Option.
522    pub fn emit<T>(&self, opt: &mut NdiscOption<&'a mut T>)
523    where
524        T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
525    {
526        match *self {
527            Repr::SourceLinkLayerAddr(addr) => {
528                opt.set_option_type(Type::SourceLinkLayerAddr);
529                let opt_len = addr.len() + 2;
530                opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
531                opt.set_link_layer_addr(addr);
532            }
533            Repr::TargetLinkLayerAddr(addr) => {
534                opt.set_option_type(Type::TargetLinkLayerAddr);
535                let opt_len = addr.len() + 2;
536                opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
537                opt.set_link_layer_addr(addr);
538            }
539            Repr::PrefixInformation(PrefixInformation {
540                prefix_len,
541                flags,
542                valid_lifetime,
543                preferred_lifetime,
544                prefix,
545            }) => {
546                opt.clear_prefix_reserved();
547                opt.set_option_type(Type::PrefixInformation);
548                opt.set_data_len(4);
549                opt.set_prefix_len(prefix_len);
550                opt.set_prefix_flags(flags);
551                opt.set_valid_lifetime(valid_lifetime);
552                opt.set_preferred_lifetime(preferred_lifetime);
553                opt.set_prefix(prefix);
554            }
555            Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
556                // TODO(thvdveld): I think we need to check if the data we are sending is not
557                // exceeding the MTU.
558                opt.clear_redirected_reserved();
559                opt.set_option_type(Type::RedirectedHeader);
560                opt.set_data_len((((8 + header.buffer_len() + data.len()) + 7) / 8) as u8);
561                let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..];
562                let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet);
563                header.emit(&mut ip_packet);
564                ip_packet.payload_mut().copy_from_slice(data);
565            }
566            Repr::Mtu(mtu) => {
567                opt.set_option_type(Type::Mtu);
568                opt.set_data_len(1);
569                opt.set_mtu(mtu);
570            }
571            Repr::Unknown {
572                type_: id,
573                length,
574                data,
575            } => {
576                opt.set_option_type(Type::Unknown(id));
577                opt.set_data_len(length);
578                opt.data_mut().copy_from_slice(data);
579            }
580        }
581    }
582}
583
584impl<'a> fmt::Display for Repr<'a> {
585    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586        write!(f, "NDISC Option: ")?;
587        match *self {
588            Repr::SourceLinkLayerAddr(addr) => {
589                write!(f, "SourceLinkLayer addr={addr}")
590            }
591            Repr::TargetLinkLayerAddr(addr) => {
592                write!(f, "TargetLinkLayer addr={addr}")
593            }
594            Repr::PrefixInformation(PrefixInformation {
595                prefix, prefix_len, ..
596            }) => {
597                write!(f, "PrefixInformation prefix={prefix}/{prefix_len}")
598            }
599            Repr::RedirectedHeader(RedirectedHeader { header, .. }) => {
600                write!(f, "RedirectedHeader header={header}")
601            }
602            Repr::Mtu(mtu) => {
603                write!(f, "MTU mtu={mtu}")
604            }
605            Repr::Unknown {
606                type_: id, length, ..
607            } => {
608                write!(f, "Unknown({id}) length={length}")
609            }
610        }
611    }
612}
613
614use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
615
616impl<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
617    fn pretty_print(
618        buffer: &dyn AsRef<[u8]>,
619        f: &mut fmt::Formatter,
620        indent: &mut PrettyIndent,
621    ) -> fmt::Result {
622        match NdiscOption::new_checked(buffer) {
623            Err(err) => write!(f, "{indent}({err})"),
624            Ok(ndisc) => match Repr::parse(&ndisc) {
625                Err(_) => Ok(()),
626                Ok(repr) => {
627                    write!(f, "{indent}{repr}")
628                }
629            },
630        }
631    }
632}
633
634#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
635#[cfg(test)]
636mod test {
637    use super::Error;
638    use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type};
639    use crate::time::Duration;
640    use crate::wire::Ipv6Address;
641
642    #[cfg(feature = "medium-ethernet")]
643    use crate::wire::EthernetAddress;
644    #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
645    use crate::wire::Ieee802154Address;
646
647    static PREFIX_OPT_BYTES: [u8; 32] = [
648        0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00,
649        0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
650        0x00, 0x01,
651    ];
652
653    #[test]
654    fn test_deconstruct() {
655        let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]);
656        assert_eq!(opt.option_type(), Type::PrefixInformation);
657        assert_eq!(opt.data_len(), 4);
658        assert_eq!(opt.prefix_len(), 64);
659        assert_eq!(
660            opt.prefix_flags(),
661            PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF
662        );
663        assert_eq!(opt.valid_lifetime(), Duration::from_secs(900));
664        assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000));
665        assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
666    }
667
668    #[test]
669    fn test_construct() {
670        let mut bytes = [0x00; 32];
671        let mut opt = NdiscOption::new_unchecked(&mut bytes[..]);
672        opt.set_option_type(Type::PrefixInformation);
673        opt.set_data_len(4);
674        opt.set_prefix_len(64);
675        opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF);
676        opt.set_valid_lifetime(Duration::from_secs(900));
677        opt.set_preferred_lifetime(Duration::from_secs(1000));
678        opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
679        assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner());
680    }
681
682    #[test]
683    fn test_short_packet() {
684        assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error));
685        let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
686        assert_eq!(NdiscOption::new_checked(&bytes), Err(Error));
687    }
688
689    #[cfg(feature = "medium-ethernet")]
690    #[test]
691    fn test_repr_parse_link_layer_opt_ethernet() {
692        let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34];
693        let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]);
694        {
695            assert_eq!(
696                Repr::parse(&NdiscOption::new_unchecked(&bytes)),
697                Ok(Repr::SourceLinkLayerAddr(addr.into()))
698            );
699        }
700        bytes[0] = 0x02;
701        {
702            assert_eq!(
703                Repr::parse(&NdiscOption::new_unchecked(&bytes)),
704                Ok(Repr::TargetLinkLayerAddr(addr.into()))
705            );
706        }
707    }
708
709    #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
710    #[test]
711    fn test_repr_parse_link_layer_opt_ieee802154() {
712        let mut bytes = [
713            0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
714            0x00, 0x00,
715        ];
716        let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
717        {
718            assert_eq!(
719                Repr::parse(&NdiscOption::new_unchecked(&bytes)),
720                Ok(Repr::SourceLinkLayerAddr(addr.into()))
721            );
722        }
723        bytes[0] = 0x02;
724        {
725            assert_eq!(
726                Repr::parse(&NdiscOption::new_unchecked(&bytes)),
727                Ok(Repr::TargetLinkLayerAddr(addr.into()))
728            );
729        }
730    }
731
732    #[test]
733    fn test_repr_parse_prefix_info() {
734        let repr = Repr::PrefixInformation(PrefixInformation {
735            prefix_len: 64,
736            flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
737            valid_lifetime: Duration::from_secs(900),
738            preferred_lifetime: Duration::from_secs(1000),
739            prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
740        });
741        assert_eq!(
742            Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)),
743            Ok(repr)
744        );
745    }
746
747    #[test]
748    fn test_repr_emit_prefix_info() {
749        let mut bytes = [0x2a; 32];
750        let repr = Repr::PrefixInformation(PrefixInformation {
751            prefix_len: 64,
752            flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
753            valid_lifetime: Duration::from_secs(900),
754            preferred_lifetime: Duration::from_secs(1000),
755            prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
756        });
757        let mut opt = NdiscOption::new_unchecked(&mut bytes);
758        repr.emit(&mut opt);
759        assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]);
760    }
761
762    #[test]
763    fn test_repr_parse_mtu() {
764        let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc];
765        assert_eq!(
766            Repr::parse(&NdiscOption::new_unchecked(&bytes)),
767            Ok(Repr::Mtu(1500))
768        );
769    }
770}