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::checksum;
182pub use self::ip::{
183    Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint,
184    ListenEndpoint as IpListenEndpoint, Protocol as IpProtocol, Repr as IpRepr,
185    Version as IpVersion,
186};
187
188#[cfg(feature = "proto-ipv4")]
189pub use self::ipv4::{
190    Address as Ipv4Address, Cidr as Ipv4Cidr, HEADER_LEN as IPV4_HEADER_LEN, Key as Ipv4FragKey,
191    MIN_MTU as IPV4_MIN_MTU, MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS,
192    MULTICAST_ALL_SYSTEMS as IPV4_MULTICAST_ALL_SYSTEMS, Packet as Ipv4Packet, Repr as Ipv4Repr,
193};
194
195#[cfg(feature = "proto-ipv4")]
196pub(crate) use self::ipv4::AddressExt as Ipv4AddressExt;
197
198#[cfg(feature = "proto-ipv6")]
199pub use self::ipv6::{
200    Address as Ipv6Address, Cidr as Ipv6Cidr, HEADER_LEN as IPV6_HEADER_LEN,
201    LINK_LOCAL_ALL_MLDV2_ROUTERS as IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS,
202    LINK_LOCAL_ALL_NODES as IPV6_LINK_LOCAL_ALL_NODES,
203    LINK_LOCAL_ALL_ROUTERS as IPV6_LINK_LOCAL_ALL_ROUTERS,
204    LINK_LOCAL_ALL_RPL_NODES as IPV6_LINK_LOCAL_ALL_RPL_NODES, MIN_MTU as IPV6_MIN_MTU,
205    Packet as Ipv6Packet, Repr as Ipv6Repr,
206};
207#[cfg(feature = "proto-ipv6")]
208pub(crate) use self::ipv6::{AddressExt as Ipv6AddressExt, MulticastScope as Ipv6MulticastScope};
209
210#[cfg(feature = "proto-ipv6")]
211pub use self::ipv6option::{
212    FailureType as Ipv6OptionFailureType, Ipv6Option, Ipv6OptionsIterator, Repr as Ipv6OptionRepr,
213    RouterAlert as Ipv6OptionRouterAlert, Type as Ipv6OptionType,
214};
215
216#[cfg(feature = "proto-ipv6")]
217pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr};
218
219#[cfg(feature = "proto-ipv6")]
220pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
221
222#[cfg(feature = "proto-ipv6")]
223pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
224
225#[cfg(feature = "proto-ipv6")]
226pub use self::ipv6routing::{
227    Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr, Type as Ipv6RoutingType,
228};
229
230#[cfg(feature = "proto-ipv4")]
231pub use self::icmpv4::{
232    DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet,
233    ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr,
234    TimeExceeded as Icmpv4TimeExceeded,
235};
236
237#[cfg(feature = "proto-ipv4")]
238pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr};
239
240#[cfg(feature = "proto-ipv6")]
241pub use self::icmpv6::{
242    DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet,
243    ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded,
244};
245
246#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
247pub use self::icmp::Repr as IcmpRepr;
248
249#[cfg(all(
250    feature = "proto-ipv6",
251    any(feature = "medium-ethernet", feature = "medium-ieee802154")
252))]
253pub use self::ndisc::{
254    NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags,
255};
256
257#[cfg(all(
258    feature = "proto-ipv6",
259    any(feature = "medium-ethernet", feature = "medium-ieee802154")
260))]
261pub use self::ndiscoption::{
262    NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags,
263    PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader,
264    Repr as NdiscOptionRepr, Type as NdiscOptionType,
265};
266
267#[cfg(feature = "proto-ipv6")]
268pub use self::mld::{
269    AddressRecord as MldAddressRecord, AddressRecordRepr as MldAddressRecordRepr,
270    RecordType as MldRecordType, Repr as MldRepr,
271};
272
273pub use self::udp::{HEADER_LEN as UDP_HEADER_LEN, Packet as UdpPacket, Repr as UdpRepr};
274
275pub use self::tcp::{
276    Control as TcpControl, HEADER_LEN as TCP_HEADER_LEN, Packet as TcpPacket, Repr as TcpRepr,
277    SeqNumber as TcpSeqNumber, TcpOption, TcpTimestampGenerator, TcpTimestampRepr,
278};
279
280#[cfg(feature = "proto-dhcpv4")]
281pub use self::dhcpv4::{
282    CLIENT_PORT as DHCP_CLIENT_PORT, DhcpOption, DhcpOptionWriter, Flags as DhcpFlags,
283    MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, MessageType as DhcpMessageType,
284    OpCode as DhcpOpCode, Packet as DhcpPacket, Repr as DhcpRepr, SERVER_PORT as DHCP_SERVER_PORT,
285};
286
287#[cfg(feature = "proto-dns")]
288pub use self::dns::{
289    Flags as DnsFlags, Opcode as DnsOpcode, Packet as DnsPacket, Question as DnsQuestion,
290    Rcode as DnsRcode, Record as DnsRecord, RecordData as DnsRecordData, Repr as DnsRepr,
291    Type as DnsQueryType,
292};
293
294#[cfg(feature = "proto-ipsec-ah")]
295pub use self::ipsec_ah::{Packet as IpSecAuthHeaderPacket, Repr as IpSecAuthHeaderRepr};
296
297#[cfg(feature = "proto-ipsec-esp")]
298pub use self::ipsec_esp::{Packet as IpSecEspPacket, Repr as IpSecEspRepr};
299
300/// Parsing a packet failed.
301///
302/// Either it is malformed, or it is not supported by smoltcp.
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304#[cfg_attr(feature = "defmt", derive(defmt::Format))]
305pub struct Error;
306
307impl core::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}