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-igmp")]
97mod igmp;
98pub(crate) mod ip;
99#[cfg(feature = "proto-ipv4")]
100mod ipv4;
101#[cfg(feature = "proto-ipv6")]
102mod 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};
192
193#[cfg(feature = "proto-ipv6")]
194pub(crate) use self::ipv6::Scope as Ipv6AddressScope;
195#[cfg(feature = "proto-ipv6")]
196pub use self::ipv6::{
197    Address as Ipv6Address, Cidr as Ipv6Cidr, Packet as Ipv6Packet, Repr as Ipv6Repr,
198    HEADER_LEN as IPV6_HEADER_LEN, MIN_MTU as IPV6_MIN_MTU,
199};
200
201#[cfg(feature = "proto-ipv6")]
202pub use self::ipv6option::{
203    FailureType as Ipv6OptionFailureType, Ipv6Option, Ipv6OptionsIterator, Repr as Ipv6OptionRepr,
204    Type as Ipv6OptionType,
205};
206
207#[cfg(feature = "proto-ipv6")]
208pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr};
209
210#[cfg(feature = "proto-ipv6")]
211pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
212
213#[cfg(feature = "proto-ipv6")]
214pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
215
216#[cfg(feature = "proto-ipv6")]
217pub use self::ipv6routing::{
218    Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr, Type as Ipv6RoutingType,
219};
220
221#[cfg(feature = "proto-ipv4")]
222pub use self::icmpv4::{
223    DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet,
224    ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr,
225    TimeExceeded as Icmpv4TimeExceeded,
226};
227
228#[cfg(feature = "proto-igmp")]
229pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr};
230
231#[cfg(feature = "proto-ipv6")]
232pub use self::icmpv6::{
233    DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet,
234    ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded,
235};
236
237#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
238pub use self::icmp::Repr as IcmpRepr;
239
240#[cfg(all(
241    feature = "proto-ipv6",
242    any(feature = "medium-ethernet", feature = "medium-ieee802154")
243))]
244pub use self::ndisc::{
245    NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags,
246};
247
248#[cfg(all(
249    feature = "proto-ipv6",
250    any(feature = "medium-ethernet", feature = "medium-ieee802154")
251))]
252pub use self::ndiscoption::{
253    NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags,
254    PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader,
255    Repr as NdiscOptionRepr, Type as NdiscOptionType,
256};
257
258#[cfg(feature = "proto-ipv6")]
259pub use self::mld::{AddressRecord as MldAddressRecord, Repr as MldRepr};
260
261pub use self::udp::{Packet as UdpPacket, Repr as UdpRepr, HEADER_LEN as UDP_HEADER_LEN};
262
263pub use self::tcp::{
264    Control as TcpControl, Packet as TcpPacket, Repr as TcpRepr, SeqNumber as TcpSeqNumber,
265    TcpOption, HEADER_LEN as TCP_HEADER_LEN,
266};
267
268#[cfg(feature = "proto-dhcpv4")]
269pub use self::dhcpv4::{
270    DhcpOption, DhcpOptionWriter, MessageType as DhcpMessageType, Packet as DhcpPacket,
271    Repr as DhcpRepr, CLIENT_PORT as DHCP_CLIENT_PORT,
272    MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, SERVER_PORT as DHCP_SERVER_PORT,
273};
274
275#[cfg(feature = "proto-dns")]
276pub use self::dns::{
277    Flags as DnsFlags, Opcode as DnsOpcode, Packet as DnsPacket, Rcode as DnsRcode,
278    Repr as DnsRepr, Type as DnsQueryType,
279};
280
281#[cfg(feature = "proto-ipsec-ah")]
282pub use self::ipsec_ah::{Packet as IpSecAuthHeaderPacket, Repr as IpSecAuthHeaderRepr};
283
284#[cfg(feature = "proto-ipsec-esp")]
285pub use self::ipsec_esp::{Packet as IpSecEspPacket, Repr as IpSecEspRepr};
286
287/// Parsing a packet failed.
288///
289/// Either it is malformed, or it is not supported by smoltcp.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291#[cfg_attr(feature = "defmt", derive(defmt::Format))]
292pub struct Error;
293
294#[cfg(feature = "std")]
295impl std::error::Error for Error {}
296
297impl fmt::Display for Error {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        write!(f, "wire::Error")
300    }
301}
302
303pub type Result<T> = core::result::Result<T, Error>;
304
305/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
306#[cfg(any(
307    feature = "medium-ip",
308    feature = "medium-ethernet",
309    feature = "medium-ieee802154"
310))]
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312#[cfg_attr(feature = "defmt", derive(defmt::Format))]
313pub enum HardwareAddress {
314    #[cfg(feature = "medium-ip")]
315    Ip,
316    #[cfg(feature = "medium-ethernet")]
317    Ethernet(EthernetAddress),
318    #[cfg(feature = "medium-ieee802154")]
319    Ieee802154(Ieee802154Address),
320}
321
322#[cfg(any(
323    feature = "medium-ip",
324    feature = "medium-ethernet",
325    feature = "medium-ieee802154"
326))]
327impl HardwareAddress {
328    pub const fn as_bytes(&self) -> &[u8] {
329        match self {
330            #[cfg(feature = "medium-ip")]
331            HardwareAddress::Ip => unreachable!(),
332            #[cfg(feature = "medium-ethernet")]
333            HardwareAddress::Ethernet(addr) => addr.as_bytes(),
334            #[cfg(feature = "medium-ieee802154")]
335            HardwareAddress::Ieee802154(addr) => addr.as_bytes(),
336        }
337    }
338
339    /// Query whether the address is an unicast address.
340    pub fn is_unicast(&self) -> bool {
341        match self {
342            #[cfg(feature = "medium-ip")]
343            HardwareAddress::Ip => unreachable!(),
344            #[cfg(feature = "medium-ethernet")]
345            HardwareAddress::Ethernet(addr) => addr.is_unicast(),
346            #[cfg(feature = "medium-ieee802154")]
347            HardwareAddress::Ieee802154(addr) => addr.is_unicast(),
348        }
349    }
350
351    /// Query whether the address is a broadcast address.
352    pub fn is_broadcast(&self) -> bool {
353        match self {
354            #[cfg(feature = "medium-ip")]
355            HardwareAddress::Ip => unreachable!(),
356            #[cfg(feature = "medium-ethernet")]
357            HardwareAddress::Ethernet(addr) => addr.is_broadcast(),
358            #[cfg(feature = "medium-ieee802154")]
359            HardwareAddress::Ieee802154(addr) => addr.is_broadcast(),
360        }
361    }
362
363    #[cfg(feature = "medium-ethernet")]
364    pub(crate) fn ethernet_or_panic(&self) -> EthernetAddress {
365        match self {
366            HardwareAddress::Ethernet(addr) => *addr,
367            #[allow(unreachable_patterns)]
368            _ => panic!("HardwareAddress is not Ethernet."),
369        }
370    }
371
372    #[cfg(feature = "medium-ieee802154")]
373    pub(crate) fn ieee802154_or_panic(&self) -> Ieee802154Address {
374        match self {
375            HardwareAddress::Ieee802154(addr) => *addr,
376            #[allow(unreachable_patterns)]
377            _ => panic!("HardwareAddress is not Ethernet."),
378        }
379    }
380
381    #[inline]
382    pub(crate) fn medium(&self) -> Medium {
383        match self {
384            #[cfg(feature = "medium-ip")]
385            HardwareAddress::Ip => Medium::Ip,
386            #[cfg(feature = "medium-ethernet")]
387            HardwareAddress::Ethernet(_) => Medium::Ethernet,
388            #[cfg(feature = "medium-ieee802154")]
389            HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
390        }
391    }
392}
393
394#[cfg(any(
395    feature = "medium-ip",
396    feature = "medium-ethernet",
397    feature = "medium-ieee802154"
398))]
399impl core::fmt::Display for HardwareAddress {
400    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
401        match self {
402            #[cfg(feature = "medium-ip")]
403            HardwareAddress::Ip => write!(f, "no hardware addr"),
404            #[cfg(feature = "medium-ethernet")]
405            HardwareAddress::Ethernet(addr) => write!(f, "{addr}"),
406            #[cfg(feature = "medium-ieee802154")]
407            HardwareAddress::Ieee802154(addr) => write!(f, "{addr}"),
408        }
409    }
410}
411
412#[cfg(feature = "medium-ethernet")]
413impl From<EthernetAddress> for HardwareAddress {
414    fn from(addr: EthernetAddress) -> Self {
415        HardwareAddress::Ethernet(addr)
416    }
417}
418
419#[cfg(feature = "medium-ieee802154")]
420impl From<Ieee802154Address> for HardwareAddress {
421    fn from(addr: Ieee802154Address) -> Self {
422        HardwareAddress::Ieee802154(addr)
423    }
424}
425
426#[cfg(not(feature = "medium-ieee802154"))]
427pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6;
428#[cfg(feature = "medium-ieee802154")]
429pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8;
430
431/// Unparsed hardware address.
432///
433/// Used to make NDISC parsing agnostic of the hardware medium in use.
434#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
435#[derive(Debug, PartialEq, Eq, Clone, Copy)]
436#[cfg_attr(feature = "defmt", derive(defmt::Format))]
437pub struct RawHardwareAddress {
438    len: u8,
439    data: [u8; MAX_HARDWARE_ADDRESS_LEN],
440}
441
442#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
443impl RawHardwareAddress {
444    pub fn from_bytes(addr: &[u8]) -> Self {
445        let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN];
446        data[..addr.len()].copy_from_slice(addr);
447
448        Self {
449            len: addr.len() as u8,
450            data,
451        }
452    }
453
454    pub fn as_bytes(&self) -> &[u8] {
455        &self.data[..self.len as usize]
456    }
457
458    pub const fn len(&self) -> usize {
459        self.len as usize
460    }
461
462    pub const fn is_empty(&self) -> bool {
463        self.len == 0
464    }
465
466    pub fn parse(&self, medium: Medium) -> Result<HardwareAddress> {
467        match medium {
468            #[cfg(feature = "medium-ethernet")]
469            Medium::Ethernet => {
470                if self.len() < 6 {
471                    return Err(Error);
472                }
473                Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes(
474                    self.as_bytes(),
475                )))
476            }
477            #[cfg(feature = "medium-ieee802154")]
478            Medium::Ieee802154 => {
479                if self.len() < 8 {
480                    return Err(Error);
481                }
482                Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
483                    self.as_bytes(),
484                )))
485            }
486            #[cfg(feature = "medium-ip")]
487            Medium::Ip => unreachable!(),
488        }
489    }
490}
491
492#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
493impl core::fmt::Display for RawHardwareAddress {
494    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
495        for (i, &b) in self.as_bytes().iter().enumerate() {
496            if i != 0 {
497                write!(f, ":")?;
498            }
499            write!(f, "{b:02x}")?;
500        }
501        Ok(())
502    }
503}
504
505#[cfg(feature = "medium-ethernet")]
506impl From<EthernetAddress> for RawHardwareAddress {
507    fn from(addr: EthernetAddress) -> Self {
508        Self::from_bytes(addr.as_bytes())
509    }
510}
511
512#[cfg(feature = "medium-ieee802154")]
513impl From<Ieee802154Address> for RawHardwareAddress {
514    fn from(addr: Ieee802154Address) -> Self {
515        Self::from_bytes(addr.as_bytes())
516    }
517}
518
519#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
520impl From<HardwareAddress> for RawHardwareAddress {
521    fn from(addr: HardwareAddress) -> Self {
522        Self::from_bytes(addr.as_bytes())
523    }
524}