Skip to main content

smoltcp/wire/
mod.rs

1/*! Low-level packet access and construction.
2
3The `wire` module deals with the packet *representation*. It provides two levels
4of functionality.
5
6 * First, it provides functions to extract fields from sequences of octets,
7   and to insert fields into sequences of octets. This happens `Packet` family of
8   structures, e.g. [EthernetFrame] or [Ipv4Packet].
9 * Second, in cases where the space of valid field values is much smaller than the space
10   of possible field values, it provides a compact, high-level representation
11   of packet data that can be parsed from and emitted into a sequence of octets.
12   This happens through the `Repr` family of structs and enums, e.g. [ArpRepr] or [Ipv4Repr].
13
14[EthernetFrame]: struct.EthernetFrame.html
15[Ipv4Packet]: struct.Ipv4Packet.html
16[ArpRepr]: enum.ArpRepr.html
17[Ipv4Repr]: struct.Ipv4Repr.html
18
19The functions in the `wire` module are designed for use together with `-Cpanic=abort`.
20
21The `Packet` family of data structures guarantees that, if the `Packet::check_len()` method
22returned `Ok(())`, then no accessor or setter method will panic; however, the guarantee
23provided by `Packet::check_len()` may no longer hold after changing certain fields,
24which are listed in the documentation for the specific packet.
25
26The `Packet::new_checked` method is a shorthand for a combination of `Packet::new_unchecked`
27and `Packet::check_len`.
28When parsing untrusted input, it is *necessary* to use `Packet::new_checked()`;
29so long as the buffer is not modified, no accessor will fail.
30When emitting output, though, it is *incorrect* to use `Packet::new_checked()`;
31the length check is likely to succeed on a zeroed buffer, but fail on a buffer
32filled with data from a previous packet, such as when reusing buffers, resulting
33in nondeterministic panics with some network devices but not others.
34The buffer length for emission is not calculated by the `Packet` layer.
35
36In the `Repr` family of data structures, the `Repr::parse()` method never panics
37as long as `Packet::new_checked()` (or `Packet::check_len()`) has succeeded, and
38the `Repr::emit()` method never panics as long as the underlying buffer is exactly
39`Repr::buffer_len()` octets long.
40
41# Examples
42
43To emit an IP packet header into an octet buffer, and then parse it back:
44
45```rust
46# #[cfg(feature = "proto-ipv4")]
47# {
48use smoltcp::phy::ChecksumCapabilities;
49use smoltcp::wire::*;
50let repr = Ipv4Repr {
51    src_addr:    Ipv4Address::new(10, 0, 0, 1),
52    dst_addr:    Ipv4Address::new(10, 0, 0, 2),
53    next_header: IpProtocol::Tcp,
54    payload_len: 10,
55    hop_limit:   64,
56};
57let mut buffer = vec![0; repr.buffer_len() + repr.payload_len];
58{ // emission
59    let mut packet = Ipv4Packet::new_unchecked(&mut buffer);
60    repr.emit(&mut packet, &ChecksumCapabilities::default());
61}
62{ // parsing
63    let packet = Ipv4Packet::new_checked(&buffer)
64                            .expect("truncated packet");
65    let parsed = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default())
66                          .expect("malformed packet");
67    assert_eq!(repr, parsed);
68}
69# }
70```
71*/
72
73mod field {
74    pub type Field = ::core::ops::Range<usize>;
75    pub type Rest = ::core::ops::RangeFrom<usize>;
76}
77
78pub mod pretty_print;
79
80#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
81mod arp;
82#[cfg(feature = "proto-dhcpv4")]
83pub(crate) mod dhcpv4;
84#[cfg(feature = "proto-dns")]
85pub(crate) mod dns;
86#[cfg(feature = "medium-ethernet")]
87mod ethernet;
88#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
89mod icmp;
90#[cfg(feature = "proto-ipv4")]
91mod icmpv4;
92#[cfg(feature = "proto-ipv6")]
93mod icmpv6;
94#[cfg(feature = "medium-ieee802154")]
95pub mod ieee802154;
96#[cfg(feature = "proto-ipv4")]
97mod igmp;
98pub(crate) mod ip;
99#[cfg(feature = "proto-ipv4")]
100pub(crate) mod ipv4;
101#[cfg(feature = "proto-ipv6")]
102pub(crate) mod ipv6;
103#[cfg(feature = "proto-ipv6")]
104mod ipv6ext_header;
105#[cfg(feature = "proto-ipv6")]
106mod ipv6fragment;
107#[cfg(feature = "proto-ipv6")]
108mod ipv6hbh;
109#[cfg(feature = "proto-ipv6")]
110mod ipv6option;
111#[cfg(feature = "proto-ipv6")]
112mod ipv6routing;
113#[cfg(feature = "proto-ipv6")]
114mod mld;
115#[cfg(all(
116    feature = "proto-ipv6",
117    any(feature = "medium-ethernet", feature = "medium-ieee802154")
118))]
119mod ndisc;
120#[cfg(all(
121    feature = "proto-ipv6",
122    any(feature = "medium-ethernet", feature = "medium-ieee802154")
123))]
124mod ndiscoption;
125#[cfg(feature = "proto-rpl")]
126mod rpl;
127#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
128mod sixlowpan;
129mod tcp;
130mod udp;
131
132#[cfg(feature = "proto-ipsec-ah")]
133mod ipsec_ah;
134
135#[cfg(feature = "proto-ipsec-esp")]
136mod ipsec_esp;
137
138use core::fmt;
139
140use crate::phy::Medium;
141
142pub use self::pretty_print::PrettyPrinter;
143
144#[cfg(feature = "medium-ethernet")]
145pub use self::ethernet::{
146    Address as EthernetAddress, EtherType as EthernetProtocol, Frame as EthernetFrame,
147    HEADER_LEN as ETHERNET_HEADER_LEN, Repr as EthernetRepr,
148};
149
150#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
151pub use self::arp::{
152    Hardware as ArpHardware, Operation as ArpOperation, Packet as ArpPacket, Repr as ArpRepr,
153};
154
155#[cfg(feature = "proto-rpl")]
156pub use self::rpl::{
157    InstanceId as RplInstanceId, Repr as RplRepr, data::HopByHopOption as RplHopByHopRepr,
158    data::Packet as RplHopByHopPacket, options::Packet as RplOptionPacket,
159    options::Repr as RplOptionRepr,
160};
161
162#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
163pub use self::sixlowpan::{
164    AddressContext as SixlowpanAddressContext, NextHeader as SixlowpanNextHeader, SixlowpanPacket,
165    frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr},
166    iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr},
167    nhc::{
168        ExtHeaderId as SixlowpanExtHeaderId, ExtHeaderPacket as SixlowpanExtHeaderPacket,
169        ExtHeaderRepr as SixlowpanExtHeaderRepr, NhcPacket as SixlowpanNhcPacket,
170        UdpNhcPacket as SixlowpanUdpNhcPacket, UdpNhcRepr as SixlowpanUdpNhcRepr,
171    },
172};
173
174#[cfg(feature = "medium-ieee802154")]
175pub use self::ieee802154::{
176    Address as Ieee802154Address, AddressingMode as Ieee802154AddressingMode,
177    Frame as Ieee802154Frame, FrameType as Ieee802154FrameType,
178    FrameVersion as Ieee802154FrameVersion, Pan as Ieee802154Pan, Repr as Ieee802154Repr,
179};
180
181pub use self::ip::{
182    Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint,
183    ListenEndpoint as IpListenEndpoint, Protocol as IpProtocol, Repr as IpRepr,
184    Version as IpVersion,
185};
186
187#[cfg(feature = "proto-ipv4")]
188pub use self::ipv4::{
189    Address as Ipv4Address, Cidr as Ipv4Cidr, HEADER_LEN as IPV4_HEADER_LEN, Key as Ipv4FragKey,
190    MIN_MTU as IPV4_MIN_MTU, MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS,
191    MULTICAST_ALL_SYSTEMS as IPV4_MULTICAST_ALL_SYSTEMS, Packet as Ipv4Packet, Repr as Ipv4Repr,
192};
193
194#[cfg(feature = "proto-ipv4")]
195pub(crate) use self::ipv4::AddressExt as Ipv4AddressExt;
196
197#[cfg(feature = "proto-ipv6")]
198pub use self::ipv6::{
199    Address as Ipv6Address, Cidr as Ipv6Cidr, HEADER_LEN as IPV6_HEADER_LEN,
200    LINK_LOCAL_ALL_MLDV2_ROUTERS as IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS,
201    LINK_LOCAL_ALL_NODES as IPV6_LINK_LOCAL_ALL_NODES,
202    LINK_LOCAL_ALL_ROUTERS as IPV6_LINK_LOCAL_ALL_ROUTERS,
203    LINK_LOCAL_ALL_RPL_NODES as IPV6_LINK_LOCAL_ALL_RPL_NODES, MIN_MTU as IPV6_MIN_MTU,
204    Packet as Ipv6Packet, Repr as Ipv6Repr,
205};
206#[cfg(feature = "proto-ipv6")]
207pub(crate) use self::ipv6::{AddressExt as Ipv6AddressExt, MulticastScope as Ipv6MulticastScope};
208
209#[cfg(feature = "proto-ipv6")]
210pub use self::ipv6option::{
211    FailureType as Ipv6OptionFailureType, Ipv6Option, Ipv6OptionsIterator, Repr as Ipv6OptionRepr,
212    RouterAlert as Ipv6OptionRouterAlert, Type as Ipv6OptionType,
213};
214
215#[cfg(feature = "proto-ipv6")]
216pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr};
217
218#[cfg(feature = "proto-ipv6")]
219pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
220
221#[cfg(feature = "proto-ipv6")]
222pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
223
224#[cfg(feature = "proto-ipv6")]
225pub use self::ipv6routing::{
226    Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr, Type as Ipv6RoutingType,
227};
228
229#[cfg(feature = "proto-ipv4")]
230pub use self::icmpv4::{
231    DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet,
232    ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr,
233    TimeExceeded as Icmpv4TimeExceeded,
234};
235
236#[cfg(feature = "proto-ipv4")]
237pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr};
238
239#[cfg(feature = "proto-ipv6")]
240pub use self::icmpv6::{
241    DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet,
242    ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded,
243};
244
245#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
246pub use self::icmp::Repr as IcmpRepr;
247
248#[cfg(all(
249    feature = "proto-ipv6",
250    any(feature = "medium-ethernet", feature = "medium-ieee802154")
251))]
252pub use self::ndisc::{
253    NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags,
254};
255
256#[cfg(all(
257    feature = "proto-ipv6",
258    any(feature = "medium-ethernet", feature = "medium-ieee802154")
259))]
260pub use self::ndiscoption::{
261    NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags,
262    PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader,
263    Repr as NdiscOptionRepr, Type as NdiscOptionType,
264};
265
266#[cfg(feature = "proto-ipv6")]
267pub use self::mld::{
268    AddressRecord as MldAddressRecord, AddressRecordRepr as MldAddressRecordRepr,
269    RecordType as MldRecordType, Repr as MldRepr,
270};
271
272pub use self::udp::{HEADER_LEN as UDP_HEADER_LEN, Packet as UdpPacket, Repr as UdpRepr};
273
274pub use self::tcp::{
275    Control as TcpControl, HEADER_LEN as TCP_HEADER_LEN, Packet as TcpPacket, Repr as TcpRepr,
276    SeqNumber as TcpSeqNumber, TcpOption, TcpTimestampGenerator, TcpTimestampRepr,
277};
278
279#[cfg(feature = "proto-dhcpv4")]
280pub use self::dhcpv4::{
281    CLIENT_PORT as DHCP_CLIENT_PORT, DhcpOption, DhcpOptionWriter, Flags as DhcpFlags,
282    MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, MessageType as DhcpMessageType,
283    OpCode as DhcpOpCode, Packet as DhcpPacket, Repr as DhcpRepr, SERVER_PORT as DHCP_SERVER_PORT,
284};
285
286#[cfg(feature = "proto-dns")]
287pub use self::dns::{
288    Flags as DnsFlags, Opcode as DnsOpcode, Packet as DnsPacket, Question as DnsQuestion,
289    Rcode as DnsRcode, Record as DnsRecord, RecordData as DnsRecordData, Repr as DnsRepr,
290    Type as DnsQueryType,
291};
292
293#[cfg(feature = "proto-ipsec-ah")]
294pub use self::ipsec_ah::{Packet as IpSecAuthHeaderPacket, Repr as IpSecAuthHeaderRepr};
295
296#[cfg(feature = "proto-ipsec-esp")]
297pub use self::ipsec_esp::{Packet as IpSecEspPacket, Repr as IpSecEspRepr};
298
299/// Parsing a packet failed.
300///
301/// Either it is malformed, or it is not supported by smoltcp.
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303#[cfg_attr(feature = "defmt", derive(defmt::Format))]
304pub struct Error;
305
306#[cfg(feature = "std")]
307impl std::error::Error for Error {}
308
309impl fmt::Display for Error {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        write!(f, "wire::Error")
312    }
313}
314
315pub type Result<T> = core::result::Result<T, Error>;
316
317/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
318#[cfg(any(
319    feature = "medium-ip",
320    feature = "medium-ethernet",
321    feature = "medium-ieee802154"
322))]
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324#[cfg_attr(feature = "defmt", derive(defmt::Format))]
325pub enum HardwareAddress {
326    #[cfg(feature = "medium-ip")]
327    Ip,
328    #[cfg(feature = "medium-ethernet")]
329    Ethernet(EthernetAddress),
330    #[cfg(feature = "medium-ieee802154")]
331    Ieee802154(Ieee802154Address),
332}
333
334#[cfg(any(
335    feature = "medium-ip",
336    feature = "medium-ethernet",
337    feature = "medium-ieee802154"
338))]
339#[cfg(test)]
340impl Default for HardwareAddress {
341    fn default() -> Self {
342        #![allow(unreachable_code)]
343        #[cfg(feature = "medium-ethernet")]
344        {
345            return Self::Ethernet(EthernetAddress::default());
346        }
347        #[cfg(feature = "medium-ip")]
348        {
349            return Self::Ip;
350        }
351        #[cfg(feature = "medium-ieee802154")]
352        {
353            Self::Ieee802154(Ieee802154Address::default())
354        }
355    }
356}
357
358#[cfg(any(
359    feature = "medium-ip",
360    feature = "medium-ethernet",
361    feature = "medium-ieee802154"
362))]
363impl HardwareAddress {
364    pub const fn as_bytes(&self) -> &[u8] {
365        match self {
366            #[cfg(feature = "medium-ip")]
367            HardwareAddress::Ip => unreachable!(),
368            #[cfg(feature = "medium-ethernet")]
369            HardwareAddress::Ethernet(addr) => addr.as_bytes(),
370            #[cfg(feature = "medium-ieee802154")]
371            HardwareAddress::Ieee802154(addr) => addr.as_bytes(),
372        }
373    }
374
375    /// Query whether the address is an unicast address.
376    pub fn is_unicast(&self) -> bool {
377        match self {
378            #[cfg(feature = "medium-ip")]
379            HardwareAddress::Ip => unreachable!(),
380            #[cfg(feature = "medium-ethernet")]
381            HardwareAddress::Ethernet(addr) => addr.is_unicast(),
382            #[cfg(feature = "medium-ieee802154")]
383            HardwareAddress::Ieee802154(addr) => addr.is_unicast(),
384        }
385    }
386
387    /// Query whether the address is a broadcast address.
388    pub fn is_broadcast(&self) -> bool {
389        match self {
390            #[cfg(feature = "medium-ip")]
391            HardwareAddress::Ip => unreachable!(),
392            #[cfg(feature = "medium-ethernet")]
393            HardwareAddress::Ethernet(addr) => addr.is_broadcast(),
394            #[cfg(feature = "medium-ieee802154")]
395            HardwareAddress::Ieee802154(addr) => addr.is_broadcast(),
396        }
397    }
398
399    #[cfg(feature = "medium-ethernet")]
400    pub(crate) fn ethernet_or_panic(&self) -> EthernetAddress {
401        match self {
402            HardwareAddress::Ethernet(addr) => *addr,
403            #[allow(unreachable_patterns)]
404            _ => panic!("HardwareAddress is not Ethernet."),
405        }
406    }
407
408    #[cfg(feature = "medium-ieee802154")]
409    pub(crate) fn ieee802154_or_panic(&self) -> Ieee802154Address {
410        match self {
411            HardwareAddress::Ieee802154(addr) => *addr,
412            #[allow(unreachable_patterns)]
413            _ => panic!("HardwareAddress is not Ethernet."),
414        }
415    }
416
417    #[inline]
418    pub(crate) fn medium(&self) -> Medium {
419        match self {
420            #[cfg(feature = "medium-ip")]
421            HardwareAddress::Ip => Medium::Ip,
422            #[cfg(feature = "medium-ethernet")]
423            HardwareAddress::Ethernet(_) => Medium::Ethernet,
424            #[cfg(feature = "medium-ieee802154")]
425            HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
426        }
427    }
428
429    pub fn as_eui_64(&self) -> Option<[u8; 8]> {
430        match self {
431            #[cfg(feature = "medium-ip")]
432            HardwareAddress::Ip => None,
433            #[cfg(feature = "medium-ethernet")]
434            HardwareAddress::Ethernet(ethernet) => ethernet.as_eui_64(),
435            #[cfg(feature = "medium-ieee802154")]
436            HardwareAddress::Ieee802154(ieee802154) => ieee802154.as_eui_64(),
437        }
438    }
439}
440
441#[cfg(any(
442    feature = "medium-ip",
443    feature = "medium-ethernet",
444    feature = "medium-ieee802154"
445))]
446impl core::fmt::Display for HardwareAddress {
447    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
448        match self {
449            #[cfg(feature = "medium-ip")]
450            HardwareAddress::Ip => write!(f, "no hardware addr"),
451            #[cfg(feature = "medium-ethernet")]
452            HardwareAddress::Ethernet(addr) => write!(f, "{addr}"),
453            #[cfg(feature = "medium-ieee802154")]
454            HardwareAddress::Ieee802154(addr) => write!(f, "{addr}"),
455        }
456    }
457}
458
459#[cfg(feature = "medium-ethernet")]
460impl From<EthernetAddress> for HardwareAddress {
461    fn from(addr: EthernetAddress) -> Self {
462        HardwareAddress::Ethernet(addr)
463    }
464}
465
466#[cfg(feature = "medium-ieee802154")]
467impl From<Ieee802154Address> for HardwareAddress {
468    fn from(addr: Ieee802154Address) -> Self {
469        HardwareAddress::Ieee802154(addr)
470    }
471}
472
473#[cfg(not(feature = "medium-ieee802154"))]
474pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6;
475#[cfg(feature = "medium-ieee802154")]
476pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8;
477
478/// Unparsed hardware address.
479///
480/// Used to make NDISC parsing agnostic of the hardware medium in use.
481#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
482#[derive(Debug, PartialEq, Eq, Clone, Copy)]
483#[cfg_attr(feature = "defmt", derive(defmt::Format))]
484pub struct RawHardwareAddress {
485    len: u8,
486    data: [u8; MAX_HARDWARE_ADDRESS_LEN],
487}
488
489#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
490impl RawHardwareAddress {
491    /// Create a new `RawHardwareAddress` from a byte slice.
492    ///
493    /// # Panics
494    /// Panics if `addr.len() > MAX_HARDWARE_ADDRESS_LEN`.
495    pub fn from_bytes(addr: &[u8]) -> Self {
496        let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN];
497        data[..addr.len()].copy_from_slice(addr);
498
499        Self {
500            len: addr.len() as u8,
501            data,
502        }
503    }
504
505    pub fn as_bytes(&self) -> &[u8] {
506        &self.data[..self.len as usize]
507    }
508
509    pub const fn len(&self) -> usize {
510        self.len as usize
511    }
512
513    pub const fn is_empty(&self) -> bool {
514        self.len == 0
515    }
516
517    pub fn parse(&self, medium: Medium) -> Result<HardwareAddress> {
518        match medium {
519            #[cfg(feature = "medium-ethernet")]
520            Medium::Ethernet => {
521                if self.len() != 6 {
522                    return Err(Error);
523                }
524                Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes(
525                    self.as_bytes(),
526                )))
527            }
528            #[cfg(feature = "medium-ieee802154")]
529            Medium::Ieee802154 => {
530                if self.len() != 8 {
531                    return Err(Error);
532                }
533                Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
534                    self.as_bytes(),
535                )))
536            }
537            #[cfg(feature = "medium-ip")]
538            Medium::Ip => unreachable!(),
539        }
540    }
541}
542
543#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
544impl core::fmt::Display for RawHardwareAddress {
545    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
546        for (i, &b) in self.as_bytes().iter().enumerate() {
547            if i != 0 {
548                write!(f, ":")?;
549            }
550            write!(f, "{b:02x}")?;
551        }
552        Ok(())
553    }
554}
555
556#[cfg(feature = "medium-ethernet")]
557impl From<EthernetAddress> for RawHardwareAddress {
558    fn from(addr: EthernetAddress) -> Self {
559        Self::from_bytes(addr.as_bytes())
560    }
561}
562
563#[cfg(feature = "medium-ieee802154")]
564impl From<Ieee802154Address> for RawHardwareAddress {
565    fn from(addr: Ieee802154Address) -> Self {
566        Self::from_bytes(addr.as_bytes())
567    }
568}
569
570#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
571impl From<HardwareAddress> for RawHardwareAddress {
572    fn from(addr: HardwareAddress) -> Self {
573        Self::from_bytes(addr.as_bytes())
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use rstest::rstest;
581
582    #[rstest]
583    #[cfg(feature = "medium-ethernet")]
584    #[case((Medium::Ethernet, &[0u8; 6][..]), Ok(HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 0]))))]
585    #[cfg(feature = "medium-ethernet")]
586    #[case((Medium::Ethernet, &[1u8; 5][..]), Err(Error))]
587    #[cfg(feature = "medium-ethernet")]
588    #[case((Medium::Ethernet, &[1u8; 7][..]), Err(Error))]
589    #[cfg(feature = "medium-ieee802154")]
590    #[case((Medium::Ieee802154, &[0u8; 8][..]), Ok(HardwareAddress::Ieee802154(Ieee802154Address::Extended([0, 0, 0, 0, 0, 0, 0, 0]))))]
591    #[cfg(feature = "medium-ieee802154")]
592    #[case((Medium::Ieee802154, &[1u8; 2][..]), Err(Error))]
593    #[cfg(feature = "medium-ieee802154")]
594    #[case((Medium::Ieee802154, &[1u8; 1][..]), Err(Error))]
595    fn parse_hardware_address(
596        #[case] input: (Medium, &[u8]),
597        #[case] expected: Result<HardwareAddress>,
598    ) {
599        let (medium, input) = input;
600
601        // NOTE: we check the length since `RawHardwareAddress::parse()` panics if the length is
602        // invalid. MAX_HARDWARE_ADDRESS_LEN is based on the medium, and depending on the feature
603        // flags, it can be different.
604        if input.len() < MAX_HARDWARE_ADDRESS_LEN {
605            let raw = RawHardwareAddress::from_bytes(input);
606            assert_eq!(raw.parse(medium), expected);
607        }
608    }
609}