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    Repr as EthernetRepr, HEADER_LEN as ETHERNET_HEADER_LEN,
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    data::HopByHopOption as RplHopByHopRepr, data::Packet as RplHopByHopPacket,
158    options::Packet as RplOptionPacket, options::Repr as RplOptionRepr,
159    InstanceId as RplInstanceId, Repr as RplRepr,
160};
161
162#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
163pub use self::sixlowpan::{
164    frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr},
165    iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr},
166    nhc::{
167        ExtHeaderId as SixlowpanExtHeaderId, ExtHeaderPacket as SixlowpanExtHeaderPacket,
168        ExtHeaderRepr as SixlowpanExtHeaderRepr, NhcPacket as SixlowpanNhcPacket,
169        UdpNhcPacket as SixlowpanUdpNhcPacket, UdpNhcRepr as SixlowpanUdpNhcRepr,
170    },
171    AddressContext as SixlowpanAddressContext, NextHeader as SixlowpanNextHeader, SixlowpanPacket,
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, Key as Ipv4FragKey, Packet as Ipv4Packet,
190    Repr as Ipv4Repr, HEADER_LEN as IPV4_HEADER_LEN, MIN_MTU as IPV4_MIN_MTU,
191    MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS,
192    MULTICAST_ALL_SYSTEMS as IPV4_MULTICAST_ALL_SYSTEMS,
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, Packet as Ipv6Packet, Repr as Ipv6Repr,
201    HEADER_LEN as IPV6_HEADER_LEN,
202    LINK_LOCAL_ALL_MLDV2_ROUTERS as IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS,
203    LINK_LOCAL_ALL_NODES as IPV6_LINK_LOCAL_ALL_NODES,
204    LINK_LOCAL_ALL_ROUTERS as IPV6_LINK_LOCAL_ALL_ROUTERS,
205    LINK_LOCAL_ALL_RPL_NODES as IPV6_LINK_LOCAL_ALL_RPL_NODES, MIN_MTU as IPV6_MIN_MTU,
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::{Packet as UdpPacket, Repr as UdpRepr, HEADER_LEN as UDP_HEADER_LEN};
274
275pub use self::tcp::{
276    Control as TcpControl, Packet as TcpPacket, Repr as TcpRepr, SeqNumber as TcpSeqNumber,
277    TcpOption, TcpTimestampGenerator, TcpTimestampRepr, HEADER_LEN as TCP_HEADER_LEN,
278};
279
280#[cfg(feature = "proto-dhcpv4")]
281pub use self::dhcpv4::{
282    DhcpOption, DhcpOptionWriter, Flags as DhcpFlags, MessageType as DhcpMessageType,
283    OpCode as DhcpOpCode, Packet as DhcpPacket, Repr as DhcpRepr, CLIENT_PORT as DHCP_CLIENT_PORT,
284    MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, 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
307#[cfg(feature = "std")]
308impl std::error::Error for Error {}
309
310impl fmt::Display for Error {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        write!(f, "wire::Error")
313    }
314}
315
316pub type Result<T> = core::result::Result<T, Error>;
317
318/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
319#[cfg(any(
320    feature = "medium-ip",
321    feature = "medium-ethernet",
322    feature = "medium-ieee802154"
323))]
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325#[cfg_attr(feature = "defmt", derive(defmt::Format))]
326pub enum HardwareAddress {
327    #[cfg(feature = "medium-ip")]
328    Ip,
329    #[cfg(feature = "medium-ethernet")]
330    Ethernet(EthernetAddress),
331    #[cfg(feature = "medium-ieee802154")]
332    Ieee802154(Ieee802154Address),
333}
334
335#[cfg(any(
336    feature = "medium-ip",
337    feature = "medium-ethernet",
338    feature = "medium-ieee802154"
339))]
340#[cfg(test)]
341impl Default for HardwareAddress {
342    fn default() -> Self {
343        #![allow(unreachable_code)]
344        #[cfg(feature = "medium-ethernet")]
345        {
346            return Self::Ethernet(EthernetAddress::default());
347        }
348        #[cfg(feature = "medium-ip")]
349        {
350            return Self::Ip;
351        }
352        #[cfg(feature = "medium-ieee802154")]
353        {
354            Self::Ieee802154(Ieee802154Address::default())
355        }
356    }
357}
358
359#[cfg(any(
360    feature = "medium-ip",
361    feature = "medium-ethernet",
362    feature = "medium-ieee802154"
363))]
364impl HardwareAddress {
365    pub const fn as_bytes(&self) -> &[u8] {
366        match self {
367            #[cfg(feature = "medium-ip")]
368            HardwareAddress::Ip => unreachable!(),
369            #[cfg(feature = "medium-ethernet")]
370            HardwareAddress::Ethernet(addr) => addr.as_bytes(),
371            #[cfg(feature = "medium-ieee802154")]
372            HardwareAddress::Ieee802154(addr) => addr.as_bytes(),
373        }
374    }
375
376    /// Query whether the address is an unicast address.
377    pub fn is_unicast(&self) -> bool {
378        match self {
379            #[cfg(feature = "medium-ip")]
380            HardwareAddress::Ip => unreachable!(),
381            #[cfg(feature = "medium-ethernet")]
382            HardwareAddress::Ethernet(addr) => addr.is_unicast(),
383            #[cfg(feature = "medium-ieee802154")]
384            HardwareAddress::Ieee802154(addr) => addr.is_unicast(),
385        }
386    }
387
388    /// Query whether the address is a broadcast address.
389    pub fn is_broadcast(&self) -> bool {
390        match self {
391            #[cfg(feature = "medium-ip")]
392            HardwareAddress::Ip => unreachable!(),
393            #[cfg(feature = "medium-ethernet")]
394            HardwareAddress::Ethernet(addr) => addr.is_broadcast(),
395            #[cfg(feature = "medium-ieee802154")]
396            HardwareAddress::Ieee802154(addr) => addr.is_broadcast(),
397        }
398    }
399
400    #[cfg(feature = "medium-ethernet")]
401    pub(crate) fn ethernet_or_panic(&self) -> EthernetAddress {
402        match self {
403            HardwareAddress::Ethernet(addr) => *addr,
404            #[allow(unreachable_patterns)]
405            _ => panic!("HardwareAddress is not Ethernet."),
406        }
407    }
408
409    #[cfg(feature = "medium-ieee802154")]
410    pub(crate) fn ieee802154_or_panic(&self) -> Ieee802154Address {
411        match self {
412            HardwareAddress::Ieee802154(addr) => *addr,
413            #[allow(unreachable_patterns)]
414            _ => panic!("HardwareAddress is not Ethernet."),
415        }
416    }
417
418    #[inline]
419    pub(crate) fn medium(&self) -> Medium {
420        match self {
421            #[cfg(feature = "medium-ip")]
422            HardwareAddress::Ip => Medium::Ip,
423            #[cfg(feature = "medium-ethernet")]
424            HardwareAddress::Ethernet(_) => Medium::Ethernet,
425            #[cfg(feature = "medium-ieee802154")]
426            HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
427        }
428    }
429}
430
431#[cfg(any(
432    feature = "medium-ip",
433    feature = "medium-ethernet",
434    feature = "medium-ieee802154"
435))]
436impl core::fmt::Display for HardwareAddress {
437    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
438        match self {
439            #[cfg(feature = "medium-ip")]
440            HardwareAddress::Ip => write!(f, "no hardware addr"),
441            #[cfg(feature = "medium-ethernet")]
442            HardwareAddress::Ethernet(addr) => write!(f, "{addr}"),
443            #[cfg(feature = "medium-ieee802154")]
444            HardwareAddress::Ieee802154(addr) => write!(f, "{addr}"),
445        }
446    }
447}
448
449#[cfg(feature = "medium-ethernet")]
450impl From<EthernetAddress> for HardwareAddress {
451    fn from(addr: EthernetAddress) -> Self {
452        HardwareAddress::Ethernet(addr)
453    }
454}
455
456#[cfg(feature = "medium-ieee802154")]
457impl From<Ieee802154Address> for HardwareAddress {
458    fn from(addr: Ieee802154Address) -> Self {
459        HardwareAddress::Ieee802154(addr)
460    }
461}
462
463#[cfg(not(feature = "medium-ieee802154"))]
464pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6;
465#[cfg(feature = "medium-ieee802154")]
466pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8;
467
468/// Unparsed hardware address.
469///
470/// Used to make NDISC parsing agnostic of the hardware medium in use.
471#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
472#[derive(Debug, PartialEq, Eq, Clone, Copy)]
473#[cfg_attr(feature = "defmt", derive(defmt::Format))]
474pub struct RawHardwareAddress {
475    len: u8,
476    data: [u8; MAX_HARDWARE_ADDRESS_LEN],
477}
478
479#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
480impl RawHardwareAddress {
481    pub fn from_bytes(addr: &[u8]) -> Self {
482        let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN];
483        data[..addr.len()].copy_from_slice(addr);
484
485        Self {
486            len: addr.len() as u8,
487            data,
488        }
489    }
490
491    pub fn as_bytes(&self) -> &[u8] {
492        &self.data[..self.len as usize]
493    }
494
495    pub const fn len(&self) -> usize {
496        self.len as usize
497    }
498
499    pub const fn is_empty(&self) -> bool {
500        self.len == 0
501    }
502
503    pub fn parse(&self, medium: Medium) -> Result<HardwareAddress> {
504        match medium {
505            #[cfg(feature = "medium-ethernet")]
506            Medium::Ethernet => {
507                if self.len() < 6 {
508                    return Err(Error);
509                }
510                Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes(
511                    self.as_bytes(),
512                )))
513            }
514            #[cfg(feature = "medium-ieee802154")]
515            Medium::Ieee802154 => {
516                if self.len() < 8 {
517                    return Err(Error);
518                }
519                Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
520                    self.as_bytes(),
521                )))
522            }
523            #[cfg(feature = "medium-ip")]
524            Medium::Ip => unreachable!(),
525        }
526    }
527}
528
529#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
530impl core::fmt::Display for RawHardwareAddress {
531    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
532        for (i, &b) in self.as_bytes().iter().enumerate() {
533            if i != 0 {
534                write!(f, ":")?;
535            }
536            write!(f, "{b:02x}")?;
537        }
538        Ok(())
539    }
540}
541
542#[cfg(feature = "medium-ethernet")]
543impl From<EthernetAddress> for RawHardwareAddress {
544    fn from(addr: EthernetAddress) -> Self {
545        Self::from_bytes(addr.as_bytes())
546    }
547}
548
549#[cfg(feature = "medium-ieee802154")]
550impl From<Ieee802154Address> for RawHardwareAddress {
551    fn from(addr: Ieee802154Address) -> Self {
552        Self::from_bytes(addr.as_bytes())
553    }
554}
555
556#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
557impl From<HardwareAddress> for RawHardwareAddress {
558    fn from(addr: HardwareAddress) -> Self {
559        Self::from_bytes(addr.as_bytes())
560    }
561}