summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Yepishev <ryepishev@google.com>2024-02-29 20:48:38 +0000
committerRoman Yepishev <ryepishev@google.com>2024-02-29 20:48:38 +0000
commit22ce981acab277a6809f53856ee2733d282bea31 (patch)
tree802782cc8230ccf2ea9ca80a0b4cf2cb2a430159
parent051c16650263ae4e8305547e5a92d4fc441445e9 (diff)
parentfabac8ea399f5da14f27b41eb543e75542904b78 (diff)
downloadsmoltcp-22ce981acab277a6809f53856ee2733d282bea31.tar.gz
Merge remote-tracking branch 'origin/upstream'main
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--.gitignore3
-rw-r--r--CHANGELOG.md296
-rw-r--r--Cargo.toml466
-rw-r--r--Cargo.toml.orig305
l---------LICENSE1
-rw-r--r--LICENSE-0BSD.txt13
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_ZERO_BSD0
-rw-r--r--OWNERS2
-rw-r--r--README.md560
-rw-r--r--benches/bench.rs117
-rw-r--r--build.rs104
-rwxr-xr-xci.sh120
-rw-r--r--examples/benchmark.rs164
-rw-r--r--examples/client.rs118
-rw-r--r--examples/dhcp_client.rs94
-rw-r--r--examples/dns.rs92
-rw-r--r--examples/httpclient.rs123
-rw-r--r--examples/loopback.rs184
-rw-r--r--examples/multicast.rs129
-rw-r--r--examples/ping.rs281
-rw-r--r--examples/server.rs209
-rw-r--r--examples/sixlowpan.rs177
-rw-r--r--examples/sixlowpan_benchmark.rs235
-rw-r--r--examples/tcpdump.rs21
-rw-r--r--examples/utils.rs218
-rw-r--r--gen_config.py86
-rw-r--r--src/iface/fragmentation.rs506
-rw-r--r--src/iface/interface/ethernet.rs76
-rw-r--r--src/iface/interface/ieee802154.rs94
-rw-r--r--src/iface/interface/igmp.rs275
-rw-r--r--src/iface/interface/ipv4.rs445
-rw-r--r--src/iface/interface/ipv6.rs355
-rw-r--r--src/iface/interface/mod.rs1644
-rw-r--r--src/iface/interface/sixlowpan.rs922
-rw-r--r--src/iface/interface/tests/ipv4.rs968
-rw-r--r--src/iface/interface/tests/ipv6.rs988
-rw-r--r--src/iface/interface/tests/mod.rs235
-rw-r--r--src/iface/interface/tests/sixlowpan.rs434
-rw-r--r--src/iface/mod.rs24
-rw-r--r--src/iface/neighbor.rs306
-rw-r--r--src/iface/packet.rs234
-rw-r--r--src/iface/route.rs327
-rw-r--r--src/iface/rpl/consts.rs8
-rw-r--r--src/iface/rpl/lollipop.rs189
-rw-r--r--src/iface/rpl/mod.rs9
-rw-r--r--src/iface/rpl/of0.rs129
-rw-r--r--src/iface/rpl/parents.rs176
-rw-r--r--src/iface/rpl/rank.rs104
-rw-r--r--src/iface/rpl/relations.rs162
-rw-r--r--src/iface/rpl/trickle.rs266
-rw-r--r--src/iface/socket_meta.rs103
-rw-r--r--src/iface/socket_set.rs151
-rw-r--r--src/lib.rs180
-rw-r--r--src/macros.rs169
-rw-r--r--src/parsers.rs765
-rw-r--r--src/phy/fault_injector.rs330
-rw-r--r--src/phy/fuzz_injector.rs129
-rw-r--r--src/phy/loopback.rs88
-rw-r--r--src/phy/mod.rs398
-rw-r--r--src/phy/pcap_writer.rs268
-rw-r--r--src/phy/raw_socket.rs137
-rw-r--r--src/phy/sys/bpf.rs180
-rw-r--r--src/phy/sys/linux.rs26
-rw-r--r--src/phy/sys/mod.rs136
-rw-r--r--src/phy/sys/raw_socket.rs115
-rw-r--r--src/phy/sys/tuntap_interface.rs130
-rw-r--r--src/phy/tracer.rs189
-rw-r--r--src/phy/tuntap_interface.rs126
-rw-r--r--src/rand.rs40
-rw-r--r--src/socket/dhcpv4.rs1417
-rw-r--r--src/socket/dns.rs699
-rw-r--r--src/socket/icmp.rs1240
-rw-r--r--src/socket/mod.rs141
-rw-r--r--src/socket/raw.rs848
-rw-r--r--src/socket/tcp.rs7312
-rw-r--r--src/socket/udp.rs1030
-rw-r--r--src/socket/waker.rs33
-rw-r--r--src/storage/assembler.rs750
-rw-r--r--src/storage/mod.rs31
-rw-r--r--src/storage/packet_buffer.rs402
-rw-r--r--src/storage/ring_buffer.rs803
-rw-r--r--src/tests.rs148
-rw-r--r--src/time.rs460
-rw-r--r--src/wire/arp.rs458
-rw-r--r--src/wire/dhcpv4.rs1315
-rw-r--r--src/wire/dns.rs793
-rw-r--r--src/wire/ethernet.rs400
-rw-r--r--src/wire/icmp.rs25
-rw-r--r--src/wire/icmpv4.rs702
-rw-r--r--src/wire/icmpv6.rs1085
-rw-r--r--src/wire/ieee802154.rs1182
-rw-r--r--src/wire/igmp.rs445
-rw-r--r--src/wire/ip.rs998
-rw-r--r--src/wire/ipsec_ah.rs286
-rw-r--r--src/wire/ipsec_esp.rs177
-rw-r--r--src/wire/ipv4.rs1178
-rw-r--r--src/wire/ipv6.rs1449
-rw-r--r--src/wire/ipv6ext_header.rs305
-rw-r--r--src/wire/ipv6fragment.rs283
-rw-r--r--src/wire/ipv6hbh.rs176
-rw-r--r--src/wire/ipv6option.rs611
-rw-r--r--src/wire/ipv6routing.rs606
-rw-r--r--src/wire/mld.rs578
-rw-r--r--src/wire/mod.rs524
-rw-r--r--src/wire/ndisc.rs541
-rw-r--r--src/wire/ndiscoption.rs768
-rw-r--r--src/wire/pretty_print.rs126
-rw-r--r--src/wire/rpl.rs2721
-rw-r--r--src/wire/sixlowpan/frag.rs275
-rw-r--r--src/wire/sixlowpan/iphc.rs948
-rw-r--r--src/wire/sixlowpan/mod.rs371
-rw-r--r--src/wire/sixlowpan/nhc.rs890
-rw-r--r--src/wire/tcp.rs1331
-rw-r--r--src/wire/udp.rs482
-rw-r--r--utils/packet2pcap.rs74
117 files changed, 54097 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..4b22af8
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "ce420118efff83b47767389500ef1562f5074b55"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..41ca801
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/target
+Cargo.lock
+*.pcap
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4cf517e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,296 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+No unreleased changes yet.
+
+## [0.11.0] - 2023-12-23
+
+### Additions
+
+- wire/ipsec: add basic IPsec parsing/emitting ([#821](https://github.com/smoltcp-rs/smoltcp/pull/821)).
+- phy: add support for `TUNSETIFF` on MIPS, PPC and SPARC ([#839](https://github.com/smoltcp-rs/smoltcp/pull/839)).
+- socket/tcp: accept FIN on zero window ([#845](https://github.com/smoltcp-rs/smoltcp/pull/845)).
+- wire/ipv6: add `is_unique_local()` to IPv6 addresses ([#862](https://github.com/smoltcp-rs/smoltcp/pull/862)).
+- wire/ipv6: add `is_global_unicast()` to IPv6 addresses ([#864](https://github.com/smoltcp-rs/smoltcp/pull/864)).
+- iface/neigh: add `fill_with_expiration` ([#871](https://github.com/smoltcp-rs/smoltcp/pull/871)).
+
+### Fixes
+
+- icmpv6: truncate packet to MTU ([#807](https://github.com/smoltcp-rs/smoltcp/pull/807), [#808](https://github.com/smoltcp-rs/smoltcp/pull/810)).
+- wire/rpl: DAO-ACK DODAG ID was wrongly read ([#824](https://github.com/smoltcp-rs/smoltcp/pull/824)).
+- socket/tcp: don't panic when calling `listen` again on the same local endpoint ([#841](https://github.com/smoltcp-rs/smoltcp/pull/841)).
+- wire/dhcpv4: don't panic when parsing addresses with incorrect amount of bytes ([#843](https://github.com/smoltcp-rs/smoltcp/pull/843)).
+- iface/ndisc: prevent ndisc when the medium is IP ([#865](https://github.com/smoltcp-rs/smoltcp/pull/865)).
+- wire/ieee802154: better parsing of security fields. Correctly parse frame type (3 bits instead of 2 bits) ([#868](https://github.com/smoltcp-rs/smoltcp/pull/864)).
+- wire/ieee802154: better handle address fields for new frame version ([#870](https://github.com/smoltcp-rs/smoltcp/pull/870)).
+- iface/tcp: don't send TCP RST with unspecified addresses ([#867](https://github.com/smoltcp-rs/smoltcp/pull/867)).
+- iface: don't handle empty packets (this would panic when reading the IP version) ([#866](https://github.com/smoltcp-rs/smoltcp/pull/866)).
+- socket/dhcp: Add an upper bound to the renew/rebind timeout in `RetryConfig` ([#835](https://github.com/smoltcp-rs/smoltcp/pull/835)).
+
+### Changes
+
+- iface: rewrite `IpPacket` such that IPv6 packets can contain owned extension headers ([#802](https://github.com/smoltcp-rs/smoltcp/pull/802)).
+- iface: remove generic `T: [u8]` in functions. This reduced the server example by 10KB ([#810](https://github.com/smoltcp-rs/smoltcp/pull/810)).
+- SocketSet: add comment about using static lifetime for SocketSets with owned storage ([#813](https://github.com/smoltcp-rs/smoltcp/pull/813)).
+- phy/RawSocket: open raw socket with `O_NONBLOCK` ([#817](https://github.com/smoltcp-rs/smoltcp/pull/817)).
+- tests/rstest: use rstest for fixture based testing ([#823](https://github.com/smoltcp-rs/smoltcp/pull/823)).
+- docs/readme: update readme about IEEE802.15.4 and 6LoWPAN ([#826](https://github.com/smoltcp-rs/smoltcp/pull/826)).
+- wire/ipv6-hbh: IPv6 HBH has owned options instead of references ([#827](https://github.com/smoltcp-rs/smoltcp/pull/827)).
+- wire/sixlowpan: 6LoWPAN is split into multiple modules ([#828](https://github.com/smoltcp-rs/smoltcp/pull/828)).
+- sockets: match the behaviour of `peek_slice` and `recv_slice` ([#834](https://github.com/smoltcp-rs/smoltcp/pull/834)).
+- dependencies: update to headpless v0.8 ([#853](https://github.com/smoltcp-rs/smoltcp/pull/853)).
+- config: make `config` constants public ([#855](https://github.com/smoltcp-rs/smoltcp/pull/855)).
+- phy/ieee802154: clarify `mtu+=2` for IEEE802.15.4 ([#857](https://github.com/smoltcp-rs/smoltcp/pull/857)).
+- sockets: `recv_slice` returns `RcvError::Truncated` when the length of the slice is smaller than the data received by the socket ([#859](https://github.com/smoltcp-rs/smoltcp/pull/859)).
+- iface/ipv6: `get_source_address` uses [RFC 6724](https://www.rfc-editor.org/rfc/rfc6724) for address selection ([#864](https://github.com/smoltcp-rs/smoltcp/pull/864)).
+- pcap: use IEEE 802.15.4 without FCS for PCAP link types ([#874](https://github.com/smoltcp-rs/smoltcp/pull/874)).
+- iface: rename `IpPacket`/`Ipv4Packet`/`Ipv6Packet` to `Pacet`/`PacketV4`/`PacketV4`. This is to remove the ambiguity with `IpPacket` in `src/wire/` ([#873](https://github.com/smoltcp-rs/smoltcp/pull/873)).
+- wire/ndisc: rewrite parse function (3.1KiB -> 1.9KiB) ([#878](https://github.com/smoltcp-rs/smoltcp/pull/878))
+- iface: Check IPv6 address after processing HBH ([#861](https://github.com/smoltcp-rs/smoltcp/pull/861))
+
+## [0.10.0] - 2023-06-26
+
+- Add optional packet metadata. Allows tracking packets by ID across the whole stack, between the `Device` impl and sockets. One application is timestamping packets with the PHY's collaboration, allowing implementing PTP (#628)
+- Work-in-progress implementation of RPL (Routing Protocol for Low-Power and Lossy Networks), commonly used for IEEE 802.15.4 / 6LoWPAN networks. Wire is mostly complete, full functionality will be in 0.11 hopefully! (#627, #766, #767, #772, #773, #777, #790, #798, #804)
+- dhcp: Add support for rebinding (#744)
+
+- iface:
+ - add support for sending to subnet-local broadcast addrs (like 192.168.1.255). (#801)
+ - Creating an interface requires passing in the time. (#799)
+ - fix wrong payload length of first IPv4 fragment (#791, #792)
+ - Don't discard from unspecified IPv4 src addresses (#787)
+
+- tcp:
+ - do not count window updates as duplicate acks. (#748)
+ - consider segments partially overlapping the window as acceptable (#749)
+ - Perform a reset() after an abort() (#788)
+
+- 6lowpan:
+ - Hop-by-Hop Header compression (#765)
+ - Routing Header compression (#770)
+
+- wire:
+ - reexport DNS opcode, rcode, flag. (#763, #806)
+ - refactor IPv6 Extension Headers to make them more consistent and easier to parse. (#781)
+ - check length field of NDISC redirected head (#784)
+
+- Modify `hardware_addr` and `neighbor_cache` to be not `Option`, add `HardwareAddress::Ip` (#745)
+- Add file descriptor support for tuntap devices, needed for the Android VPN API. (#776)
+- implement Display and Error for error types (#750, #756, #757)
+- Better defmt for Instant, Duration and Ipv6Address (#754, #758)
+- Add Hash trait for enum_with_unknown macro (#755)
+
+## [0.9.1] - 2023-02-08
+
+- iface: make MulticastError public. (#747)
+- Fix parsing of ieee802154 link layer address for NDISC options (#746)
+
+## [0.9.0] - 2023-02-06
+
+- Minimum Supported Rust Version (MSRV) **bumped** from 1.56 to 1.65
+- Added DNS client support.
+ - Add DnsSocket (#465)
+ - Add support for one-shot mDNS resolution (#669)
+- Added support for packet fragmentation and reassembly, both for IPv4 and 6LoWPAN. (#591, #580, #624, #634, #645, #653, #684)
+- Major error handling overhaul.
+ - Previously, _smoltcp_ had a single `Error` enum that all methods returned. Now methods that can fail have their own error enums, with only the actual errors they can return. (#617, #667, #730)
+ - Consuming `phy::Device` tokens is now infallible.
+ - In the case of "buffer full", `phy::Device` implementations must return `None` from the `transmit`/`receive` methods. (Previously, they could either do that, or return tokens and then return `Error::Exhausted` when consuming them. The latter wasted computation since it'd make _smoltcp_ pointlessly spend effort preparing the packet, and is now disallowed).
+ - For all other phy errors, `phy::Device` implementations should drop the packet and handle the error themselves. (Either log it and forget it, or buffer/count it and offer methods to let the user retrieve the error queue/counts.) Returning the error to have it bubble up to `Interface::poll()` is no longer supported.
+- phy: the `trait Device` now uses Generic Associated Types (GAT) for the TX and RX tokens. The main impact of this is `Device` impls can now borrow data (because previously, the`for<'a> T: Device<'a>` bounds required to workaround the lack of GATs essentially implied `T: 'static`.) (#572)
+- iface: The `Interface` API has been significantly simplified and cleaned up.
+ - The builder has been removed (#736)
+ - SocketSet and Device are now borrowed in methods that need them, instead of owning them. (#619)
+ - `Interface` now owns the list of addresses (#719), routes, neighbor cache (#722), 6LoWPAN address contexts, and fragmentation buffers (#736) instead of borrowing them with `managed`.
+ - A new compile-time configuration mechanism has been added, to configure the size of the (now owned) buffers (#742)
+- iface: Change neighbor discovery timeout from 3s to 1s, to match Linux's behavior. (#620)
+- iface: Remove implicit sized bound on device generics (#679)
+- iface/6lowpan: Add address context information for resolving 6LoWPAN addresses (#687)
+- iface/6lowpan: fix incorrect SAM value in IPHC when address is not compressed (#630)
+- iface/6lowpan: packet parsing fuzz fixes (#636)
+- socket: Add send_with to udp, raw, and icmp sockets. These methods enable reserving a packet buffer with a greater size than you need, and then shrinking the size once you know it. (#625)
+- socket: Make `trait AnySocket` object-safe (#718)
+- socket/dhcpv4: add waker support (#623)
+- socket/dhcpv4: indicate new config if there's a packet buffer provided (#685)
+- socket/dhcpv4: Use renewal time from DHCP server ACK, if given (#683)
+- socket/dhcpv4: allow for extra configuration
+ - setting arbitrary options in the request. (#650)
+ - retrieving arbitrary options from the response. (#650)
+ - setting custom parameter request list. (#650)
+ - setting custom timing for retries. (#650)
+ - Allow specifying different server/client DHCP ports (#738)
+- socket/raw: Add `peek` and `peek_slice` methods (#734)
+- socket/raw: When sending packets, send the source IP address unmodified (it was previously replaced with the interface's address if it was unspecified). (#616)
+- socket/tcp: Do not reset socket-level settings, such as keepalive, on reset (#603)
+- socket/tcp: ensure we always accept the segment at offset=0 even if the assembler is full. (#735, #452)
+- socket/tcp: Refactored assembler, now more robust and faster (#726, #735)
+- socket/udp: accept packets with checksum field set to `0`, since that means the checksum is not computed (#632)
+- wire: make many functions const (#693)
+- wire/dhcpv4: remove Option enum (#656)
+- wire/dhcpv4: use heapless Vec for DNS server list (#678)
+- wire/icmpv4: add support for TimeExceeded packets (#609)
+- wire/ip: Remove `IpRepr::Unspecified`, `IpVersion::Unspecified`, `IpAddress::Unspecified` (#579, #616)
+- wire/ip: support parsing unspecified IPv6 IpEndpoints from string (like `[::]:12345`) (#732)
+- wire/ipv6: Make Public Ipv6RoutingType (#691)
+- wire/ndisc: do not error on unrecognized options. (#737)
+- Switch to Rust 2021 edition. (#729)
+- Remove obsolete Cargo feature `rust-1_28` (#725)
+
+## [0.8.2] - 2022-11-27
+
+- tcp: Fix return value of nagle_enable ([#642](https://github.com/smoltcp-rs/smoltcp/pull/642))
+- tcp: Only clear retransmit timer when all packets are acked ([#662](https://github.com/smoltcp-rs/smoltcp/pull/662))
+- tcp: Send incomplete fin packets even if nagle enabled ([#665](https://github.com/smoltcp-rs/smoltcp/pull/665))
+- phy: Fix mtu calculation for raw_socket ([#611](https://github.com/smoltcp-rs/smoltcp/pull/611))
+- wire: Fix ipv6 contains_addr function ([#605](https://github.com/smoltcp-rs/smoltcp/pull/605))
+
+## [0.8.1] - 2022-05-12
+
+- Remove unused `rand_core` dep. ([#589](https://github.com/smoltcp-rs/smoltcp/pull/589))
+- Use socklen_t instead of u32 for RawSocket bind() parameter. Fixes build on 32bit Android. ([#593](https://github.com/smoltcp-rs/smoltcp/pull/593))
+- Propagate phy::RawSocket send errors to caller ([#588](https://github.com/smoltcp-rs/smoltcp/pull/588))
+- Fix Interface set_hardware_addr, get_hardware_addr for ieee802154/6lowpan. ([#584](https://github.com/smoltcp-rs/smoltcp/pull/584))
+
+## [0.8.0] - 2021-12-11
+
+- Minimum Supported Rust Version (MSRV) **bumped** from 1.40 to 1.56
+- Add support for IEEE 802.15.4 + 6LoWPAN medium ([#469](https://github.com/smoltcp-rs/smoltcp/pull/469))
+- Add support for IP medium ([#401](https://github.com/smoltcp-rs/smoltcp/pull/401))
+- Add `defmt` logging support ([#455](https://github.com/smoltcp-rs/smoltcp/pull/455))
+- Add RNG infrastructure ([#547](https://github.com/smoltcp-rs/smoltcp/pull/547), [#573](https://github.com/smoltcp-rs/smoltcp/pull/573))
+- Add `Context` struct that must be passed to some socket methods ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500))
+- Remove `SocketSet`, sockets are owned by `Interface` now. ([#557](https://github.com/smoltcp-rs/smoltcp/pull/557), [#571](https://github.com/smoltcp-rs/smoltcp/pull/571))
+- TCP: Add Nagle's Algorithm. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500))
+- TCP crash and correctness fixes:
+ - Add Nagle's Algorithm. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500))
+ - Window scaling fixes. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500))
+ - Fix delayed ack causing ack not to be sent after 3 packets. ([#530](https://github.com/smoltcp-rs/smoltcp/pull/530))
+ - Fix RTT estimation for RTTs longer than 1 second ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix infinite loop when remote side sets a MSS of 0 ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix infinite loop when retransmit when remote window is 0 ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix crash when receiving a FIN in SYN_SENT state ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix overflow crash when receiving a wrong ACK seq in SYN_RECEIVED state ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix overflow crash when initial sequence number is u32::MAX ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538))
+ - Fix infinite loop on challenge ACKs ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542))
+ - Reply with RST to invalid packets in SynReceived state. ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542))
+ - Do not abort socket when receiving some invalid packets. ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542))
+ - Make initial sequence number random. ([#547](https://github.com/smoltcp-rs/smoltcp/pull/547))
+ - Reply with RST to ACKs with invalid ackno in SYN_SENT. ([#522](https://github.com/smoltcp-rs/smoltcp/pull/522))
+- ARP fixes to deal better with broken networks:
+ - Fill cache only from ARP packets, not any packets. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544))
+ - Fill cache only from ARP packets directed at us. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544))
+ - Reject ARP packets with a source address not in the local network. ([#536](https://github.com/smoltcp-rs/smoltcp/pull/536), [#544](https://github.com/smoltcp-rs/smoltcp/pull/544))
+ - Ignore unknown ARP packets. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544))
+ - Flush neighbor cache on IP change ([#564](https://github.com/smoltcp-rs/smoltcp/pull/564))
+- UDP: Add `close()` method to unbind socket. ([#475](https://github.com/smoltcp-rs/smoltcp/pull/475), [#482](https://github.com/smoltcp-rs/smoltcp/pull/482))
+- DHCP client improvements:
+ - Refactored implementation to improve reliability and RFC compliance ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459))
+ - Convert to socket ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459))
+ - Added `max_lease_duration` option ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459))
+ - Do not set the BROADCAST flag ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548))
+ - Add option to ignore NAKs ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548))
+- DHCP wire:
+ - Fix DhcpRepr::buffer_len not accounting for lease time, router and subnet options ([#478](https://github.com/smoltcp-rs/smoltcp/pull/478))
+ - Emit DNS servers in DhcpRepr ([#510](https://github.com/smoltcp-rs/smoltcp/pull/510))
+ - Fix incorrect bit for BROADCAST flag ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548))
+- Improve resilience against packet ingress processing errors ([#281](https://github.com/smoltcp-rs/smoltcp/pull/281), [#483](https://github.com/smoltcp-rs/smoltcp/pull/483))
+- Implement `std::error::Error` for `smoltcp::Error` ([#485](https://github.com/smoltcp-rs/smoltcp/pull/485))
+- Update `managed` from 0.7 to 0.8 ([442](https://github.com/smoltcp-rs/smoltcp/pull/442))
+- Fix incorrect timestamp in PCAP captures ([#513](https://github.com/smoltcp-rs/smoltcp/pull/513))
+- Use microseconds instead of milliseconds in Instant and Duration ([#514](https://github.com/smoltcp-rs/smoltcp/pull/514))
+- Expose inner `Device` in `PcapWriter` ([#524](https://github.com/smoltcp-rs/smoltcp/pull/524))
+- Fix assert with any_ip + broadcast dst_addr. ([#533](https://github.com/smoltcp-rs/smoltcp/pull/533), [#534](https://github.com/smoltcp-rs/smoltcp/pull/534))
+- Simplify PcapSink trait ([#535](https://github.com/smoltcp-rs/smoltcp/pull/535))
+- Fix wrong operation order in FuzzInjector ([#525](https://github.com/smoltcp-rs/smoltcp/pull/525), [#535](https://github.com/smoltcp-rs/smoltcp/pull/535))
+
+## [0.7.5] - 2021-06-28
+
+- dhcpv4: emit DNS servers in repr ([#505](https://github.com/smoltcp-rs/smoltcp/pull/505))
+
+## [0.7.4] - 2021-06-11
+
+- tcp: fix "subtract sequence numbers with underflow" on remote window shrink. ([#490](https://github.com/smoltcp-rs/smoltcp/pull/490))
+- tcp: fix subtract with overflow when receiving a SYNACK with unincremented ACK number. ([#491](https://github.com/smoltcp-rs/smoltcp/pull/491))
+- tcp: use nonzero initial sequence number to workaround misbehaving servers. ([#492](https://github.com/smoltcp-rs/smoltcp/pull/492))
+
+## [0.7.3] - 2021-05-29
+
+- Fix "unused attribute" error in recent nightlies.
+
+## [0.7.2] - 2021-05-29
+
+- iface: check for ipv4 subnet broadcast addrs everywhere ([#462](https://github.com/smoltcp-rs/smoltcp/pull/462))
+- dhcp: always send parameter_request_list. ([#456](https://github.com/smoltcp-rs/smoltcp/pull/456))
+- dhcp: Clear expiration time on reset. ([#456](https://github.com/smoltcp-rs/smoltcp/pull/456))
+- phy: fix FaultInjector returning a too big buffer when simulating a drop on tx ([#463](https://github.com/smoltcp-rs/smoltcp/pull/463))
+- tcp rtte: fix "attempt to multiply with overflow". ([#476](https://github.com/smoltcp-rs/smoltcp/pull/476))
+- tcp: LastAck should only change to Closed on ack of fin. ([#477](https://github.com/smoltcp-rs/smoltcp/pull/477))
+- wire/dhcpv4: account for lease time, router and subnet options in DhcpRepr::buffer_len ([#478](https://github.com/smoltcp-rs/smoltcp/pull/478))
+
+## [0.7.1] - 2021-03-27
+
+- ndisc: Fix NeighborSolicit incorrectly asking for src addr instead of dst addr ([419](https://github.com/smoltcp-rs/smoltcp/pull/419))
+- dhcpv4: respect lease time from the server instead of renewing every 60 seconds. ([437](https://github.com/smoltcp-rs/smoltcp/pull/437))
+- Fix build errors due to invalid combinations of features ([416](https://github.com/smoltcp-rs/smoltcp/pull/416), [447](https://github.com/smoltcp-rs/smoltcp/pull/447))
+- wire/ipv4: make some functions const ([420](https://github.com/smoltcp-rs/smoltcp/pull/420))
+- phy: fix BPF on OpenBSD ([421](https://github.com/smoltcp-rs/smoltcp/pull/421), [427](https://github.com/smoltcp-rs/smoltcp/pull/427))
+- phy: enable RawSocket, TapInterface on Android ([435](https://github.com/smoltcp-rs/smoltcp/pull/435))
+- phy: fix phy_wait for waits longer than 1 second ([449](https://github.com/smoltcp-rs/smoltcp/pull/449))
+
+## [0.7.0] - 2021-01-20
+
+- Minimum Supported Rust Version (MSRV) **bumped** from 1.36 to 1.40
+
+### New features
+- tcp: Allow distinguishing between graceful (FIN) and ungraceful (RST) close. On graceful close, `recv()` now returns `Error::Finished`. On ungraceful close, `Error::Illegal` is returned, as before. ([351](https://github.com/smoltcp-rs/smoltcp/pull/351))
+- sockets: Add support for attaching async/await Wakers to sockets. Wakers are woken on socket state changes. ([394](https://github.com/smoltcp-rs/smoltcp/pull/394))
+- tcp: Set retransmission timeout based on an RTT estimation, instead of the previously fixed 100ms. This improves performance on high-latency links, such as mobile networks. ([406](https://github.com/smoltcp-rs/smoltcp/pull/406))
+- tcp: add Delayed ACK support. On by default, with a 10ms delay. ([404](https://github.com/smoltcp-rs/smoltcp/pull/404))
+- ip: Process broadcast packets directed to the subnet's broadcast address, such as 192.168.1.255. Previously broadcast packets were
+only processed when directed to the 255.255.255.255 address. ([377](https://github.com/smoltcp-rs/smoltcp/pull/377))
+
+### Fixes
+- udp,raw,icmp: Fix packet buffer panic caused by large payload ([332](https://github.com/smoltcp-rs/smoltcp/pull/332))
+- dhcpv4: use offered ip in requested ip option ([310](https://github.com/smoltcp-rs/smoltcp/pull/310))
+- dhcpv4: Re-export dhcp::clientv4::Config
+- dhcpv4: Enable `proto-dhcpv4` feature by default. ([327](https://github.com/smoltcp-rs/smoltcp/pull/327))
+- ethernet,arp: Allow for ARP retry during egress ([368](https://github.com/smoltcp-rs/smoltcp/pull/368))
+- ethernet,arp: Only limit the neighbor cache rate after sending a request packet ([369](https://github.com/smoltcp-rs/smoltcp/pull/369))
+- tcp: use provided ip for TcpSocket::connect instead of 0.0.0.0 ([329](https://github.com/smoltcp-rs/smoltcp/pull/329))
+- tcp: Accept data packets in FIN_WAIT_2 state. ([350](https://github.com/smoltcp-rs/smoltcp/pull/350))
+- tcp: Always send updated ack number in `ack_reply()`. ([353](https://github.com/smoltcp-rs/smoltcp/pull/353))
+- tcp: allow sending ACKs in FinWait2 state. ([388](https://github.com/smoltcp-rs/smoltcp/pull/388))
+- tcp: fix racey simultaneous close not sending FIN. ([398](https://github.com/smoltcp-rs/smoltcp/pull/398))
+- tcp: Do not send window updates in states that shouldn't do so ([360](https://github.com/smoltcp-rs/smoltcp/pull/360))
+- tcp: Return RST to unexpected ACK in SYN-SENT state. ([367](https://github.com/smoltcp-rs/smoltcp/pull/367))
+- tcp: Take MTU into account during TcpSocket dispatch. ([384](https://github.com/smoltcp-rs/smoltcp/pull/384))
+- tcp: don't send data outside the remote window ([387](https://github.com/smoltcp-rs/smoltcp/pull/387))
+- phy: Take Ethernet header into account for MTU of RawSocket and TapInterface. ([393](https://github.com/smoltcp-rs/smoltcp/pull/393))
+- phy: add null terminator to c-string passed to libc API ([372](https://github.com/smoltcp-rs/smoltcp/pull/372))
+
+### Quality of Life&trade; improvements
+- Update to Rust 2018 edition ([396](https://github.com/smoltcp-rs/smoltcp/pull/396))
+- Migrate CI to Github Actions ([390](https://github.com/smoltcp-rs/smoltcp/pull/390))
+- Fix clippy lints, enforce clippy in CI ([395](https://github.com/smoltcp-rs/smoltcp/pull/395), [402](https://github.com/smoltcp-rs/smoltcp/pull/402), [403](https://github.com/smoltcp-rs/smoltcp/pull/403), [405](https://github.com/smoltcp-rs/smoltcp/pull/405), [407](https://github.com/smoltcp-rs/smoltcp/pull/407))
+- Use #[non_exhaustive] for enums and structs ([409](https://github.com/smoltcp-rs/smoltcp/pull/409), [411](https://github.com/smoltcp-rs/smoltcp/pull/411))
+- Simplify lifetime parameters of sockets, SocketSet, EthernetInterface ([410](https://github.com/smoltcp-rs/smoltcp/pull/410), [413](https://github.com/smoltcp-rs/smoltcp/pull/413))
+
+[Unreleased]: https://github.com/smoltcp-rs/smoltcp/compare/v0.11.0...HEAD
+[0.11.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.10.0...v0.11.0
+[0.10.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.1...v0.10.0
+[0.9.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.0...v0.9.1
+[0.9.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.2...v0.9.0
+[0.8.2]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.1...v0.8.2
+[0.8.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.0...v0.8.1
+[0.8.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...v0.8.0
+[0.7.5]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.4...v0.7.5
+[0.7.4]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.3...v0.7.4
+[0.7.3]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.2...v0.7.3
+[0.7.2]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.1...v0.7.2
+[0.7.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...v0.7.1
+[0.7.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.6.0...v0.7.0
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..d67ab14
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,466 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.65"
+name = "smoltcp"
+version = "0.11.0"
+authors = ["whitequark <whitequark@whitequark.org>"]
+autoexamples = false
+description = "A TCP/IP stack designed for bare-metal, real-time systems without a heap."
+homepage = "https://github.com/smoltcp-rs/smoltcp"
+documentation = "https://docs.rs/smoltcp/"
+readme = "README.md"
+keywords = [
+ "ip",
+ "tcp",
+ "udp",
+ "ethernet",
+ "network",
+]
+categories = [
+ "embedded",
+ "network-programming",
+]
+license = "0BSD"
+repository = "https://github.com/smoltcp-rs/smoltcp.git"
+
+[profile.release]
+debug = 2
+
+[[example]]
+name = "packet2pcap"
+path = "utils/packet2pcap.rs"
+required-features = ["std"]
+
+[[example]]
+name = "tcpdump"
+required-features = [
+ "std",
+ "phy-raw_socket",
+ "proto-ipv4",
+]
+
+[[example]]
+name = "httpclient"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "proto-ipv6",
+ "socket-tcp",
+]
+
+[[example]]
+name = "ping"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "proto-ipv6",
+ "socket-icmp",
+]
+
+[[example]]
+name = "server"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "socket-tcp",
+ "socket-udp",
+]
+
+[[example]]
+name = "client"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "socket-tcp",
+ "socket-udp",
+]
+
+[[example]]
+name = "loopback"
+required-features = [
+ "log",
+ "medium-ethernet",
+ "proto-ipv4",
+ "socket-tcp",
+]
+
+[[example]]
+name = "multicast"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "proto-igmp",
+ "socket-udp",
+]
+
+[[example]]
+name = "benchmark"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "socket-raw",
+ "socket-udp",
+]
+
+[[example]]
+name = "dhcp_client"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "proto-dhcpv4",
+ "socket-raw",
+]
+
+[[example]]
+name = "sixlowpan"
+required-features = [
+ "std",
+ "medium-ieee802154",
+ "phy-raw_socket",
+ "proto-sixlowpan",
+ "proto-sixlowpan-fragmentation",
+ "socket-udp",
+]
+
+[[example]]
+name = "sixlowpan_benchmark"
+required-features = [
+ "std",
+ "medium-ieee802154",
+ "phy-raw_socket",
+ "proto-sixlowpan",
+ "proto-sixlowpan-fragmentation",
+ "socket-udp",
+]
+
+[[example]]
+name = "dns"
+required-features = [
+ "std",
+ "medium-ethernet",
+ "medium-ip",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "socket-dns",
+]
+
+[dependencies.bitflags]
+version = "1.0"
+default-features = false
+
+[dependencies.byteorder]
+version = "1.0"
+default-features = false
+
+[dependencies.cfg-if]
+version = "1.0.0"
+
+[dependencies.defmt]
+version = "0.3"
+optional = true
+
+[dependencies.heapless]
+version = "0.8"
+
+[dependencies.libc]
+version = "0.2.18"
+optional = true
+
+[dependencies.log]
+version = "0.4.4"
+optional = true
+default-features = false
+
+[dependencies.managed]
+version = "0.8"
+features = ["map"]
+default-features = false
+
+[dev-dependencies.env_logger]
+version = "0.10"
+
+[dev-dependencies.getopts]
+version = "0.2"
+
+[dev-dependencies.rand]
+version = "0.8"
+
+[dev-dependencies.rstest]
+version = "0.17"
+
+[dev-dependencies.url]
+version = "2.0"
+
+[features]
+_proto-fragmentation = []
+alloc = [
+ "managed/alloc",
+ "defmt?/alloc",
+]
+assembler-max-segment-count-1 = []
+assembler-max-segment-count-16 = []
+assembler-max-segment-count-2 = []
+assembler-max-segment-count-3 = []
+assembler-max-segment-count-32 = []
+assembler-max-segment-count-4 = []
+assembler-max-segment-count-8 = []
+async = []
+default = [
+ "std",
+ "log",
+ "medium-ethernet",
+ "medium-ip",
+ "medium-ieee802154",
+ "phy-raw_socket",
+ "phy-tuntap_interface",
+ "proto-ipv4",
+ "proto-igmp",
+ "proto-dhcpv4",
+ "proto-ipv6",
+ "proto-dns",
+ "proto-ipv4-fragmentation",
+ "proto-sixlowpan-fragmentation",
+ "socket-raw",
+ "socket-icmp",
+ "socket-udp",
+ "socket-tcp",
+ "socket-dhcpv4",
+ "socket-dns",
+ "socket-mdns",
+ "packetmeta-id",
+ "async",
+]
+defmt = [
+ "dep:defmt",
+ "heapless/defmt-03",
+]
+dns-max-name-size-128 = []
+dns-max-name-size-255 = []
+dns-max-name-size-64 = []
+dns-max-result-count-1 = []
+dns-max-result-count-16 = []
+dns-max-result-count-2 = []
+dns-max-result-count-3 = []
+dns-max-result-count-32 = []
+dns-max-result-count-4 = []
+dns-max-result-count-8 = []
+dns-max-server-count-1 = []
+dns-max-server-count-16 = []
+dns-max-server-count-2 = []
+dns-max-server-count-3 = []
+dns-max-server-count-32 = []
+dns-max-server-count-4 = []
+dns-max-server-count-8 = []
+fragmentation-buffer-size-1024 = []
+fragmentation-buffer-size-1500 = []
+fragmentation-buffer-size-16384 = []
+fragmentation-buffer-size-2048 = []
+fragmentation-buffer-size-256 = []
+fragmentation-buffer-size-32768 = []
+fragmentation-buffer-size-4096 = []
+fragmentation-buffer-size-512 = []
+fragmentation-buffer-size-65536 = []
+fragmentation-buffer-size-8192 = []
+iface-max-addr-count-1 = []
+iface-max-addr-count-2 = []
+iface-max-addr-count-3 = []
+iface-max-addr-count-4 = []
+iface-max-addr-count-5 = []
+iface-max-addr-count-6 = []
+iface-max-addr-count-7 = []
+iface-max-addr-count-8 = []
+iface-max-multicast-group-count-1 = []
+iface-max-multicast-group-count-1024 = []
+iface-max-multicast-group-count-128 = []
+iface-max-multicast-group-count-16 = []
+iface-max-multicast-group-count-2 = []
+iface-max-multicast-group-count-256 = []
+iface-max-multicast-group-count-3 = []
+iface-max-multicast-group-count-32 = []
+iface-max-multicast-group-count-4 = []
+iface-max-multicast-group-count-5 = []
+iface-max-multicast-group-count-512 = []
+iface-max-multicast-group-count-6 = []
+iface-max-multicast-group-count-64 = []
+iface-max-multicast-group-count-7 = []
+iface-max-multicast-group-count-8 = []
+iface-max-route-count-1 = []
+iface-max-route-count-1024 = []
+iface-max-route-count-128 = []
+iface-max-route-count-16 = []
+iface-max-route-count-2 = []
+iface-max-route-count-256 = []
+iface-max-route-count-3 = []
+iface-max-route-count-32 = []
+iface-max-route-count-4 = []
+iface-max-route-count-5 = []
+iface-max-route-count-512 = []
+iface-max-route-count-6 = []
+iface-max-route-count-64 = []
+iface-max-route-count-7 = []
+iface-max-route-count-8 = []
+iface-max-sixlowpan-address-context-count-1 = []
+iface-max-sixlowpan-address-context-count-1024 = []
+iface-max-sixlowpan-address-context-count-128 = []
+iface-max-sixlowpan-address-context-count-16 = []
+iface-max-sixlowpan-address-context-count-2 = []
+iface-max-sixlowpan-address-context-count-256 = []
+iface-max-sixlowpan-address-context-count-3 = []
+iface-max-sixlowpan-address-context-count-32 = []
+iface-max-sixlowpan-address-context-count-4 = []
+iface-max-sixlowpan-address-context-count-5 = []
+iface-max-sixlowpan-address-context-count-512 = []
+iface-max-sixlowpan-address-context-count-6 = []
+iface-max-sixlowpan-address-context-count-64 = []
+iface-max-sixlowpan-address-context-count-7 = []
+iface-max-sixlowpan-address-context-count-8 = []
+iface-neighbor-cache-count-1 = []
+iface-neighbor-cache-count-1024 = []
+iface-neighbor-cache-count-128 = []
+iface-neighbor-cache-count-16 = []
+iface-neighbor-cache-count-2 = []
+iface-neighbor-cache-count-256 = []
+iface-neighbor-cache-count-3 = []
+iface-neighbor-cache-count-32 = []
+iface-neighbor-cache-count-4 = []
+iface-neighbor-cache-count-5 = []
+iface-neighbor-cache-count-512 = []
+iface-neighbor-cache-count-6 = []
+iface-neighbor-cache-count-64 = []
+iface-neighbor-cache-count-7 = []
+iface-neighbor-cache-count-8 = []
+ipv6-hbh-max-options-1 = []
+ipv6-hbh-max-options-16 = []
+ipv6-hbh-max-options-2 = []
+ipv6-hbh-max-options-3 = []
+ipv6-hbh-max-options-32 = []
+ipv6-hbh-max-options-4 = []
+ipv6-hbh-max-options-8 = []
+medium-ethernet = ["socket"]
+medium-ieee802154 = [
+ "socket",
+ "proto-sixlowpan",
+]
+medium-ip = ["socket"]
+packetmeta-id = []
+phy-raw_socket = [
+ "std",
+ "libc",
+]
+phy-tuntap_interface = [
+ "std",
+ "libc",
+ "medium-ethernet",
+]
+proto-dhcpv4 = ["proto-ipv4"]
+proto-dns = []
+proto-igmp = ["proto-ipv4"]
+proto-ipsec = [
+ "proto-ipsec-ah",
+ "proto-ipsec-esp",
+]
+proto-ipsec-ah = []
+proto-ipsec-esp = []
+proto-ipv4 = []
+proto-ipv4-fragmentation = [
+ "proto-ipv4",
+ "_proto-fragmentation",
+]
+proto-ipv6 = []
+proto-ipv6-fragmentation = [
+ "proto-ipv6",
+ "_proto-fragmentation",
+]
+proto-ipv6-hbh = ["proto-ipv6"]
+proto-ipv6-routing = ["proto-ipv6"]
+proto-rpl = [
+ "proto-ipv6-hbh",
+ "proto-ipv6-routing",
+]
+proto-sixlowpan = ["proto-ipv6"]
+proto-sixlowpan-fragmentation = [
+ "proto-sixlowpan",
+ "_proto-fragmentation",
+]
+reassembly-buffer-count-1 = []
+reassembly-buffer-count-16 = []
+reassembly-buffer-count-2 = []
+reassembly-buffer-count-3 = []
+reassembly-buffer-count-32 = []
+reassembly-buffer-count-4 = []
+reassembly-buffer-count-8 = []
+reassembly-buffer-size-1024 = []
+reassembly-buffer-size-1500 = []
+reassembly-buffer-size-16384 = []
+reassembly-buffer-size-2048 = []
+reassembly-buffer-size-256 = []
+reassembly-buffer-size-32768 = []
+reassembly-buffer-size-4096 = []
+reassembly-buffer-size-512 = []
+reassembly-buffer-size-65536 = []
+reassembly-buffer-size-8192 = []
+rpl-parents-buffer-count-16 = []
+rpl-parents-buffer-count-2 = []
+rpl-parents-buffer-count-32 = []
+rpl-parents-buffer-count-4 = []
+rpl-parents-buffer-count-8 = []
+rpl-relations-buffer-count-1 = []
+rpl-relations-buffer-count-128 = []
+rpl-relations-buffer-count-16 = []
+rpl-relations-buffer-count-2 = []
+rpl-relations-buffer-count-32 = []
+rpl-relations-buffer-count-4 = []
+rpl-relations-buffer-count-64 = []
+rpl-relations-buffer-count-8 = []
+socket = []
+socket-dhcpv4 = [
+ "socket",
+ "medium-ethernet",
+ "proto-dhcpv4",
+]
+socket-dns = [
+ "socket",
+ "proto-dns",
+]
+socket-icmp = ["socket"]
+socket-mdns = ["socket-dns"]
+socket-raw = ["socket"]
+socket-tcp = ["socket"]
+socket-udp = ["socket"]
+std = [
+ "managed/std",
+ "alloc",
+]
+verbose = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..b3cd9c9
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,305 @@
+[package]
+name = "smoltcp"
+version = "0.11.0"
+edition = "2021"
+rust-version = "1.65"
+authors = ["whitequark <whitequark@whitequark.org>"]
+description = "A TCP/IP stack designed for bare-metal, real-time systems without a heap."
+documentation = "https://docs.rs/smoltcp/"
+homepage = "https://github.com/smoltcp-rs/smoltcp"
+repository = "https://github.com/smoltcp-rs/smoltcp.git"
+readme = "README.md"
+keywords = ["ip", "tcp", "udp", "ethernet", "network"]
+categories = ["embedded", "network-programming"]
+license = "0BSD"
+# Each example should have an explicit `[[example]]` section here to
+# ensure that the correct features are enabled.
+autoexamples = false
+
+[dependencies]
+managed = { version = "0.8", default-features = false, features = ["map"] }
+byteorder = { version = "1.0", default-features = false }
+log = { version = "0.4.4", default-features = false, optional = true }
+libc = { version = "0.2.18", optional = true }
+bitflags = { version = "1.0", default-features = false }
+defmt = { version = "0.3", optional = true }
+cfg-if = "1.0.0"
+heapless = "0.8"
+
+[dev-dependencies]
+env_logger = "0.10"
+getopts = "0.2"
+rand = "0.8"
+url = "2.0"
+rstest = "0.17"
+
+[features]
+std = ["managed/std", "alloc"]
+alloc = ["managed/alloc", "defmt?/alloc"]
+verbose = []
+defmt = ["dep:defmt", "heapless/defmt-03"]
+"medium-ethernet" = ["socket"]
+"medium-ip" = ["socket"]
+"medium-ieee802154" = ["socket", "proto-sixlowpan"]
+
+"phy-raw_socket" = ["std", "libc"]
+"phy-tuntap_interface" = ["std", "libc", "medium-ethernet"]
+
+"proto-ipv4" = []
+"proto-ipv4-fragmentation" = ["proto-ipv4", "_proto-fragmentation"]
+"proto-igmp" = ["proto-ipv4"]
+"proto-dhcpv4" = ["proto-ipv4"]
+"proto-ipv6" = []
+"proto-ipv6-hbh" = ["proto-ipv6"]
+"proto-ipv6-fragmentation" = ["proto-ipv6", "_proto-fragmentation"]
+"proto-ipv6-routing" = ["proto-ipv6"]
+"proto-rpl" = ["proto-ipv6-hbh", "proto-ipv6-routing"]
+"proto-sixlowpan" = ["proto-ipv6"]
+"proto-sixlowpan-fragmentation" = ["proto-sixlowpan", "_proto-fragmentation"]
+"proto-dns" = []
+"proto-ipsec" = ["proto-ipsec-ah", "proto-ipsec-esp"]
+"proto-ipsec-ah" = []
+"proto-ipsec-esp" = []
+
+"socket" = []
+"socket-raw" = ["socket"]
+"socket-udp" = ["socket"]
+"socket-tcp" = ["socket"]
+"socket-icmp" = ["socket"]
+"socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"]
+"socket-dns" = ["socket", "proto-dns"]
+"socket-mdns" = ["socket-dns"]
+
+"packetmeta-id" = []
+
+"async" = []
+
+default = [
+ "std", "log", # needed for `cargo test --no-default-features --features default` :/
+ "medium-ethernet", "medium-ip", "medium-ieee802154",
+ "phy-raw_socket", "phy-tuntap_interface",
+ "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-dns",
+ "proto-ipv4-fragmentation", "proto-sixlowpan-fragmentation",
+ "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns", "socket-mdns",
+ "packetmeta-id", "async"
+]
+
+# Private features
+# Features starting with "_" are considered private. They should not be enabled by
+# other crates, and they are not considered semver-stable.
+
+"_proto-fragmentation" = []
+
+# BEGIN AUTOGENERATED CONFIG FEATURES
+# Generated by gen_config.py. DO NOT EDIT.
+iface-max-addr-count-1 = []
+iface-max-addr-count-2 = [] # Default
+iface-max-addr-count-3 = []
+iface-max-addr-count-4 = []
+iface-max-addr-count-5 = []
+iface-max-addr-count-6 = []
+iface-max-addr-count-7 = []
+iface-max-addr-count-8 = []
+
+iface-max-multicast-group-count-1 = []
+iface-max-multicast-group-count-2 = []
+iface-max-multicast-group-count-3 = []
+iface-max-multicast-group-count-4 = [] # Default
+iface-max-multicast-group-count-5 = []
+iface-max-multicast-group-count-6 = []
+iface-max-multicast-group-count-7 = []
+iface-max-multicast-group-count-8 = []
+iface-max-multicast-group-count-16 = []
+iface-max-multicast-group-count-32 = []
+iface-max-multicast-group-count-64 = []
+iface-max-multicast-group-count-128 = []
+iface-max-multicast-group-count-256 = []
+iface-max-multicast-group-count-512 = []
+iface-max-multicast-group-count-1024 = []
+
+iface-max-sixlowpan-address-context-count-1 = []
+iface-max-sixlowpan-address-context-count-2 = []
+iface-max-sixlowpan-address-context-count-3 = []
+iface-max-sixlowpan-address-context-count-4 = [] # Default
+iface-max-sixlowpan-address-context-count-5 = []
+iface-max-sixlowpan-address-context-count-6 = []
+iface-max-sixlowpan-address-context-count-7 = []
+iface-max-sixlowpan-address-context-count-8 = []
+iface-max-sixlowpan-address-context-count-16 = []
+iface-max-sixlowpan-address-context-count-32 = []
+iface-max-sixlowpan-address-context-count-64 = []
+iface-max-sixlowpan-address-context-count-128 = []
+iface-max-sixlowpan-address-context-count-256 = []
+iface-max-sixlowpan-address-context-count-512 = []
+iface-max-sixlowpan-address-context-count-1024 = []
+
+iface-neighbor-cache-count-1 = []
+iface-neighbor-cache-count-2 = []
+iface-neighbor-cache-count-3 = []
+iface-neighbor-cache-count-4 = [] # Default
+iface-neighbor-cache-count-5 = []
+iface-neighbor-cache-count-6 = []
+iface-neighbor-cache-count-7 = []
+iface-neighbor-cache-count-8 = []
+iface-neighbor-cache-count-16 = []
+iface-neighbor-cache-count-32 = []
+iface-neighbor-cache-count-64 = []
+iface-neighbor-cache-count-128 = []
+iface-neighbor-cache-count-256 = []
+iface-neighbor-cache-count-512 = []
+iface-neighbor-cache-count-1024 = []
+
+iface-max-route-count-1 = []
+iface-max-route-count-2 = [] # Default
+iface-max-route-count-3 = []
+iface-max-route-count-4 = []
+iface-max-route-count-5 = []
+iface-max-route-count-6 = []
+iface-max-route-count-7 = []
+iface-max-route-count-8 = []
+iface-max-route-count-16 = []
+iface-max-route-count-32 = []
+iface-max-route-count-64 = []
+iface-max-route-count-128 = []
+iface-max-route-count-256 = []
+iface-max-route-count-512 = []
+iface-max-route-count-1024 = []
+
+fragmentation-buffer-size-256 = []
+fragmentation-buffer-size-512 = []
+fragmentation-buffer-size-1024 = []
+fragmentation-buffer-size-1500 = [] # Default
+fragmentation-buffer-size-2048 = []
+fragmentation-buffer-size-4096 = []
+fragmentation-buffer-size-8192 = []
+fragmentation-buffer-size-16384 = []
+fragmentation-buffer-size-32768 = []
+fragmentation-buffer-size-65536 = []
+
+assembler-max-segment-count-1 = []
+assembler-max-segment-count-2 = []
+assembler-max-segment-count-3 = []
+assembler-max-segment-count-4 = [] # Default
+assembler-max-segment-count-8 = []
+assembler-max-segment-count-16 = []
+assembler-max-segment-count-32 = []
+
+reassembly-buffer-size-256 = []
+reassembly-buffer-size-512 = []
+reassembly-buffer-size-1024 = []
+reassembly-buffer-size-1500 = [] # Default
+reassembly-buffer-size-2048 = []
+reassembly-buffer-size-4096 = []
+reassembly-buffer-size-8192 = []
+reassembly-buffer-size-16384 = []
+reassembly-buffer-size-32768 = []
+reassembly-buffer-size-65536 = []
+
+reassembly-buffer-count-1 = [] # Default
+reassembly-buffer-count-2 = []
+reassembly-buffer-count-3 = []
+reassembly-buffer-count-4 = []
+reassembly-buffer-count-8 = []
+reassembly-buffer-count-16 = []
+reassembly-buffer-count-32 = []
+
+ipv6-hbh-max-options-1 = [] # Default
+ipv6-hbh-max-options-2 = []
+ipv6-hbh-max-options-3 = []
+ipv6-hbh-max-options-4 = []
+ipv6-hbh-max-options-8 = []
+ipv6-hbh-max-options-16 = []
+ipv6-hbh-max-options-32 = []
+
+dns-max-result-count-1 = [] # Default
+dns-max-result-count-2 = []
+dns-max-result-count-3 = []
+dns-max-result-count-4 = []
+dns-max-result-count-8 = []
+dns-max-result-count-16 = []
+dns-max-result-count-32 = []
+
+dns-max-server-count-1 = [] # Default
+dns-max-server-count-2 = []
+dns-max-server-count-3 = []
+dns-max-server-count-4 = []
+dns-max-server-count-8 = []
+dns-max-server-count-16 = []
+dns-max-server-count-32 = []
+
+dns-max-name-size-64 = []
+dns-max-name-size-128 = []
+dns-max-name-size-255 = [] # Default
+
+rpl-relations-buffer-count-1 = []
+rpl-relations-buffer-count-2 = []
+rpl-relations-buffer-count-4 = []
+rpl-relations-buffer-count-8 = []
+rpl-relations-buffer-count-16 = [] # Default
+rpl-relations-buffer-count-32 = []
+rpl-relations-buffer-count-64 = []
+rpl-relations-buffer-count-128 = []
+
+rpl-parents-buffer-count-2 = []
+rpl-parents-buffer-count-4 = []
+rpl-parents-buffer-count-8 = [] # Default
+rpl-parents-buffer-count-16 = []
+rpl-parents-buffer-count-32 = []
+
+# END AUTOGENERATED CONFIG FEATURES
+
+[[example]]
+name = "packet2pcap"
+path = "utils/packet2pcap.rs"
+required-features = ["std"]
+
+[[example]]
+name = "tcpdump"
+required-features = ["std", "phy-raw_socket", "proto-ipv4"]
+
+[[example]]
+name = "httpclient"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"]
+
+[[example]]
+name = "ping"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"]
+
+[[example]]
+name = "server"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
+
+[[example]]
+name = "client"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
+
+[[example]]
+name = "loopback"
+required-features = ["log", "medium-ethernet", "proto-ipv4", "socket-tcp"]
+
+[[example]]
+name = "multicast"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-igmp", "socket-udp"]
+
+[[example]]
+name = "benchmark"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
+
+[[example]]
+name = "dhcp_client"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"]
+
+[[example]]
+name = "sixlowpan"
+required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "proto-sixlowpan-fragmentation", "socket-udp"]
+
+[[example]]
+name = "sixlowpan_benchmark"
+required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "proto-sixlowpan-fragmentation", "socket-udp"]
+
+[[example]]
+name = "dns"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-dns"]
+
+[profile.release]
+debug = 2
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..26f20b7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-0BSD.txt \ No newline at end of file
diff --git a/LICENSE-0BSD.txt b/LICENSE-0BSD.txt
new file mode 100644
index 0000000..427fafa
--- /dev/null
+++ b/LICENSE-0BSD.txt
@@ -0,0 +1,13 @@
+Copyright (C) 2016 whitequark@whitequark.org
+
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..ce996b4
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "smoltcp"
+description: "A TCP/IP stack designed for bare-metal, real-time systems without a heap."
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "smoltcp"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/smoltcp/smoltcp-0.11.0.crate"
+ primary_source: true
+ }
+ version: "0.11.0"
+ license_type: PERMISSIVE
+ last_upgrade_date {
+ year: 2024
+ month: 2
+ day: 17
+ }
+}
diff --git a/MODULE_LICENSE_ZERO_BSD b/MODULE_LICENSE_ZERO_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_ZERO_BSD
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74fa161
--- /dev/null
+++ b/README.md
@@ -0,0 +1,560 @@
+# smoltcp
+
+[![docs.rs](https://docs.rs/smoltcp/badge.svg)](https://docs.rs/smoltcp)
+[![crates.io](https://img.shields.io/crates/v/smoltcp.svg)](https://crates.io/crates/smoltcp)
+[![crates.io](https://img.shields.io/crates/d/smoltcp.svg)](https://crates.io/crates/smoltcp)
+[![crates.io](https://img.shields.io/matrix/smoltcp:matrix.org)](https://matrix.to/#/#smoltcp:matrix.org)
+[![codecov](https://codecov.io/github/smoltcp-rs/smoltcp/branch/master/graph/badge.svg?token=3KbAR9xH1t)](https://codecov.io/github/smoltcp-rs/smoltcp)
+
+_smoltcp_ is a standalone, event-driven TCP/IP stack that is designed for bare-metal,
+real-time systems. Its design goals are simplicity and robustness. Its design anti-goals
+include complicated compile-time computations, such as macro or type tricks, even
+at cost of performance degradation.
+
+_smoltcp_ does not need heap allocation *at all*, is [extensively documented][docs],
+and compiles on stable Rust 1.65 and later.
+
+_smoltcp_ achieves [~Gbps of throughput](#examplesbenchmarkrs) when tested against
+the Linux TCP stack in loopback mode.
+
+[docs]: https://docs.rs/smoltcp/
+
+## Features
+
+_smoltcp_ is missing many widely deployed features, usually because no one implemented them yet.
+To set expectations right, both implemented and omitted features are listed.
+
+### Media layer
+
+There are 3 supported mediums.
+
+* Ethernet
+ * Regular Ethernet II frames are supported.
+ * Unicast, broadcast and multicast packets are supported.
+ * ARP packets (including gratuitous requests and replies) are supported.
+ * ARP requests are sent at a rate not exceeding one per second.
+ * Cached ARP entries expire after one minute.
+ * 802.3 frames and 802.1Q are **not** supported.
+ * Jumbo frames are **not** supported.
+* IP
+ * Unicast, broadcast and multicast packets are supported.
+* IEEE 802.15.4
+ * Only support for data frames.
+
+### IP layer
+
+#### IPv4
+
+ * IPv4 header checksum is generated and validated.
+ * IPv4 time-to-live value is configurable per socket, set to 64 by default.
+ * IPv4 default gateway is supported.
+ * Routing outgoing IPv4 packets is supported, through a default gateway or a CIDR route table.
+ * IPv4 fragmentation and reassembly is supported.
+ * IPv4 options are **not** supported and are silently ignored.
+
+#### IPv6
+
+ * IPv6 hop-limit value is configurable per socket, set to 64 by default.
+ * Routing outgoing IPv6 packets is supported, through a default gateway or a CIDR route table.
+ * IPv6 hop-by-hop header is supported.
+ * ICMPv6 parameter problem message is generated in response to an unrecognized IPv6 next header.
+ * ICMPv6 parameter problem message is **not** generated in response to an unknown IPv6
+ hop-by-hop option.
+
+#### 6LoWPAN
+
+ * Implementation of [RFC6282](https://tools.ietf.org/rfc/rfc6282.txt).
+ * Fragmentation is supported, as defined in [RFC4944](https://tools.ietf.org/rfc/rfc4944.txt).
+ * UDP header compression/decompression is supported.
+ * Extension header compression/decompression is supported.
+ * Uncompressed IPv6 Extension Headers are **not** supported.
+
+### IP multicast
+
+#### IGMP
+
+The IGMPv1 and IGMPv2 protocols are supported, and IPv4 multicast is available.
+
+ * Membership reports are sent in response to membership queries at
+ equal intervals equal to the maximum response time divided by the
+ number of groups to be reported.
+
+### ICMP layer
+
+#### ICMPv4
+
+The ICMPv4 protocol is supported, and ICMP sockets are available.
+
+ * ICMPv4 header checksum is supported.
+ * ICMPv4 echo replies are generated in response to echo requests.
+ * ICMP sockets can listen to ICMPv4 Port Unreachable messages, or any ICMPv4 messages with
+ a given IPv4 identifier field.
+ * ICMPv4 protocol unreachable messages are **not** passed to higher layers when received.
+ * ICMPv4 parameter problem messages are **not** generated.
+
+#### ICMPv6
+
+The ICMPv6 protocol is supported, and ICMP sockets are available.
+
+ * ICMPv6 header checksum is supported.
+ * ICMPv6 echo replies are generated in response to echo requests.
+ * ICMPv6 protocol unreachable messages are **not** passed to higher layers when received.
+
+#### NDISC
+
+ * Neighbor Advertisement messages are generated in response to Neighbor Solicitations.
+ * Router Advertisement messages are **not** generated or read.
+ * Router Solicitation messages are **not** generated or read.
+ * Redirected Header messages are **not** generated or read.
+
+### UDP layer
+
+The UDP protocol is supported over IPv4 and IPv6, and UDP sockets are available.
+
+ * Header checksum is always generated and validated.
+ * In response to a packet arriving at a port without a listening socket,
+ an ICMP destination unreachable message is generated.
+
+### TCP layer
+
+The TCP protocol is supported over IPv4 and IPv6, and server and client TCP sockets are available.
+
+ * Header checksum is generated and validated.
+ * Maximum segment size is negotiated.
+ * Window scaling is negotiated.
+ * Multiple packets are transmitted without waiting for an acknowledgement.
+ * Reassembly of out-of-order segments is supported, with no more than 4 or 32 gaps in sequence space.
+ * Keep-alive packets may be sent at a configurable interval.
+ * Retransmission timeout starts at at an estimate of RTT, and doubles every time.
+ * Time-wait timeout has a fixed interval of 10 s.
+ * User timeout has a configurable interval.
+ * Delayed acknowledgements are supported, with configurable delay.
+ * Nagle's algorithm is implemented.
+ * Selective acknowledgements are **not** implemented.
+ * Silly window syndrome avoidance is **not** implemented.
+ * Congestion control is **not** implemented.
+ * Timestamping is **not** supported.
+ * Urgent pointer is **ignored**.
+ * Probing Zero Windows is **not** implemented.
+ * Packetization Layer Path MTU Discovery [PLPMTU](https://tools.ietf.org/rfc/rfc4821.txt) is **not** implemented.
+
+## Installation
+
+To use the _smoltcp_ library in your project, add the following to `Cargo.toml`:
+
+```toml
+[dependencies]
+smoltcp = "0.10.0"
+```
+
+The default configuration assumes a hosted environment, for ease of evaluation.
+You probably want to disable default features and configure them one by one:
+
+```toml
+[dependencies]
+smoltcp = { version = "0.10.0", default-features = false, features = ["log"] }
+```
+
+## Feature flags
+
+### Feature `std`
+
+The `std` feature enables use of objects and slices owned by the networking stack through a
+dependency on `std::boxed::Box` and `std::vec::Vec`.
+
+This feature is enabled by default.
+
+### Feature `alloc`
+
+The `alloc` feature enables use of objects owned by the networking stack through a dependency
+on collections from the `alloc` crate. This only works on nightly rustc.
+
+This feature is disabled by default.
+
+### Feature `log`
+
+The `log` feature enables logging of events within the networking stack through
+the [log crate][log]. Normal events (e.g. buffer level or TCP state changes) are emitted with
+the TRACE log level. Exceptional events (e.g. malformed packets) are emitted with
+the DEBUG log level.
+
+[log]: https://crates.io/crates/log
+
+This feature is enabled by default.
+
+### Feature `defmt`
+
+The `defmt` feature enables logging of events with the [defmt crate][defmt].
+
+[defmt]: https://crates.io/crates/defmt
+
+This feature is disabled by default, and cannot be used at the same time as `log`.
+
+### Feature `verbose`
+
+The `verbose` feature enables logging of events where the logging itself may incur very high
+overhead. For example, emitting a log line every time an application reads or writes as little
+as 1 octet from a socket is likely to overwhelm the application logic unless a `BufReader`
+or `BufWriter` is used, which are of course not available on heap-less systems.
+
+This feature is disabled by default.
+
+### Features `phy-raw_socket` and `phy-tuntap_interface`
+
+Enable `smoltcp::phy::RawSocket` and `smoltcp::phy::TunTapInterface`, respectively.
+
+These features are enabled by default.
+
+### Features `socket-raw`, `socket-udp`, `socket-tcp`, `socket-icmp`, `socket-dhcpv4`, `socket-dns`
+
+Enable the corresponding socket type.
+
+These features are enabled by default.
+
+### Features `proto-ipv4`, `proto-ipv6` and `proto-sixlowpan`
+
+Enable [IPv4], [IPv6] and [6LoWPAN] respectively.
+
+[IPv4]: https://tools.ietf.org/rfc/rfc791.txt
+[IPv6]: https://tools.ietf.org/rfc/rfc8200.txt
+[6LoWPAN]: https://tools.ietf.org/rfc/rfc6282.txt
+
+## Configuration
+
+_smoltcp_ has some configuration settings that are set at compile time, affecting sizes
+and counts of buffers.
+
+They can be set in two ways:
+
+- Via Cargo features: enable a feature like `<name>-<value>`. `name` must be in lowercase and
+use dashes instead of underscores. For example. `iface-max-addr-count-3`. Only a selection of values
+is available, check `Cargo.toml` for the list.
+- Via environment variables at build time: set the variable named `SMOLTCP_<value>`. For example
+`SMOLTCP_IFACE_MAX_ADDR_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
+Any value can be set, unlike with Cargo features.
+
+Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
+with different values, compilation fails.
+
+### `IFACE_MAX_ADDR_COUNT`
+
+Max amount of IP addresses that can be assigned to one interface (counting both IPv4 and IPv6 addresses). Default: 2.
+
+### `IFACE_MAX_MULTICAST_GROUP_COUNT`
+
+Max amount of multicast groups that can be joined by one interface. Default: 4.
+
+### `IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT`
+
+Max amount of 6LoWPAN address contexts that can be assigned to one interface. Default: 4.
+
+### `IFACE_NEIGHBOR_CACHE_COUNT`
+
+Amount of "IP address -> hardware address" entries the neighbor cache (also known as the "ARP cache" or the "ARP table") holds. Default: 4.
+
+### `IFACE_MAX_ROUTE_COUNT`
+
+Max amount of routes that can be added to one interface. Includes the default route. Includes both IPv4 and IPv6. Default: 2.
+
+### `FRAGMENTATION_BUFFER_SIZE`
+
+Size of the buffer used for fragmenting outgoing packets larger than the MTU. Packets larger than this setting will be dropped instead of fragmented. Default: 1500.
+
+### `ASSEMBLER_MAX_SEGMENT_COUNT`
+
+Maximum number of non-contiguous segments the assembler can hold. Used for both packet reassembly and TCP stream reassembly. Default: 4.
+
+### `REASSEMBLY_BUFFER_SIZE`
+
+Size of the buffer used for reassembling (de-fragmenting) incoming packets. If the reassembled packet is larger than this setting, it will be dropped instead of reassembled. Default: 1500.
+
+### `REASSEMBLY_BUFFER_COUNT`
+
+Number of reassembly buffers, i.e how many different incoming packets can be reassembled at the same time. Default: 1.
+
+### `DNS_MAX_RESULT_COUNT`
+
+Maximum amount of address results for a given DNS query that will be kept. For example, if this is set to 2 and the queried name has 4 `A` records, only the first 2 will be returned. Default: 1.
+
+### `DNS_MAX_SERVER_COUNT`
+
+Maximum amount of DNS servers that can be configured in one DNS socket. Default: 1.
+
+### `DNS_MAX_NAME_SIZE`
+
+Maximum length of DNS names that can be queried. Default: 255.
+
+### IPV6_HBH_MAX_OPTIONS
+
+The maximum amount of parsed options the IPv6 Hop-by-Hop header can hold. Default: 1.
+
+## Hosted usage examples
+
+_smoltcp_, being a freestanding networking stack, needs to be able to transmit and receive
+raw frames. For testing purposes, we will use a regular OS, and run _smoltcp_ in
+a userspace process. Only Linux is supported (right now).
+
+On \*nix OSes, transmitting and receiving raw frames normally requires superuser privileges, but
+on Linux it is possible to create a _persistent tap interface_ that can be manipulated by
+a specific user:
+
+```sh
+sudo ip tuntap add name tap0 mode tap user $USER
+sudo ip link set tap0 up
+sudo ip addr add 192.168.69.100/24 dev tap0
+sudo ip -6 addr add fe80::100/64 dev tap0
+sudo ip -6 addr add fdaa::100/64 dev tap0
+sudo ip -6 route add fe80::/64 dev tap0
+sudo ip -6 route add fdaa::/64 dev tap0
+```
+
+It's possible to let _smoltcp_ access Internet by enabling routing for the tap interface:
+
+```sh
+sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE
+sudo sysctl net.ipv4.ip_forward=1
+sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE
+sudo sysctl -w net.ipv6.conf.all.forwarding=1
+
+# Some distros have a default policy of DROP. This allows the traffic.
+sudo iptables -A FORWARD -i tap0 -s 192.168.69.0/24 -j ACCEPT
+sudo iptables -A FORWARD -o tap0 -d 192.168.69.0/24 -j ACCEPT
+```
+
+### Bridged connection
+
+Instead of the routed connection above, you may also set up a bridged (switched)
+connection. This will make smoltcp speak directly to your LAN, with real ARP, etc.
+It is needed to run the DHCP example.
+
+NOTE: In this case, the examples' IP configuration must match your LAN's!
+
+NOTE: this ONLY works with actual wired Ethernet connections. It
+will NOT work on a WiFi connection.
+
+```sh
+# Replace with your wired Ethernet interface name
+ETH=enp0s20f0u1u1
+
+sudo modprobe bridge
+sudo modprobe br_netfilter
+
+sudo sysctl -w net.bridge.bridge-nf-call-arptables=0
+sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0
+sudo sysctl -w net.bridge.bridge-nf-call-iptables=0
+
+sudo ip tuntap add name tap0 mode tap user $USER
+sudo brctl addbr br0
+sudo brctl addif br0 tap0
+sudo brctl addif br0 $ETH
+sudo ip link set tap0 up
+sudo ip link set $ETH up
+sudo ip link set br0 up
+
+# This connects your host system to the internet, so you can use it
+# at the same time you run the examples.
+sudo dhcpcd br0
+```
+
+To tear down:
+
+```
+sudo killall dhcpcd
+sudo ip link set br0 down
+sudo brctl delbr br0
+```
+
+### Fault injection
+
+In order to demonstrate the response of _smoltcp_ to adverse network conditions, all examples
+implement fault injection, available through command-line options:
+
+ * The `--drop-chance` option randomly drops packets, with given probability in percents.
+ * The `--corrupt-chance` option randomly mutates one octet in a packet, with given
+ probability in percents.
+ * The `--size-limit` option drops packets larger than specified size.
+ * The `--tx-rate-limit` and `--rx-rate-limit` options set the amount of tokens for
+ a token bucket rate limiter, in packets per bucket.
+ * The `--shaping-interval` option sets the refill interval of a token bucket rate limiter,
+ in milliseconds.
+
+A good starting value for `--drop-chance` and `--corrupt-chance` is 15%. A good starting
+value for `--?x-rate-limit` is 4 and `--shaping-interval` is 50 ms.
+
+Note that packets dropped by the fault injector still get traced;
+the `rx: randomly dropping a packet` message indicates that the packet *above* it got dropped,
+and the `tx: randomly dropping a packet` message indicates that the packet *below* it was.
+
+### Packet dumps
+
+All examples provide a `--pcap` option that writes a [libpcap] file containing a view of every
+packet as it is seen by _smoltcp_.
+
+[libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
+
+### examples/tcpdump.rs
+
+_examples/tcpdump.rs_ is a tiny clone of the _tcpdump_ utility.
+
+Unlike the rest of the examples, it uses raw sockets, and so it can be used on regular interfaces,
+e.g. `eth0` or `wlan0`, as well as the `tap0` interface we've created above.
+
+Read its [source code](/examples/tcpdump.rs), then run it as:
+
+```sh
+cargo build --example tcpdump
+sudo ./target/debug/examples/tcpdump eth0
+```
+
+### examples/httpclient.rs
+
+_examples/httpclient.rs_ emulates a network host that can initiate HTTP requests.
+
+The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192.168.69.1`, and IPv6 address `fdaa::1`.
+
+Read its [source code](/examples/httpclient.rs), then run it as:
+
+```sh
+cargo run --example httpclient -- --tap tap0 ADDRESS URL
+```
+
+For example:
+
+```sh
+cargo run --example httpclient -- --tap tap0 93.184.216.34 http://example.org/
+```
+
+or:
+
+```sh
+cargo run --example httpclient -- --tap tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
+```
+
+It connects to the given address (not a hostname) and URL, and prints any returned response data.
+The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting.
+
+### examples/ping.rs
+
+_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets.
+
+The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
+
+Read its [source code](/examples/ping.rs), then run it as:
+
+```sh
+cargo run --example ping -- --tap tap0 ADDRESS
+```
+
+It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
+prints out a status line on each valid ECHO\_RESPONSE received.
+
+The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup;
+the ECHO\_REQUEST packet is dropped and an ARP request is sent instead.
+
+Currently, netmasks are not implemented, and so the only address this example can reach
+is the other endpoint of the tap interface, `192.168.69.100`. It cannot reach itself because
+packets entering a tap interface do not loop back.
+
+### examples/server.rs
+
+_examples/server.rs_ emulates a network host that can respond to basic requests.
+
+The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `192.168.69.1`.
+
+Read its [source code](/examples/server.rs), then run it as:
+
+```sh
+cargo run --example server -- --tap tap0
+```
+
+It responds to:
+
+ * pings (`ping 192.168.69.1`);
+ * UDP packets on port 6969 (`socat stdio udp4-connect:192.168.69.1:6969 <<<"abcdefg"`),
+ where it will respond with reversed chunks of the input indefinitely;
+ * TCP connections on port 6969 (`socat stdio tcp4-connect:192.168.69.1:6969`),
+ where it will respond "hello" to any incoming connection and immediately close it;
+ * TCP connections on port 6970 (`socat stdio tcp4-connect:192.168.69.1:6970 <<<"abcdefg"`),
+ where it will respond with reversed chunks of the input indefinitely.
+ * TCP connections on port 6971 (`socat stdio tcp4-connect:192.168.69.1:6971 </dev/urandom`),
+ which will sink data. Also, keep-alive packets (every 1 s) and a user timeout (at 2 s)
+ are enabled on this port; try to trigger them using fault injection.
+ * TCP connections on port 6972 (`socat stdio tcp4-connect:192.168.69.1:6972 >/dev/null`),
+ which will source data.
+
+Except for the socket on port 6971. the buffers are only 64 bytes long, for convenience
+of testing resource exhaustion conditions.
+
+### examples/client.rs
+
+_examples/client.rs_ emulates a network host that can initiate basic requests.
+
+The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.2`.
+
+Read its [source code](/examples/client.rs), then run it as:
+
+```sh
+cargo run --example client -- --tap tap0 ADDRESS PORT
+```
+
+It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen:1234`),
+and will respond with reversed chunks of the input indefinitely.
+
+### examples/benchmark.rs
+
+_examples/benchmark.rs_ implements a simple throughput benchmark.
+
+Read its [source code](/examples/benchmark.rs), then run it as:
+
+```sh
+cargo run --release --example benchmark -- --tap tap0 [reader|writer]
+```
+
+It establishes a connection to itself from a different thread and reads or writes a large amount
+of data in one direction.
+
+A typical result (achieved on a Intel Core i7-7500U CPU and a Linux 4.9.65 x86_64 kernel running
+on a Dell XPS 13 9360 laptop) is as follows:
+
+```
+$ cargo run -q --release --example benchmark -- --tap tap0 reader
+throughput: 2.556 Gbps
+$ cargo run -q --release --example benchmark -- --tap tap0 writer
+throughput: 5.301 Gbps
+```
+
+## Bare-metal usage examples
+
+Examples that use no services from the host OS are necessarily less illustrative than examples
+that do. Because of this, only one such example is provided.
+
+### examples/loopback.rs
+
+_examples/loopback.rs_ sets up _smoltcp_ to talk with itself via a loopback interface.
+Although it does not require `std`, this example still requires the `alloc` feature to run, as well as `log`, `proto-ipv4` and `socket-tcp`.
+
+Read its [source code](/examples/loopback.rs), then run it without `std`:
+
+```sh
+cargo run --example loopback --no-default-features --features="log proto-ipv4 socket-tcp alloc"
+```
+
+... or with `std` (in this case the features don't have to be explicitly listed):
+
+```sh
+cargo run --example loopback -- --pcap loopback.pcap
+```
+
+It opens a server and a client TCP socket, and transfers a chunk of data. You can examine
+the packet exchange by opening `loopback.pcap` in [Wireshark].
+
+If the `std` feature is enabled, it will print logs and packet dumps, and fault injection
+is possible; otherwise, nothing at all will be displayed and no options are accepted.
+
+[wireshark]: https://wireshark.org
+
+## License
+
+_smoltcp_ is distributed under the terms of 0-clause BSD license.
+
+See [LICENSE-0BSD](LICENSE-0BSD.txt) for details.
diff --git a/benches/bench.rs b/benches/bench.rs
new file mode 100644
index 0000000..2738840
--- /dev/null
+++ b/benches/bench.rs
@@ -0,0 +1,117 @@
+#![feature(test)]
+
+mod wire {
+ use smoltcp::phy::ChecksumCapabilities;
+ use smoltcp::wire::{IpAddress, IpProtocol};
+ #[cfg(feature = "proto-ipv4")]
+ use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr};
+ #[cfg(feature = "proto-ipv6")]
+ use smoltcp::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr};
+ use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber};
+ use smoltcp::wire::{UdpPacket, UdpRepr};
+
+ extern crate test;
+
+ #[cfg(feature = "proto-ipv6")]
+ const SRC_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ ]));
+ #[cfg(feature = "proto-ipv6")]
+ const DST_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ ]));
+
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ const SRC_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 1]));
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ const DST_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 2]));
+
+ #[bench]
+ #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))]
+ fn bench_emit_tcp(b: &mut test::Bencher) {
+ static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400];
+ let repr = TcpRepr {
+ src_port: 48896,
+ dst_port: 80,
+ control: TcpControl::Syn,
+ seq_number: TcpSeqNumber(0x01234567),
+ ack_number: None,
+ window_len: 0x0123,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &PAYLOAD_BYTES,
+ };
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+
+ b.iter(|| {
+ let mut packet = TcpPacket::new_unchecked(&mut bytes);
+ repr.emit(
+ &mut packet,
+ &SRC_ADDR,
+ &DST_ADDR,
+ &ChecksumCapabilities::default(),
+ );
+ });
+ }
+
+ #[bench]
+ #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))]
+ fn bench_emit_udp(b: &mut test::Bencher) {
+ static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400];
+ let repr = UdpRepr {
+ src_port: 48896,
+ dst_port: 80,
+ };
+ let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()];
+
+ b.iter(|| {
+ let mut packet = UdpPacket::new_unchecked(&mut bytes);
+ repr.emit(
+ &mut packet,
+ &SRC_ADDR,
+ &DST_ADDR,
+ PAYLOAD_BYTES.len(),
+ |buf| buf.copy_from_slice(&PAYLOAD_BYTES),
+ &ChecksumCapabilities::default(),
+ );
+ });
+ }
+
+ #[bench]
+ #[cfg(feature = "proto-ipv4")]
+ fn bench_emit_ipv4(b: &mut test::Bencher) {
+ let repr = Ipv4Repr {
+ src_addr: Ipv4Address([192, 168, 1, 1]),
+ dst_addr: Ipv4Address([192, 168, 1, 2]),
+ next_header: IpProtocol::Tcp,
+ payload_len: 100,
+ hop_limit: 64,
+ };
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+
+ b.iter(|| {
+ let mut packet = Ipv4Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet, &ChecksumCapabilities::default());
+ });
+ }
+
+ #[bench]
+ #[cfg(feature = "proto-ipv6")]
+ fn bench_emit_ipv6(b: &mut test::Bencher) {
+ let repr = Ipv6Repr {
+ src_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
+ dst_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]),
+ next_header: IpProtocol::Tcp,
+ payload_len: 100,
+ hop_limit: 64,
+ };
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+
+ b.iter(|| {
+ let mut packet = Ipv6Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet);
+ });
+ }
+}
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..54662ed
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,104 @@
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::path::PathBuf;
+use std::{env, fs};
+
+static CONFIGS: &[(&str, usize)] = &[
+ // BEGIN AUTOGENERATED CONFIG FEATURES
+ // Generated by gen_config.py. DO NOT EDIT.
+ ("IFACE_MAX_ADDR_COUNT", 2),
+ ("IFACE_MAX_MULTICAST_GROUP_COUNT", 4),
+ ("IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT", 4),
+ ("IFACE_NEIGHBOR_CACHE_COUNT", 4),
+ ("IFACE_MAX_ROUTE_COUNT", 2),
+ ("FRAGMENTATION_BUFFER_SIZE", 1500),
+ ("ASSEMBLER_MAX_SEGMENT_COUNT", 4),
+ ("REASSEMBLY_BUFFER_SIZE", 1500),
+ ("REASSEMBLY_BUFFER_COUNT", 1),
+ ("IPV6_HBH_MAX_OPTIONS", 1),
+ ("DNS_MAX_RESULT_COUNT", 1),
+ ("DNS_MAX_SERVER_COUNT", 1),
+ ("DNS_MAX_NAME_SIZE", 255),
+ ("RPL_RELATIONS_BUFFER_COUNT", 16),
+ ("RPL_PARENTS_BUFFER_COUNT", 8),
+ // END AUTOGENERATED CONFIG FEATURES
+];
+
+struct ConfigState {
+ value: usize,
+ seen_feature: bool,
+ seen_env: bool,
+}
+
+fn main() {
+ // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
+ // other file changed.
+ println!("cargo:rerun-if-changed=build.rs");
+
+ // Rebuild if config envvar changed.
+ for (name, _) in CONFIGS {
+ println!("cargo:rerun-if-env-changed=SMOLTCP_{name}");
+ }
+
+ let mut configs = HashMap::new();
+ for (name, default) in CONFIGS {
+ configs.insert(
+ *name,
+ ConfigState {
+ value: *default,
+ seen_env: false,
+ seen_feature: false,
+ },
+ );
+ }
+
+ for (var, value) in env::vars() {
+ if let Some(name) = var.strip_prefix("SMOLTCP_") {
+ let Some(cfg) = configs.get_mut(name) else {
+ panic!("Unknown env var {name}")
+ };
+
+ let Ok(value) = value.parse::<usize>() else {
+ panic!("Invalid value for env var {name}: {value}")
+ };
+
+ cfg.value = value;
+ cfg.seen_env = true;
+ }
+
+ if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
+ if let Some(i) = feature.rfind('_') {
+ let name = &feature[..i];
+ let value = &feature[i + 1..];
+ if let Some(cfg) = configs.get_mut(name) {
+ let Ok(value) = value.parse::<usize>() else {
+ panic!("Invalid value for feature {name}: {value}")
+ };
+
+ // envvars take priority.
+ if !cfg.seen_env {
+ if cfg.seen_feature {
+ panic!(
+ "multiple values set for feature {}: {} and {}",
+ name, cfg.value, value
+ );
+ }
+
+ cfg.value = value;
+ cfg.seen_feature = true;
+ }
+ }
+ }
+ }
+ }
+
+ let mut data = String::new();
+
+ for (name, cfg) in &configs {
+ writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
+ }
+
+ let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
+ fs::write(out_file, data).unwrap();
+}
diff --git a/ci.sh b/ci.sh
new file mode 100755
index 0000000..ec20cc7
--- /dev/null
+++ b/ci.sh
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+
+set -eox pipefail
+
+export DEFMT_LOG=trace
+
+MSRV="1.65.0"
+
+RUSTC_VERSIONS=(
+ $MSRV
+ "stable"
+ "nightly"
+)
+
+FEATURES_TEST=(
+ "default"
+ "std,proto-ipv4"
+ "std,medium-ethernet,phy-raw_socket,proto-ipv6,socket-udp,socket-dns"
+ "std,medium-ethernet,phy-tuntap_interface,proto-ipv6,socket-udp"
+ "std,medium-ethernet,proto-ipv4,proto-ipv4-fragmentation,socket-raw,socket-dns"
+ "std,medium-ethernet,proto-ipv4,proto-igmp,socket-raw,socket-dns"
+ "std,medium-ethernet,proto-ipv4,socket-udp,socket-tcp,socket-dns"
+ "std,medium-ethernet,proto-ipv4,proto-dhcpv4,socket-udp"
+ "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv6,socket-udp,socket-dns"
+ "std,medium-ethernet,proto-ipv6,socket-tcp"
+ "std,medium-ethernet,medium-ip,proto-ipv4,socket-icmp,socket-tcp"
+ "std,medium-ip,proto-ipv6,socket-icmp,socket-tcp"
+ "std,medium-ieee802154,proto-sixlowpan,socket-udp"
+ "std,medium-ieee802154,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp"
+ "std,medium-ieee802154,proto-rpl,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp"
+ "std,medium-ip,proto-ipv4,proto-ipv6,socket-tcp,socket-udp"
+ "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv4,proto-ipv6,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async"
+ "std,medium-ieee802154,medium-ip,proto-ipv4,socket-raw"
+ "std,medium-ethernet,proto-ipv4,proto-ipsec,socket-raw"
+)
+
+FEATURES_TEST_NIGHTLY=(
+ "alloc,medium-ethernet,proto-ipv4,proto-ipv6,socket-raw,socket-udp,socket-tcp,socket-icmp"
+)
+
+FEATURES_CHECK=(
+ "medium-ip,medium-ethernet,medium-ieee802154,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,proto-ipsec,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async"
+ "defmt,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async"
+ "defmt,alloc,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async"
+)
+
+test() {
+ local version=$1
+ rustup toolchain install $version
+
+ for features in ${FEATURES_TEST[@]}; do
+ cargo +$version test --no-default-features --features "$features"
+ done
+
+ if [[ $version == "nightly" ]]; then
+ for features in ${FEATURES_TEST_NIGHTLY[@]}; do
+ cargo +$version test --no-default-features --features "$features"
+ done
+ fi
+}
+
+check() {
+ local version=$1
+ rustup toolchain install $version
+
+ export DEFMT_LOG="trace"
+
+ for features in ${FEATURES_CHECK[@]}; do
+ cargo +$version check --no-default-features --features "$features"
+ done
+}
+
+clippy() {
+ rustup toolchain install $MSRV
+ rustup component add clippy --toolchain=$MSRV
+ cargo +$MSRV clippy --tests --examples -- -D warnings
+}
+
+coverage() {
+ for features in ${FEATURES_TEST[@]}; do
+ cargo llvm-cov --no-report --no-default-features --features "$features"
+ done
+ cargo llvm-cov report --lcov --output-path lcov.info
+}
+
+if [[ $1 == "test" || $1 == "all" ]]; then
+ if [[ -n $2 ]]; then
+ if [[ $2 == "msrv" ]]; then
+ test $MSRV
+ else
+ test $2
+ fi
+ else
+ for version in ${RUSTC_VERSIONS[@]}; do
+ test $version
+ done
+ fi
+fi
+
+if [[ $1 == "check" || $1 == "all" ]]; then
+ if [[ -n $2 ]]; then
+ if [[ $2 == "msrv" ]]; then
+ check $MSRV
+ else
+ check $2
+ fi
+ else
+ for version in ${RUSTC_VERSIONS[@]}; do
+ check $version
+ done
+ fi
+fi
+
+if [[ $1 == "clippy" || $1 == "all" ]]; then
+ clippy
+fi
+
+if [[ $1 == "coverage" || $1 == "all" ]]; then
+ coverage
+fi
diff --git a/examples/benchmark.rs b/examples/benchmark.rs
new file mode 100644
index 0000000..ad2c6e1
--- /dev/null
+++ b/examples/benchmark.rs
@@ -0,0 +1,164 @@
+#![allow(clippy::collapsible_if)]
+
+mod utils;
+
+use std::cmp;
+use std::io::{Read, Write};
+use std::net::TcpStream;
+use std::os::unix::io::AsRawFd;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium};
+use smoltcp::socket::tcp;
+use smoltcp::time::{Duration, Instant};
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
+
+const AMOUNT: usize = 1_000_000_000;
+
+enum Client {
+ Reader,
+ Writer,
+}
+
+fn client(kind: Client) {
+ let port = match kind {
+ Client::Reader => 1234,
+ Client::Writer => 1235,
+ };
+ let mut stream = TcpStream::connect(("192.168.69.1", port)).unwrap();
+ let mut buffer = vec![0; 1_000_000];
+
+ let start = Instant::now();
+
+ let mut processed = 0;
+ while processed < AMOUNT {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ let result = match kind {
+ Client::Reader => stream.read(&mut buffer[..length]),
+ Client::Writer => stream.write(&buffer[..length]),
+ };
+ match result {
+ Ok(0) => break,
+ Ok(result) => {
+ // print!("(P:{})", result);
+ processed += result
+ }
+ Err(err) => panic!("cannot process: {err}"),
+ }
+ }
+
+ let end = Instant::now();
+
+ let elapsed = (end - start).total_millis() as f64 / 1000.0;
+
+ println!("throughput: {:.3} Gbps", AMOUNT as f64 / elapsed / 0.125e9);
+
+ CLIENT_DONE.store(true, Ordering::SeqCst);
+}
+
+static CLIENT_DONE: AtomicBool = AtomicBool::new(false);
+
+fn main() {
+ #[cfg(feature = "log")]
+ utils::setup_logging("info");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+ free.push("MODE");
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+ let mode = match matches.free[0].as_ref() {
+ "reader" => Client::Reader,
+ "writer" => Client::Writer,
+ _ => panic!("invalid mode"),
+ };
+
+ let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer);
+
+ let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer);
+
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ });
+
+ let mut sockets = SocketSet::new(vec![]);
+ let tcp1_handle = sockets.add(tcp1_socket);
+ let tcp2_handle = sockets.add(tcp2_socket);
+ let default_timeout = Some(Duration::from_millis(1000));
+
+ thread::spawn(move || client(mode));
+ let mut processed = 0;
+ while !CLIENT_DONE.load(Ordering::SeqCst) {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ // tcp:1234: emit data
+ let socket = sockets.get_mut::<tcp::Socket>(tcp1_handle);
+ if !socket.is_open() {
+ socket.listen(1234).unwrap();
+ }
+
+ if socket.can_send() {
+ if processed < AMOUNT {
+ let length = socket
+ .send(|buffer| {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ (length, length)
+ })
+ .unwrap();
+ processed += length;
+ }
+ }
+
+ // tcp:1235: sink data
+ let socket = sockets.get_mut::<tcp::Socket>(tcp2_handle);
+ if !socket.is_open() {
+ socket.listen(1235).unwrap();
+ }
+
+ if socket.can_recv() {
+ if processed < AMOUNT {
+ let length = socket
+ .recv(|buffer| {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ (length, length)
+ })
+ .unwrap();
+ processed += length;
+ }
+ }
+
+ match iface.poll_at(timestamp, &sockets) {
+ Some(poll_at) if timestamp < poll_at => {
+ phy_wait(fd, Some(poll_at - timestamp)).expect("wait error");
+ }
+ Some(_) => (),
+ None => {
+ phy_wait(fd, default_timeout).expect("wait error");
+ }
+ }
+ }
+}
diff --git a/examples/client.rs b/examples/client.rs
new file mode 100644
index 0000000..c18c08f
--- /dev/null
+++ b/examples/client.rs
@@ -0,0 +1,118 @@
+mod utils;
+
+use log::debug;
+use std::os::unix::io::AsRawFd;
+use std::str::{self, FromStr};
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium};
+use smoltcp::socket::tcp;
+use smoltcp::time::Instant;
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
+
+fn main() {
+ utils::setup_logging("");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+ free.push("ADDRESS");
+ free.push("PORT");
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+ let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
+ let port = u16::from_str(&matches.free[1]).expect("invalid port format");
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1500]);
+ let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1500]);
+ let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
+ let mut sockets = SocketSet::new(vec![]);
+ let tcp_handle = sockets.add(tcp_socket);
+
+ let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+ socket
+ .connect(iface.context(), (address, port), 49500)
+ .unwrap();
+
+ let mut tcp_active = false;
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+ if socket.is_active() && !tcp_active {
+ debug!("connected");
+ } else if !socket.is_active() && tcp_active {
+ debug!("disconnected");
+ break;
+ }
+ tcp_active = socket.is_active();
+
+ if socket.may_recv() {
+ let data = socket
+ .recv(|data| {
+ let mut data = data.to_owned();
+ if !data.is_empty() {
+ debug!(
+ "recv data: {:?}",
+ str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
+ );
+ data = data.split(|&b| b == b'\n').collect::<Vec<_>>().concat();
+ data.reverse();
+ data.extend(b"\n");
+ }
+ (data.len(), data)
+ })
+ .unwrap();
+ if socket.can_send() && !data.is_empty() {
+ debug!(
+ "send data: {:?}",
+ str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
+ );
+ socket.send_slice(&data[..]).unwrap();
+ }
+ } else if socket.may_send() {
+ debug!("close");
+ socket.close();
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/dhcp_client.rs b/examples/dhcp_client.rs
new file mode 100644
index 0000000..9ef46c2
--- /dev/null
+++ b/examples/dhcp_client.rs
@@ -0,0 +1,94 @@
+#![allow(clippy::option_map_unit_fn)]
+mod utils;
+
+use log::*;
+use std::os::unix::io::AsRawFd;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::socket::dhcpv4;
+use smoltcp::time::Instant;
+use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Address, Ipv4Cidr};
+use smoltcp::{
+ phy::{wait as phy_wait, Device, Medium},
+ time::Duration,
+};
+
+fn main() {
+ #[cfg(feature = "log")]
+ utils::setup_logging("");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+
+ // Create sockets
+ let mut dhcp_socket = dhcpv4::Socket::new();
+
+ // Set a ridiculously short max lease time to show DHCP renews work properly.
+ // This will cause the DHCP client to start renewing after 5 seconds, and give up the
+ // lease after 10 seconds if renew hasn't succeeded.
+ // IMPORTANT: This should be removed in production.
+ dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10)));
+
+ let mut sockets = SocketSet::new(vec![]);
+ let dhcp_handle = sockets.add(dhcp_socket);
+
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ let event = sockets.get_mut::<dhcpv4::Socket>(dhcp_handle).poll();
+ match event {
+ None => {}
+ Some(dhcpv4::Event::Configured(config)) => {
+ debug!("DHCP config acquired!");
+
+ debug!("IP address: {}", config.address);
+ set_ipv4_addr(&mut iface, config.address);
+
+ if let Some(router) = config.router {
+ debug!("Default gateway: {}", router);
+ iface.routes_mut().add_default_ipv4_route(router).unwrap();
+ } else {
+ debug!("Default gateway: None");
+ iface.routes_mut().remove_default_ipv4_route();
+ }
+
+ for (i, s) in config.dns_servers.iter().enumerate() {
+ debug!("DNS server {}: {}", i, s);
+ }
+ }
+ Some(dhcpv4::Event::Deconfigured) => {
+ debug!("DHCP lost config!");
+ set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
+ iface.routes_mut().remove_default_ipv4_route();
+ }
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
+
+fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) {
+ iface.update_ip_addrs(|addrs| {
+ let dest = addrs.iter_mut().next().unwrap();
+ *dest = IpCidr::Ipv4(cidr);
+ });
+}
diff --git a/examples/dns.rs b/examples/dns.rs
new file mode 100644
index 0000000..977f405
--- /dev/null
+++ b/examples/dns.rs
@@ -0,0 +1,92 @@
+mod utils;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::Device;
+use smoltcp::phy::{wait as phy_wait, Medium};
+use smoltcp::socket::dns::{self, GetQueryResultError};
+use smoltcp::time::Instant;
+use smoltcp::wire::{DnsQueryType, EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
+use std::os::unix::io::AsRawFd;
+
+fn main() {
+ utils::setup_logging("warn");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+ free.push("ADDRESS");
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+ let name = &matches.free[0];
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let servers = &[
+ Ipv4Address::new(8, 8, 4, 4).into(),
+ Ipv4Address::new(8, 8, 8, 8).into(),
+ ];
+ let dns_socket = dns::Socket::new(servers, vec![]);
+
+ let mut sockets = SocketSet::new(vec![]);
+ let dns_handle = sockets.add(dns_socket);
+
+ let socket = sockets.get_mut::<dns::Socket>(dns_handle);
+ let query = socket
+ .start_query(iface.context(), name, DnsQueryType::A)
+ .unwrap();
+
+ loop {
+ let timestamp = Instant::now();
+ log::debug!("timestamp {:?}", timestamp);
+
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ match sockets
+ .get_mut::<dns::Socket>(dns_handle)
+ .get_query_result(query)
+ {
+ Ok(addrs) => {
+ println!("Query done: {addrs:?}");
+ break;
+ }
+ Err(GetQueryResultError::Pending) => {} // not done yet
+ Err(e) => panic!("query failed: {e:?}"),
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/httpclient.rs b/examples/httpclient.rs
new file mode 100644
index 0000000..8f3a53a
--- /dev/null
+++ b/examples/httpclient.rs
@@ -0,0 +1,123 @@
+mod utils;
+
+use log::debug;
+use std::os::unix::io::AsRawFd;
+use std::str::{self, FromStr};
+use url::Url;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium};
+use smoltcp::socket::tcp;
+use smoltcp::time::Instant;
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
+
+fn main() {
+ utils::setup_logging("");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+ free.push("ADDRESS");
+ free.push("URL");
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+ let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
+ let url = Url::parse(&matches.free[1]).expect("invalid url format");
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
+ let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
+ let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
+
+ let mut sockets = SocketSet::new(vec![]);
+ let tcp_handle = sockets.add(tcp_socket);
+
+ enum State {
+ Connect,
+ Request,
+ Response,
+ }
+ let mut state = State::Connect;
+
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+ let cx = iface.context();
+
+ state = match state {
+ State::Connect if !socket.is_active() => {
+ debug!("connecting");
+ let local_port = 49152 + rand::random::<u16>() % 16384;
+ socket
+ .connect(cx, (address, url.port().unwrap_or(80)), local_port)
+ .unwrap();
+ State::Request
+ }
+ State::Request if socket.may_send() => {
+ debug!("sending request");
+ let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n";
+ socket.send_slice(http_get.as_ref()).expect("cannot send");
+ let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n";
+ socket.send_slice(http_host.as_ref()).expect("cannot send");
+ socket
+ .send_slice(b"Connection: close\r\n")
+ .expect("cannot send");
+ socket.send_slice(b"\r\n").expect("cannot send");
+ State::Response
+ }
+ State::Response if socket.can_recv() => {
+ socket
+ .recv(|data| {
+ println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)"));
+ (data.len(), ())
+ })
+ .unwrap();
+ State::Response
+ }
+ State::Response if !socket.may_recv() => {
+ debug!("received complete response");
+ break;
+ }
+ _ => state,
+ };
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/loopback.rs b/examples/loopback.rs
new file mode 100644
index 0000000..7ca95b1
--- /dev/null
+++ b/examples/loopback.rs
@@ -0,0 +1,184 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+#![allow(unused_mut)]
+#![allow(clippy::collapsible_if)]
+
+#[cfg(feature = "std")]
+#[allow(dead_code)]
+mod utils;
+
+use core::str;
+use log::{debug, error, info};
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{Device, Loopback, Medium};
+use smoltcp::socket::tcp;
+use smoltcp::time::{Duration, Instant};
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
+
+#[cfg(not(feature = "std"))]
+mod mock {
+ use core::cell::Cell;
+ use smoltcp::time::{Duration, Instant};
+
+ #[derive(Debug)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct Clock(Cell<Instant>);
+
+ impl Clock {
+ pub fn new() -> Clock {
+ Clock(Cell::new(Instant::from_millis(0)))
+ }
+
+ pub fn advance(&self, duration: Duration) {
+ self.0.set(self.0.get() + duration)
+ }
+
+ pub fn elapsed(&self) -> Instant {
+ self.0.get()
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+mod mock {
+ use smoltcp::time::{Duration, Instant};
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ use std::sync::Arc;
+
+ // should be AtomicU64 but that's unstable
+ #[derive(Debug, Clone)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct Clock(Arc<AtomicUsize>);
+
+ impl Clock {
+ pub fn new() -> Clock {
+ Clock(Arc::new(AtomicUsize::new(0)))
+ }
+
+ pub fn advance(&self, duration: Duration) {
+ self.0
+ .fetch_add(duration.total_millis() as usize, Ordering::SeqCst);
+ }
+
+ pub fn elapsed(&self) -> Instant {
+ Instant::from_millis(self.0.load(Ordering::SeqCst) as i64)
+ }
+ }
+}
+
+fn main() {
+ let clock = mock::Clock::new();
+ let device = Loopback::new(Medium::Ethernet);
+
+ #[cfg(feature = "std")]
+ let mut device = {
+ let clock = clock.clone();
+ utils::setup_logging_with_clock("", move || clock.elapsed());
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_middleware_options(&mut opts, &mut free);
+
+ let mut matches = utils::parse_options(&opts, free);
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true)
+ };
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
+ .unwrap();
+ });
+
+ // Create sockets
+ let server_socket = {
+ // It is not strictly necessary to use a `static mut` and unsafe code here, but
+ // on embedded systems that smoltcp targets it is far better to allocate the data
+ // statically to verify that it fits into RAM rather than get undefined behavior
+ // when stack overflows.
+ static mut TCP_SERVER_RX_DATA: [u8; 1024] = [0; 1024];
+ static mut TCP_SERVER_TX_DATA: [u8; 1024] = [0; 1024];
+ let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] });
+ let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] });
+ tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer)
+ };
+
+ let client_socket = {
+ static mut TCP_CLIENT_RX_DATA: [u8; 1024] = [0; 1024];
+ static mut TCP_CLIENT_TX_DATA: [u8; 1024] = [0; 1024];
+ let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_RX_DATA[..] });
+ let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_TX_DATA[..] });
+ tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer)
+ };
+
+ let mut sockets: [_; 2] = Default::default();
+ let mut sockets = SocketSet::new(&mut sockets[..]);
+ let server_handle = sockets.add(server_socket);
+ let client_handle = sockets.add(client_socket);
+
+ let mut did_listen = false;
+ let mut did_connect = false;
+ let mut done = false;
+ while !done && clock.elapsed() < Instant::from_millis(10_000) {
+ iface.poll(clock.elapsed(), &mut device, &mut sockets);
+
+ let mut socket = sockets.get_mut::<tcp::Socket>(server_handle);
+ if !socket.is_active() && !socket.is_listening() {
+ if !did_listen {
+ debug!("listening");
+ socket.listen(1234).unwrap();
+ did_listen = true;
+ }
+ }
+
+ if socket.can_recv() {
+ debug!(
+ "got {:?}",
+ socket.recv(|buffer| { (buffer.len(), str::from_utf8(buffer).unwrap()) })
+ );
+ socket.close();
+ done = true;
+ }
+
+ let mut socket = sockets.get_mut::<tcp::Socket>(client_handle);
+ let cx = iface.context();
+ if !socket.is_open() {
+ if !did_connect {
+ debug!("connecting");
+ socket
+ .connect(cx, (IpAddress::v4(127, 0, 0, 1), 1234), 65000)
+ .unwrap();
+ did_connect = true;
+ }
+ }
+
+ if socket.can_send() {
+ debug!("sending");
+ socket.send_slice(b"0123456789abcdef").unwrap();
+ socket.close();
+ }
+
+ match iface.poll_delay(clock.elapsed(), &sockets) {
+ Some(Duration::ZERO) => debug!("resuming"),
+ Some(delay) => {
+ debug!("sleeping for {} ms", delay);
+ clock.advance(delay)
+ }
+ None => clock.advance(Duration::from_millis(1)),
+ }
+ }
+
+ if done {
+ info!("done")
+ } else {
+ error!("this is taking too long, bailing out")
+ }
+}
diff --git a/examples/multicast.rs b/examples/multicast.rs
new file mode 100644
index 0000000..ea89a2e
--- /dev/null
+++ b/examples/multicast.rs
@@ -0,0 +1,129 @@
+mod utils;
+
+use std::os::unix::io::AsRawFd;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium};
+use smoltcp::socket::{raw, udp};
+use smoltcp::time::Instant;
+use smoltcp::wire::{
+ EthernetAddress, IgmpPacket, IgmpRepr, IpAddress, IpCidr, IpProtocol, IpVersion, Ipv4Address,
+ Ipv4Packet, Ipv6Address,
+};
+
+const MDNS_PORT: u16 = 5353;
+const MDNS_GROUP: [u8; 4] = [224, 0, 0, 251];
+
+fn main() {
+ utils::setup_logging("warn");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let mut sockets = SocketSet::new(vec![]);
+
+ // Must fit at least one IGMP packet
+ let raw_rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; 2], vec![0; 512]);
+ // Will not send IGMP
+ let raw_tx_buffer = raw::PacketBuffer::new(vec![], vec![]);
+ let raw_socket = raw::Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Igmp,
+ raw_rx_buffer,
+ raw_tx_buffer,
+ );
+ let raw_handle = sockets.add(raw_socket);
+
+ // Must fit mDNS payload of at least one packet
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]);
+ // Will not send mDNS
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_handle = sockets.add(udp_socket);
+
+ // Join a multicast group to receive mDNS traffic
+ iface
+ .join_multicast_group(
+ &mut device,
+ Ipv4Address::from_bytes(&MDNS_GROUP),
+ Instant::now(),
+ )
+ .unwrap();
+
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ let socket = sockets.get_mut::<raw::Socket>(raw_handle);
+
+ if socket.can_recv() {
+ // For display purposes only - normally we wouldn't process incoming IGMP packets
+ // in the application layer
+ match socket.recv() {
+ Err(e) => println!("Recv IGMP error: {e:?}"),
+ Ok(buf) => {
+ Ipv4Packet::new_checked(buf)
+ .and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload()))
+ .and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet))
+ .map(|igmp_repr| println!("IGMP packet: {igmp_repr:?}"))
+ .unwrap_or_else(|e| println!("parse IGMP error: {e:?}"));
+ }
+ }
+ }
+
+ let socket = sockets.get_mut::<udp::Socket>(udp_handle);
+ if !socket.is_open() {
+ socket.bind(MDNS_PORT).unwrap()
+ }
+
+ if socket.can_recv() {
+ socket
+ .recv()
+ .map(|(data, sender)| {
+ println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender)
+ })
+ .unwrap_or_else(|e| println!("Recv UDP error: {e:?}"));
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/ping.rs b/examples/ping.rs
new file mode 100644
index 0000000..7e33a21
--- /dev/null
+++ b/examples/ping.rs
@@ -0,0 +1,281 @@
+mod utils;
+
+use byteorder::{ByteOrder, NetworkEndian};
+use smoltcp::iface::{Interface, SocketSet};
+use std::cmp;
+use std::collections::HashMap;
+use std::os::unix::io::AsRawFd;
+use std::str::FromStr;
+
+use smoltcp::iface::Config;
+use smoltcp::phy::wait as phy_wait;
+use smoltcp::phy::Device;
+use smoltcp::socket::icmp;
+use smoltcp::wire::{
+ EthernetAddress, Icmpv4Packet, Icmpv4Repr, Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr,
+ Ipv4Address, Ipv6Address,
+};
+use smoltcp::{
+ phy::Medium,
+ time::{Duration, Instant},
+};
+
+macro_rules! send_icmp_ping {
+ ( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr,
+ $echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{
+ let icmp_repr = $repr_type::EchoRequest {
+ ident: $ident,
+ seq_no: $seq_no,
+ data: &$echo_payload,
+ };
+
+ let icmp_payload = $socket.send(icmp_repr.buffer_len(), $remote_addr).unwrap();
+
+ let icmp_packet = $packet_type::new_unchecked(icmp_payload);
+ (icmp_repr, icmp_packet)
+ }};
+}
+
+macro_rules! get_icmp_pong {
+ ( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr,
+ $timestamp:expr, $received:expr ) => {{
+ if let $repr_type::EchoReply { seq_no, data, .. } = $repr {
+ if let Some(_) = $waiting_queue.get(&seq_no) {
+ let packet_timestamp_ms = NetworkEndian::read_i64(data);
+ println!(
+ "{} bytes from {}: icmp_seq={}, time={}ms",
+ data.len(),
+ $remote_addr,
+ seq_no,
+ $timestamp.total_millis() - packet_timestamp_ms
+ );
+ $waiting_queue.remove(&seq_no);
+ $received += 1;
+ }
+ }
+ }};
+}
+
+fn main() {
+ utils::setup_logging("warn");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+ opts.optopt(
+ "c",
+ "count",
+ "Amount of echo request packets to send (default: 4)",
+ "COUNT",
+ );
+ opts.optopt(
+ "i",
+ "interval",
+ "Interval between successive packets sent (seconds) (default: 1)",
+ "INTERVAL",
+ );
+ opts.optopt(
+ "",
+ "timeout",
+ "Maximum wait duration for an echo response packet (seconds) (default: 5)",
+ "TIMEOUT",
+ );
+ free.push("ADDRESS");
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+ let device_caps = device.capabilities();
+ let remote_addr = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
+ let count = matches
+ .opt_str("count")
+ .map(|s| usize::from_str(&s).unwrap())
+ .unwrap_or(4);
+ let interval = matches
+ .opt_str("interval")
+ .map(|s| Duration::from_secs(u64::from_str(&s).unwrap()))
+ .unwrap_or_else(|| Duration::from_secs(1));
+ let timeout = Duration::from_secs(
+ matches
+ .opt_str("timeout")
+ .map(|s| u64::from_str(&s).unwrap())
+ .unwrap_or(5),
+ );
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let icmp_rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 256]);
+ let icmp_tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 256]);
+ let icmp_socket = icmp::Socket::new(icmp_rx_buffer, icmp_tx_buffer);
+ let mut sockets = SocketSet::new(vec![]);
+ let icmp_handle = sockets.add(icmp_socket);
+
+ let mut send_at = Instant::from_millis(0);
+ let mut seq_no = 0;
+ let mut received = 0;
+ let mut echo_payload = [0xffu8; 40];
+ let mut waiting_queue = HashMap::new();
+ let ident = 0x22b;
+
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ let timestamp = Instant::now();
+ let socket = sockets.get_mut::<icmp::Socket>(icmp_handle);
+ if !socket.is_open() {
+ socket.bind(icmp::Endpoint::Ident(ident)).unwrap();
+ send_at = timestamp;
+ }
+
+ if socket.can_send() && seq_no < count as u16 && send_at <= timestamp {
+ NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis());
+
+ match remote_addr {
+ IpAddress::Ipv4(_) => {
+ let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
+ Icmpv4Repr,
+ Icmpv4Packet,
+ ident,
+ seq_no,
+ echo_payload,
+ socket,
+ remote_addr
+ );
+ icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
+ }
+ IpAddress::Ipv6(address) => {
+ let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
+ Icmpv6Repr,
+ Icmpv6Packet,
+ ident,
+ seq_no,
+ echo_payload,
+ socket,
+ remote_addr
+ );
+ icmp_repr.emit(
+ &iface
+ .get_source_address_ipv6(&address)
+ .unwrap()
+ .into_address(),
+ &remote_addr,
+ &mut icmp_packet,
+ &device_caps.checksum,
+ );
+ }
+ }
+
+ waiting_queue.insert(seq_no, timestamp);
+ seq_no += 1;
+ send_at += interval;
+ }
+
+ if socket.can_recv() {
+ let (payload, _) = socket.recv().unwrap();
+
+ match remote_addr {
+ IpAddress::Ipv4(_) => {
+ let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap();
+ let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
+ get_icmp_pong!(
+ Icmpv4Repr,
+ icmp_repr,
+ payload,
+ waiting_queue,
+ remote_addr,
+ timestamp,
+ received
+ );
+ }
+ IpAddress::Ipv6(address) => {
+ let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap();
+ let icmp_repr = Icmpv6Repr::parse(
+ &remote_addr,
+ &iface
+ .get_source_address_ipv6(&address)
+ .unwrap()
+ .into_address(),
+ &icmp_packet,
+ &device_caps.checksum,
+ )
+ .unwrap();
+ get_icmp_pong!(
+ Icmpv6Repr,
+ icmp_repr,
+ payload,
+ waiting_queue,
+ remote_addr,
+ timestamp,
+ received
+ );
+ }
+ }
+ }
+
+ waiting_queue.retain(|seq, from| {
+ if timestamp - *from < timeout {
+ true
+ } else {
+ println!("From {remote_addr} icmp_seq={seq} timeout");
+ false
+ }
+ });
+
+ if seq_no == count as u16 && waiting_queue.is_empty() {
+ break;
+ }
+
+ let timestamp = Instant::now();
+ match iface.poll_at(timestamp, &sockets) {
+ Some(poll_at) if timestamp < poll_at => {
+ let resume_at = cmp::min(poll_at, send_at);
+ phy_wait(fd, Some(resume_at - timestamp)).expect("wait error");
+ }
+ Some(_) => (),
+ None => {
+ phy_wait(fd, Some(send_at - timestamp)).expect("wait error");
+ }
+ }
+ }
+
+ println!("--- {remote_addr} ping statistics ---");
+ println!(
+ "{} packets transmitted, {} received, {:.0}% packet loss",
+ seq_no,
+ received,
+ 100.0 * (seq_no - received) as f64 / seq_no as f64
+ );
+}
diff --git a/examples/server.rs b/examples/server.rs
new file mode 100644
index 0000000..33d95c5
--- /dev/null
+++ b/examples/server.rs
@@ -0,0 +1,209 @@
+mod utils;
+
+use log::debug;
+use std::fmt::Write;
+use std::os::unix::io::AsRawFd;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium};
+use smoltcp::socket::{tcp, udp};
+use smoltcp::time::{Duration, Instant};
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
+
+fn main() {
+ utils::setup_logging("");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_tuntap_options(&mut opts, &mut free);
+ utils::add_middleware_options(&mut opts, &mut free);
+
+ let mut matches = utils::parse_options(&opts, free);
+ let device = utils::parse_tuntap_options(&mut matches);
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => todo!(),
+ };
+
+ config.random_seed = rand::random();
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ iface
+ .routes_mut()
+ .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100))
+ .unwrap();
+ iface
+ .routes_mut()
+ .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100))
+ .unwrap();
+
+ // Create sockets
+ let udp_rx_buffer = udp::PacketBuffer::new(
+ vec![udp::PacketMetadata::EMPTY, udp::PacketMetadata::EMPTY],
+ vec![0; 65535],
+ );
+ let udp_tx_buffer = udp::PacketBuffer::new(
+ vec![udp::PacketMetadata::EMPTY, udp::PacketMetadata::EMPTY],
+ vec![0; 65535],
+ );
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+
+ let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 64]);
+ let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 128]);
+ let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer);
+
+ let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 64]);
+ let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 128]);
+ let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer);
+
+ let tcp3_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp3_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp3_socket = tcp::Socket::new(tcp3_rx_buffer, tcp3_tx_buffer);
+
+ let tcp4_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp4_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]);
+ let tcp4_socket = tcp::Socket::new(tcp4_rx_buffer, tcp4_tx_buffer);
+
+ let mut sockets = SocketSet::new(vec![]);
+ let udp_handle = sockets.add(udp_socket);
+ let tcp1_handle = sockets.add(tcp1_socket);
+ let tcp2_handle = sockets.add(tcp2_socket);
+ let tcp3_handle = sockets.add(tcp3_socket);
+ let tcp4_handle = sockets.add(tcp4_socket);
+
+ let mut tcp_6970_active = false;
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ // udp:6969: respond "hello"
+ let socket = sockets.get_mut::<udp::Socket>(udp_handle);
+ if !socket.is_open() {
+ socket.bind(6969).unwrap()
+ }
+
+ let client = match socket.recv() {
+ Ok((data, endpoint)) => {
+ debug!("udp:6969 recv data: {:?} from {}", data, endpoint);
+ let mut data = data.to_vec();
+ data.reverse();
+ Some((endpoint, data))
+ }
+ Err(_) => None,
+ };
+ if let Some((endpoint, data)) = client {
+ debug!("udp:6969 send data: {:?} to {}", data, endpoint,);
+ socket.send_slice(&data, endpoint).unwrap();
+ }
+
+ // tcp:6969: respond "hello"
+ let socket = sockets.get_mut::<tcp::Socket>(tcp1_handle);
+ if !socket.is_open() {
+ socket.listen(6969).unwrap();
+ }
+
+ if socket.can_send() {
+ debug!("tcp:6969 send greeting");
+ writeln!(socket, "hello").unwrap();
+ debug!("tcp:6969 close");
+ socket.close();
+ }
+
+ // tcp:6970: echo with reverse
+ let socket = sockets.get_mut::<tcp::Socket>(tcp2_handle);
+ if !socket.is_open() {
+ socket.listen(6970).unwrap()
+ }
+
+ if socket.is_active() && !tcp_6970_active {
+ debug!("tcp:6970 connected");
+ } else if !socket.is_active() && tcp_6970_active {
+ debug!("tcp:6970 disconnected");
+ }
+ tcp_6970_active = socket.is_active();
+
+ if socket.may_recv() {
+ let data = socket
+ .recv(|buffer| {
+ let recvd_len = buffer.len();
+ let mut data = buffer.to_owned();
+ if !data.is_empty() {
+ debug!("tcp:6970 recv data: {:?}", data);
+ data = data.split(|&b| b == b'\n').collect::<Vec<_>>().concat();
+ data.reverse();
+ data.extend(b"\n");
+ }
+ (recvd_len, data)
+ })
+ .unwrap();
+ if socket.can_send() && !data.is_empty() {
+ debug!("tcp:6970 send data: {:?}", data);
+ socket.send_slice(&data[..]).unwrap();
+ }
+ } else if socket.may_send() {
+ debug!("tcp:6970 close");
+ socket.close();
+ }
+
+ // tcp:6971: sinkhole
+ let socket = sockets.get_mut::<tcp::Socket>(tcp3_handle);
+ if !socket.is_open() {
+ socket.listen(6971).unwrap();
+ socket.set_keep_alive(Some(Duration::from_millis(1000)));
+ socket.set_timeout(Some(Duration::from_millis(2000)));
+ }
+
+ if socket.may_recv() {
+ socket
+ .recv(|buffer| {
+ if !buffer.is_empty() {
+ debug!("tcp:6971 recv {:?} octets", buffer.len());
+ }
+ (buffer.len(), ())
+ })
+ .unwrap();
+ } else if socket.may_send() {
+ socket.close();
+ }
+
+ // tcp:6972: fountain
+ let socket = sockets.get_mut::<tcp::Socket>(tcp4_handle);
+ if !socket.is_open() {
+ socket.listen(6972).unwrap()
+ }
+
+ if socket.may_send() {
+ socket
+ .send(|data| {
+ if !data.is_empty() {
+ debug!("tcp:6972 send {:?} octets", data.len());
+ for (i, b) in data.iter_mut().enumerate() {
+ *b = (i % 256) as u8;
+ }
+ }
+ (data.len(), ())
+ })
+ .unwrap();
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/sixlowpan.rs b/examples/sixlowpan.rs
new file mode 100644
index 0000000..0d9ec21
--- /dev/null
+++ b/examples/sixlowpan.rs
@@ -0,0 +1,177 @@
+//! 6lowpan example
+//!
+//! This example is designed to run using the Linux ieee802154/6lowpan support,
+//! using mac802154_hwsim.
+//!
+//! mac802154_hwsim allows you to create multiple "virtual" radios and specify
+//! which is in range with which. This is very useful for testing without
+//! needing real hardware. By default it creates two interfaces `wpan0` and
+//! `wpan1` that are in range with each other. You can customize this with
+//! the `wpan-hwsim` tool.
+//!
+//! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1`
+//! unconfigured so smoltcp can use it with a raw socket.
+//!
+//! # Setup
+//!
+//! modprobe mac802154_hwsim
+//!
+//! ip link set wpan0 down
+//! ip link set wpan1 down
+//! iwpan dev wpan0 set pan_id 0xbeef
+//! iwpan dev wpan1 set pan_id 0xbeef
+//! ip link add link wpan0 name lowpan0 type lowpan
+//! ip link set wpan0 up
+//! ip link set wpan1 up
+//! ip link set lowpan0 up
+//!
+//! # Running
+//!
+//! Run it with `sudo ./target/debug/examples/sixlowpan`.
+//!
+//! You can set wireshark to sniff on interface `wpan0` to see the packets.
+//!
+//! Ping it with `ping fe80::180b:4242:4242:4242%lowpan0`.
+//!
+//! Speak UDP with `nc -uv fe80::180b:4242:4242:4242%lowpan0 6969`.
+//!
+//! # Teardown
+//!
+//! rmmod mac802154_hwsim
+//!
+
+mod utils;
+
+use log::debug;
+use std::os::unix::io::AsRawFd;
+use std::str;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium, RawSocket};
+use smoltcp::socket::tcp;
+use smoltcp::socket::udp;
+use smoltcp::time::Instant;
+use smoltcp::wire::{EthernetAddress, Ieee802154Address, Ieee802154Pan, IpAddress, IpCidr};
+
+fn main() {
+ utils::setup_logging("");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_middleware_options(&mut opts, &mut free);
+
+ let mut matches = utils::parse_options(&opts, free);
+
+ let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap();
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => Config::new(
+ Ieee802154Address::Extended([0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42]).into(),
+ ),
+ };
+ config.random_seed = rand::random();
+ config.pan_id = Some(Ieee802154Pan(0xbeef));
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(
+ IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242),
+ 64,
+ ))
+ .unwrap();
+ });
+
+ // Create sockets
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+
+ let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
+
+ let mut sockets = SocketSet::new(vec![]);
+ let udp_handle = sockets.add(udp_socket);
+ let tcp_handle = sockets.add(tcp_socket);
+
+ let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+ socket.listen(50000).unwrap();
+
+ let mut tcp_active = false;
+
+ loop {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ // udp:6969: respond "hello"
+ let socket = sockets.get_mut::<udp::Socket>(udp_handle);
+ if !socket.is_open() {
+ socket.bind(6969).unwrap()
+ }
+
+ let mut buffer = vec![0; 1500];
+ let client = match socket.recv() {
+ Ok((data, endpoint)) => {
+ debug!(
+ "udp:6969 recv data: {:?} from {}",
+ str::from_utf8(data).unwrap(),
+ endpoint
+ );
+ buffer[..data.len()].copy_from_slice(data);
+ Some((data.len(), endpoint))
+ }
+ Err(_) => None,
+ };
+ if let Some((len, endpoint)) = client {
+ debug!(
+ "udp:6969 send data: {:?}",
+ str::from_utf8(&buffer[..len]).unwrap()
+ );
+ socket.send_slice(&buffer[..len], endpoint).unwrap();
+ }
+
+ let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+ if socket.is_active() && !tcp_active {
+ debug!("connected");
+ } else if !socket.is_active() && tcp_active {
+ debug!("disconnected");
+ }
+ tcp_active = socket.is_active();
+
+ if socket.may_recv() {
+ let data = socket
+ .recv(|data| {
+ let data = data.to_owned();
+ if !data.is_empty() {
+ debug!(
+ "recv data: {:?}",
+ str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
+ );
+ }
+ (data.len(), data)
+ })
+ .unwrap();
+
+ if socket.can_send() && !data.is_empty() {
+ debug!(
+ "send data: {:?}",
+ str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
+ );
+ socket.send_slice(&data[..]).unwrap();
+ }
+ } else if socket.may_send() {
+ debug!("close");
+ socket.close();
+ }
+
+ phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error");
+ }
+}
diff --git a/examples/sixlowpan_benchmark.rs b/examples/sixlowpan_benchmark.rs
new file mode 100644
index 0000000..4e61491
--- /dev/null
+++ b/examples/sixlowpan_benchmark.rs
@@ -0,0 +1,235 @@
+//! 6lowpan benchmark example
+//!
+//! This example runs a simple TCP throughput benchmark using the 6lowpan implementation in smoltcp
+//! It is designed to run using the Linux ieee802154/6lowpan support,
+//! using mac802154_hwsim.
+//!
+//! mac802154_hwsim allows you to create multiple "virtual" radios and specify
+//! which is in range with which. This is very useful for testing without
+//! needing real hardware. By default it creates two interfaces `wpan0` and
+//! `wpan1` that are in range with each other. You can customize this with
+//! the `wpan-hwsim` tool.
+//!
+//! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1`
+//! unconfigured so smoltcp can use it with a raw socket.
+//!
+//!
+//!
+//!
+//!
+//! # Setup
+//!
+//! modprobe mac802154_hwsim
+//!
+//! ip link set wpan0 down
+//! ip link set wpan1 down
+//! iwpan dev wpan0 set pan_id 0xbeef
+//! iwpan dev wpan1 set pan_id 0xbeef
+//! ip link add link wpan0 name lowpan0 type lowpan
+//! ip link set wpan0 up
+//! ip link set wpan1 up
+//! ip link set lowpan0 up
+//!
+//!
+//! # Running
+//!
+//! Compile with `cargo build --release --example sixlowpan_benchmark`
+//! Run it with `sudo ./target/release/examples/sixlowpan_benchmark [reader|writer]`.
+//!
+//! # Teardown
+//!
+//! rmmod mac802154_hwsim
+//!
+
+mod utils;
+
+use std::os::unix::io::AsRawFd;
+use std::str;
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{wait as phy_wait, Device, Medium, RawSocket};
+use smoltcp::socket::tcp;
+use smoltcp::wire::{EthernetAddress, Ieee802154Address, Ieee802154Pan, IpAddress, IpCidr};
+
+//For benchmark
+use smoltcp::time::{Duration, Instant};
+use std::cmp;
+use std::io::{Read, Write};
+use std::net::SocketAddrV6;
+use std::net::TcpStream;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+
+use std::fs;
+
+fn if_nametoindex(ifname: &str) -> u32 {
+ let contents = fs::read_to_string(format!("/sys/devices/virtual/net/{ifname}/ifindex"))
+ .expect("couldn't read interface from \"/sys/devices/virtual/net\"")
+ .replace('\n', "");
+ contents.parse::<u32>().unwrap()
+}
+
+const AMOUNT: usize = 100_000_000;
+
+enum Client {
+ Reader,
+ Writer,
+}
+
+fn client(kind: Client) {
+ let port: u16 = match kind {
+ Client::Reader => 1234,
+ Client::Writer => 1235,
+ };
+
+ let scope_id = if_nametoindex("lowpan0");
+
+ let socket_addr = SocketAddrV6::new(
+ "fe80:0:0:0:180b:4242:4242:4242".parse().unwrap(),
+ port,
+ 0,
+ scope_id,
+ );
+
+ let mut stream = TcpStream::connect(socket_addr).expect("failed to connect TLKAGMKA");
+ let mut buffer = vec![0; 1_000_000];
+
+ let start = Instant::now();
+
+ let mut processed = 0;
+ while processed < AMOUNT {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ let result = match kind {
+ Client::Reader => stream.read(&mut buffer[..length]),
+ Client::Writer => stream.write(&buffer[..length]),
+ };
+ match result {
+ Ok(0) => break,
+ Ok(result) => {
+ // print!("(P:{})", result);
+ processed += result
+ }
+ Err(err) => panic!("cannot process: {err}"),
+ }
+ }
+
+ let end = Instant::now();
+
+ let elapsed = (end - start).total_millis() as f64 / 1000.0;
+
+ println!("throughput: {:.3} Gbps", AMOUNT as f64 / elapsed / 0.125e9);
+
+ CLIENT_DONE.store(true, Ordering::SeqCst);
+}
+
+static CLIENT_DONE: AtomicBool = AtomicBool::new(false);
+
+fn main() {
+ #[cfg(feature = "log")]
+ utils::setup_logging("info");
+
+ let (mut opts, mut free) = utils::create_options();
+ utils::add_middleware_options(&mut opts, &mut free);
+ free.push("MODE");
+
+ let mut matches = utils::parse_options(&opts, free);
+
+ let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap();
+
+ let fd = device.as_raw_fd();
+ let mut device =
+ utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
+
+ let mode = match matches.free[0].as_ref() {
+ "reader" => Client::Reader,
+ "writer" => Client::Writer,
+ _ => panic!("invalid mode"),
+ };
+
+ // Create interface
+ let mut config = match device.capabilities().medium {
+ Medium::Ethernet => {
+ Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
+ }
+ Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
+ Medium::Ieee802154 => Config::new(
+ Ieee802154Address::Extended([0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42]).into(),
+ ),
+ };
+ config.random_seed = rand::random();
+ config.pan_id = Some(Ieee802154Pan(0xbeef));
+
+ let mut iface = Interface::new(config, &mut device, Instant::now());
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(
+ IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242),
+ 64,
+ ))
+ .unwrap();
+ });
+
+ let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer);
+
+ let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]);
+ let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer);
+
+ let mut sockets = SocketSet::new(vec![]);
+ let tcp1_handle = sockets.add(tcp1_socket);
+ let tcp2_handle = sockets.add(tcp2_socket);
+
+ let default_timeout = Some(Duration::from_millis(1000));
+
+ thread::spawn(move || client(mode));
+ let mut processed = 0;
+
+ while !CLIENT_DONE.load(Ordering::SeqCst) {
+ let timestamp = Instant::now();
+ iface.poll(timestamp, &mut device, &mut sockets);
+
+ // tcp:1234: emit data
+ let socket = sockets.get_mut::<tcp::Socket>(tcp1_handle);
+ if !socket.is_open() {
+ socket.listen(1234).unwrap();
+ }
+
+ if socket.can_send() && processed < AMOUNT {
+ let length = socket
+ .send(|buffer| {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ (length, length)
+ })
+ .unwrap();
+ processed += length;
+ }
+
+ // tcp:1235: sink data
+ let socket = sockets.get_mut::<tcp::Socket>(tcp2_handle);
+ if !socket.is_open() {
+ socket.listen(1235).unwrap();
+ }
+
+ if socket.can_recv() && processed < AMOUNT {
+ let length = socket
+ .recv(|buffer| {
+ let length = cmp::min(buffer.len(), AMOUNT - processed);
+ (length, length)
+ })
+ .unwrap();
+ processed += length;
+ }
+
+ match iface.poll_at(timestamp, &sockets) {
+ Some(poll_at) if timestamp < poll_at => {
+ phy_wait(fd, Some(poll_at - timestamp)).expect("wait error");
+ }
+ Some(_) => (),
+ None => {
+ phy_wait(fd, default_timeout).expect("wait error");
+ }
+ }
+ }
+}
diff --git a/examples/tcpdump.rs b/examples/tcpdump.rs
new file mode 100644
index 0000000..2baf376
--- /dev/null
+++ b/examples/tcpdump.rs
@@ -0,0 +1,21 @@
+use smoltcp::phy::wait as phy_wait;
+use smoltcp::phy::{Device, RawSocket, RxToken};
+use smoltcp::time::Instant;
+use smoltcp::wire::{EthernetFrame, PrettyPrinter};
+use std::env;
+use std::os::unix::io::AsRawFd;
+
+fn main() {
+ let ifname = env::args().nth(1).unwrap();
+ let mut socket = RawSocket::new(ifname.as_ref(), smoltcp::phy::Medium::Ethernet).unwrap();
+ loop {
+ phy_wait(socket.as_raw_fd(), None).unwrap();
+ let (rx_token, _) = socket.receive(Instant::now()).unwrap();
+ rx_token.consume(|buffer| {
+ println!(
+ "{}",
+ PrettyPrinter::<EthernetFrame<&[u8]>>::new("", &buffer)
+ );
+ })
+ }
+}
diff --git a/examples/utils.rs b/examples/utils.rs
new file mode 100644
index 0000000..dbe9076
--- /dev/null
+++ b/examples/utils.rs
@@ -0,0 +1,218 @@
+#![allow(dead_code)]
+
+#[cfg(feature = "log")]
+use env_logger::Builder;
+use getopts::{Matches, Options};
+#[cfg(feature = "log")]
+use log::{trace, Level, LevelFilter};
+use std::env;
+use std::fs::File;
+use std::io::{self, Write};
+use std::process;
+use std::str::{self, FromStr};
+use std::time::{SystemTime, UNIX_EPOCH};
+
+#[cfg(feature = "phy-tuntap_interface")]
+use smoltcp::phy::TunTapInterface;
+use smoltcp::phy::{Device, FaultInjector, Medium, Tracer};
+use smoltcp::phy::{PcapMode, PcapWriter};
+use smoltcp::time::{Duration, Instant};
+
+#[cfg(feature = "log")]
+pub fn setup_logging_with_clock<F>(filter: &str, since_startup: F)
+where
+ F: Fn() -> Instant + Send + Sync + 'static,
+{
+ Builder::new()
+ .format(move |buf, record| {
+ let elapsed = since_startup();
+ let timestamp = format!("[{elapsed}]");
+ if record.target().starts_with("smoltcp::") {
+ writeln!(
+ buf,
+ "\x1b[0m{} ({}): {}\x1b[0m",
+ timestamp,
+ record.target().replace("smoltcp::", ""),
+ record.args()
+ )
+ } else if record.level() == Level::Trace {
+ let message = format!("{}", record.args());
+ writeln!(
+ buf,
+ "\x1b[37m{} {}\x1b[0m",
+ timestamp,
+ message.replace('\n', "\n ")
+ )
+ } else {
+ writeln!(
+ buf,
+ "\x1b[32m{} ({}): {}\x1b[0m",
+ timestamp,
+ record.target(),
+ record.args()
+ )
+ }
+ })
+ .filter(None, LevelFilter::Trace)
+ .parse_filters(filter)
+ .parse_env("RUST_LOG")
+ .init();
+}
+
+#[cfg(feature = "log")]
+pub fn setup_logging(filter: &str) {
+ setup_logging_with_clock(filter, Instant::now)
+}
+
+pub fn create_options() -> (Options, Vec<&'static str>) {
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "print this help menu");
+ (opts, Vec::new())
+}
+
+pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches {
+ match options.parse(env::args().skip(1)) {
+ Err(err) => {
+ println!("{err}");
+ process::exit(1)
+ }
+ Ok(matches) => {
+ if matches.opt_present("h") || matches.free.len() != free.len() {
+ let brief = format!(
+ "Usage: {} [OPTION]... {}",
+ env::args().next().unwrap(),
+ free.join(" ")
+ );
+ print!("{}", options.usage(&brief));
+ process::exit((matches.free.len() != free.len()) as _);
+ }
+ matches
+ }
+ }
+}
+
+pub fn add_tuntap_options(opts: &mut Options, _free: &mut [&str]) {
+ opts.optopt("", "tun", "TUN interface to use", "tun0");
+ opts.optopt("", "tap", "TAP interface to use", "tap0");
+}
+
+#[cfg(feature = "phy-tuntap_interface")]
+pub fn parse_tuntap_options(matches: &mut Matches) -> TunTapInterface {
+ let tun = matches.opt_str("tun");
+ let tap = matches.opt_str("tap");
+ match (tun, tap) {
+ (Some(tun), None) => TunTapInterface::new(&tun, Medium::Ip).unwrap(),
+ (None, Some(tap)) => TunTapInterface::new(&tap, Medium::Ethernet).unwrap(),
+ _ => panic!("You must specify exactly one of --tun or --tap"),
+ }
+}
+
+pub fn add_middleware_options(opts: &mut Options, _free: &mut [&str]) {
+ opts.optopt("", "pcap", "Write a packet capture file", "FILE");
+ opts.optopt(
+ "",
+ "drop-chance",
+ "Chance of dropping a packet (%)",
+ "CHANCE",
+ );
+ opts.optopt(
+ "",
+ "corrupt-chance",
+ "Chance of corrupting a packet (%)",
+ "CHANCE",
+ );
+ opts.optopt(
+ "",
+ "size-limit",
+ "Drop packets larger than given size (octets)",
+ "SIZE",
+ );
+ opts.optopt(
+ "",
+ "tx-rate-limit",
+ "Drop packets after transmit rate exceeds given limit \
+ (packets per interval)",
+ "RATE",
+ );
+ opts.optopt(
+ "",
+ "rx-rate-limit",
+ "Drop packets after transmit rate exceeds given limit \
+ (packets per interval)",
+ "RATE",
+ );
+ opts.optopt(
+ "",
+ "shaping-interval",
+ "Sets the interval for rate limiting (ms)",
+ "RATE",
+ );
+}
+
+pub fn parse_middleware_options<D>(
+ matches: &mut Matches,
+ device: D,
+ loopback: bool,
+) -> FaultInjector<Tracer<PcapWriter<D, Box<dyn io::Write>>>>
+where
+ D: Device,
+{
+ let drop_chance = matches
+ .opt_str("drop-chance")
+ .map(|s| u8::from_str(&s).unwrap())
+ .unwrap_or(0);
+ let corrupt_chance = matches
+ .opt_str("corrupt-chance")
+ .map(|s| u8::from_str(&s).unwrap())
+ .unwrap_or(0);
+ let size_limit = matches
+ .opt_str("size-limit")
+ .map(|s| usize::from_str(&s).unwrap())
+ .unwrap_or(0);
+ let tx_rate_limit = matches
+ .opt_str("tx-rate-limit")
+ .map(|s| u64::from_str(&s).unwrap())
+ .unwrap_or(0);
+ let rx_rate_limit = matches
+ .opt_str("rx-rate-limit")
+ .map(|s| u64::from_str(&s).unwrap())
+ .unwrap_or(0);
+ let shaping_interval = matches
+ .opt_str("shaping-interval")
+ .map(|s| u64::from_str(&s).unwrap())
+ .unwrap_or(0);
+
+ let pcap_writer: Box<dyn io::Write> = match matches.opt_str("pcap") {
+ Some(pcap_filename) => Box::new(File::create(pcap_filename).expect("cannot open file")),
+ None => Box::new(io::sink()),
+ };
+
+ let seed = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .subsec_nanos();
+
+ let device = PcapWriter::new(
+ device,
+ pcap_writer,
+ if loopback {
+ PcapMode::TxOnly
+ } else {
+ PcapMode::Both
+ },
+ );
+
+ let device = Tracer::new(device, |_timestamp, _printer| {
+ #[cfg(feature = "log")]
+ trace!("{}", _printer);
+ });
+
+ let mut device = FaultInjector::new(device, seed);
+ device.set_drop_chance(drop_chance);
+ device.set_corrupt_chance(corrupt_chance);
+ device.set_max_packet_size(size_limit);
+ device.set_max_tx_rate(tx_rate_limit);
+ device.set_max_rx_rate(rx_rate_limit);
+ device.set_bucket_interval(Duration::from_millis(shaping_interval));
+ device
+}
diff --git a/gen_config.py b/gen_config.py
new file mode 100644
index 0000000..2569192
--- /dev/null
+++ b/gen_config.py
@@ -0,0 +1,86 @@
+import os
+
+abspath = os.path.abspath(__file__)
+dname = os.path.dirname(abspath)
+os.chdir(dname)
+
+features = []
+
+
+def feature(name, default, min, max, pow2=None):
+ vals = set()
+ val = min
+ while val <= max:
+ vals.add(val)
+ if pow2 == True or (isinstance(pow2, int) and val >= pow2):
+ val *= 2
+ else:
+ val += 1
+ vals.add(default)
+
+ features.append(
+ {
+ "name": name,
+ "default": default,
+ "vals": sorted(list(vals)),
+ }
+ )
+
+
+feature("iface_max_addr_count", default=2, min=1, max=8)
+feature("iface_max_multicast_group_count", default=4, min=1, max=1024, pow2=8)
+feature("iface_max_sixlowpan_address_context_count", default=4, min=1, max=1024, pow2=8)
+feature("iface_neighbor_cache_count", default=4, min=1, max=1024, pow2=8)
+feature("iface_max_route_count", default=2, min=1, max=1024, pow2=8)
+feature("fragmentation_buffer_size", default=1500, min=256, max=65536, pow2=True)
+feature("assembler_max_segment_count", default=4, min=1, max=32, pow2=4)
+feature("reassembly_buffer_size", default=1500, min=256, max=65536, pow2=True)
+feature("reassembly_buffer_count", default=1, min=1, max=32, pow2=4)
+feature("ipv6_hbh_max_options", default=1, min=1, max=32, pow2=4)
+feature("dns_max_result_count", default=1, min=1, max=32, pow2=4)
+feature("dns_max_server_count", default=1, min=1, max=32, pow2=4)
+feature("dns_max_name_size", default=255, min=64, max=255, pow2=True)
+feature("rpl_relations_buffer_count", default=16, min=1, max=128, pow2=True)
+feature("rpl_parents_buffer_count", default=8, min=2, max=32, pow2=True)
+
+# ========= Update Cargo.toml
+
+things = ""
+for f in features:
+ name = f["name"].replace("_", "-")
+ for val in f["vals"]:
+ things += f"{name}-{val} = []"
+ if val == f["default"]:
+ things += " # Default"
+ things += "\n"
+ things += "\n"
+
+SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
+SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
+HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
+with open("Cargo.toml", "r") as f:
+ data = f.read()
+before, data = data.split(SEPARATOR_START, maxsplit=1)
+_, after = data.split(SEPARATOR_END, maxsplit=1)
+data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
+with open("Cargo.toml", "w") as f:
+ f.write(data)
+
+
+# ========= Update build.rs
+
+things = ""
+for f in features:
+ name = f["name"].upper()
+ things += f' ("{name}", {f["default"]}),\n'
+
+SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
+SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
+HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
+with open("build.rs", "r") as f:
+ data = f.read()
+before, data = data.split(SEPARATOR_START, maxsplit=1)
+_, after = data.split(SEPARATOR_END, maxsplit=1)
+data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
+with open("build.rs", "w") as f:
+ f.write(data)
diff --git a/src/iface/fragmentation.rs b/src/iface/fragmentation.rs
new file mode 100644
index 0000000..ed00f17
--- /dev/null
+++ b/src/iface/fragmentation.rs
@@ -0,0 +1,506 @@
+#![allow(unused)]
+
+use core::fmt;
+
+use managed::{ManagedMap, ManagedSlice};
+
+use crate::config::{FRAGMENTATION_BUFFER_SIZE, REASSEMBLY_BUFFER_COUNT, REASSEMBLY_BUFFER_SIZE};
+use crate::storage::Assembler;
+use crate::time::{Duration, Instant};
+use crate::wire::*;
+
+use core::result::Result;
+
+#[cfg(feature = "alloc")]
+type Buffer = alloc::vec::Vec<u8>;
+#[cfg(not(feature = "alloc"))]
+type Buffer = [u8; REASSEMBLY_BUFFER_SIZE];
+
+/// Problem when assembling: something was out of bounds.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssemblerError;
+
+impl fmt::Display for AssemblerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "AssemblerError")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AssemblerError {}
+
+/// Packet assembler is full
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssemblerFullError;
+
+impl fmt::Display for AssemblerFullError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "AssemblerFullError")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AssemblerFullError {}
+
+/// Holds different fragments of one packet, used for assembling fragmented packets.
+///
+/// The buffer used for the `PacketAssembler` should either be dynamically sized (ex: Vec<u8>)
+/// or should be statically allocated based upon the MTU of the type of packet being
+/// assembled (ex: 1280 for a IPv6 frame).
+#[derive(Debug)]
+pub struct PacketAssembler<K> {
+ key: Option<K>,
+ buffer: Buffer,
+
+ assembler: Assembler,
+ total_size: Option<usize>,
+ expires_at: Instant,
+}
+
+impl<K> PacketAssembler<K> {
+ /// Create a new empty buffer for fragments.
+ pub const fn new() -> Self {
+ Self {
+ key: None,
+
+ #[cfg(feature = "alloc")]
+ buffer: Buffer::new(),
+ #[cfg(not(feature = "alloc"))]
+ buffer: [0u8; REASSEMBLY_BUFFER_SIZE],
+
+ assembler: Assembler::new(),
+ total_size: None,
+ expires_at: Instant::ZERO,
+ }
+ }
+
+ pub(crate) fn reset(&mut self) {
+ self.key = None;
+ self.assembler.clear();
+ self.total_size = None;
+ self.expires_at = Instant::ZERO;
+ }
+
+ /// Set the total size of the packet assembler.
+ pub(crate) fn set_total_size(&mut self, size: usize) -> Result<(), AssemblerError> {
+ if let Some(old_size) = self.total_size {
+ if old_size != size {
+ return Err(AssemblerError);
+ }
+ }
+
+ #[cfg(not(feature = "alloc"))]
+ if self.buffer.len() < size {
+ return Err(AssemblerError);
+ }
+
+ #[cfg(feature = "alloc")]
+ if self.buffer.len() < size {
+ self.buffer.resize(size, 0);
+ }
+
+ self.total_size = Some(size);
+ Ok(())
+ }
+
+ /// Return the instant when the assembler expires.
+ pub(crate) fn expires_at(&self) -> Instant {
+ self.expires_at
+ }
+
+ pub(crate) fn add_with(
+ &mut self,
+ offset: usize,
+ f: impl Fn(&mut [u8]) -> Result<usize, AssemblerError>,
+ ) -> Result<(), AssemblerError> {
+ if self.buffer.len() < offset {
+ return Err(AssemblerError);
+ }
+
+ let len = f(&mut self.buffer[offset..])?;
+ assert!(offset + len <= self.buffer.len());
+
+ net_debug!(
+ "frag assembler: receiving {} octets at offset {}",
+ len,
+ offset
+ );
+
+ self.assembler.add(offset, len);
+ Ok(())
+ }
+
+ /// Add a fragment into the packet that is being reassembled.
+ ///
+ /// # Errors
+ ///
+ /// - Returns [`Error::PacketAssemblerBufferTooSmall`] when trying to add data into the buffer at a non-existing
+ /// place.
+ pub(crate) fn add(&mut self, data: &[u8], offset: usize) -> Result<(), AssemblerError> {
+ #[cfg(not(feature = "alloc"))]
+ if self.buffer.len() < offset + data.len() {
+ return Err(AssemblerError);
+ }
+
+ #[cfg(feature = "alloc")]
+ if self.buffer.len() < offset + data.len() {
+ self.buffer.resize(offset + data.len(), 0);
+ }
+
+ let len = data.len();
+ self.buffer[offset..][..len].copy_from_slice(data);
+
+ net_debug!(
+ "frag assembler: receiving {} octets at offset {}",
+ len,
+ offset
+ );
+
+ self.assembler.add(offset, data.len());
+ Ok(())
+ }
+
+ /// Get an immutable slice of the underlying packet data, if reassembly complete.
+ /// This will mark the assembler as empty, so that it can be reused.
+ pub(crate) fn assemble(&mut self) -> Option<&'_ [u8]> {
+ if !self.is_complete() {
+ return None;
+ }
+
+ // NOTE: we can unwrap because `is_complete` already checks this.
+ let total_size = self.total_size.unwrap();
+ self.reset();
+ Some(&self.buffer[..total_size])
+ }
+
+ /// Returns `true` when all fragments have been received, otherwise `false`.
+ pub(crate) fn is_complete(&self) -> bool {
+ self.total_size == Some(self.assembler.peek_front())
+ }
+
+ /// Returns `true` when the packet assembler is free to use.
+ fn is_free(&self) -> bool {
+ self.key.is_none()
+ }
+}
+
+/// Set holding multiple [`PacketAssembler`].
+#[derive(Debug)]
+pub struct PacketAssemblerSet<K: Eq + Copy> {
+ assemblers: [PacketAssembler<K>; REASSEMBLY_BUFFER_COUNT],
+}
+
+impl<K: Eq + Copy> PacketAssemblerSet<K> {
+ const NEW_PA: PacketAssembler<K> = PacketAssembler::new();
+
+ /// Create a new set of packet assemblers.
+ pub fn new() -> Self {
+ Self {
+ assemblers: [Self::NEW_PA; REASSEMBLY_BUFFER_COUNT],
+ }
+ }
+
+ /// Get a [`PacketAssembler`] for a specific key.
+ ///
+ /// If it doesn't exist, it is created, with the `expires_at` timestamp.
+ ///
+ /// If the assembler set is full, in which case an error is returned.
+ pub(crate) fn get(
+ &mut self,
+ key: &K,
+ expires_at: Instant,
+ ) -> Result<&mut PacketAssembler<K>, AssemblerFullError> {
+ let mut empty_slot = None;
+ for slot in &mut self.assemblers {
+ if slot.key.as_ref() == Some(key) {
+ return Ok(slot);
+ }
+ if slot.is_free() {
+ empty_slot = Some(slot)
+ }
+ }
+
+ let slot = empty_slot.ok_or(AssemblerFullError)?;
+ slot.key = Some(*key);
+ slot.expires_at = expires_at;
+ Ok(slot)
+ }
+
+ /// Remove all [`PacketAssembler`]s that are expired.
+ pub fn remove_expired(&mut self, timestamp: Instant) {
+ for frag in &mut self.assemblers {
+ if !frag.is_free() && frag.expires_at < timestamp {
+ frag.reset();
+ }
+ }
+ }
+}
+
+// Max len of non-fragmented packets after decompression (including ipv6 header and payload)
+// TODO: lower. Should be (6lowpan mtu) - (min 6lowpan header size) + (max ipv6 header size)
+pub(crate) const MAX_DECOMPRESSED_LEN: usize = 1500;
+
+#[cfg(feature = "_proto-fragmentation")]
+#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) enum FragKey {
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ Ipv4(Ipv4FragKey),
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ Sixlowpan(SixlowpanFragKey),
+}
+
+pub(crate) struct FragmentsBuffer {
+ #[cfg(feature = "proto-sixlowpan")]
+ pub decompress_buf: [u8; MAX_DECOMPRESSED_LEN],
+
+ #[cfg(feature = "_proto-fragmentation")]
+ pub assembler: PacketAssemblerSet<FragKey>,
+
+ #[cfg(feature = "_proto-fragmentation")]
+ pub reassembly_timeout: Duration,
+}
+
+#[cfg(not(feature = "_proto-fragmentation"))]
+pub(crate) struct Fragmenter {}
+
+#[cfg(not(feature = "_proto-fragmentation"))]
+impl Fragmenter {
+ pub(crate) fn new() -> Self {
+ Self {}
+ }
+}
+
+#[cfg(feature = "_proto-fragmentation")]
+pub(crate) struct Fragmenter {
+ /// The buffer that holds the unfragmented 6LoWPAN packet.
+ pub buffer: [u8; FRAGMENTATION_BUFFER_SIZE],
+ /// The size of the packet without the IEEE802.15.4 header and the fragmentation headers.
+ pub packet_len: usize,
+ /// The amount of bytes that already have been transmitted.
+ pub sent_bytes: usize,
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ pub ipv4: Ipv4Fragmenter,
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ pub sixlowpan: SixlowpanFragmenter,
+}
+
+#[cfg(feature = "proto-ipv4-fragmentation")]
+pub(crate) struct Ipv4Fragmenter {
+ /// The IPv4 representation.
+ pub repr: Ipv4Repr,
+ /// The destination hardware address.
+ #[cfg(feature = "medium-ethernet")]
+ pub dst_hardware_addr: EthernetAddress,
+ /// The offset of the next fragment.
+ pub frag_offset: u16,
+ /// The identifier of the stream.
+ pub ident: u16,
+}
+
+#[cfg(feature = "proto-sixlowpan-fragmentation")]
+pub(crate) struct SixlowpanFragmenter {
+ /// The datagram size that is used for the fragmentation headers.
+ pub datagram_size: u16,
+ /// The datagram tag that is used for the fragmentation headers.
+ pub datagram_tag: u16,
+ pub datagram_offset: usize,
+
+ /// The size of the FRAG_N packets.
+ pub fragn_size: usize,
+
+ /// The link layer IEEE802.15.4 source address.
+ pub ll_dst_addr: Ieee802154Address,
+ /// The link layer IEEE802.15.4 source address.
+ pub ll_src_addr: Ieee802154Address,
+}
+
+#[cfg(feature = "_proto-fragmentation")]
+impl Fragmenter {
+ pub(crate) fn new() -> Self {
+ Self {
+ buffer: [0u8; FRAGMENTATION_BUFFER_SIZE],
+ packet_len: 0,
+ sent_bytes: 0,
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ ipv4: Ipv4Fragmenter {
+ repr: Ipv4Repr {
+ src_addr: Ipv4Address::default(),
+ dst_addr: Ipv4Address::default(),
+ next_header: IpProtocol::Unknown(0),
+ payload_len: 0,
+ hop_limit: 0,
+ },
+ #[cfg(feature = "medium-ethernet")]
+ dst_hardware_addr: EthernetAddress::default(),
+ frag_offset: 0,
+ ident: 0,
+ },
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ sixlowpan: SixlowpanFragmenter {
+ datagram_size: 0,
+ datagram_tag: 0,
+ datagram_offset: 0,
+ fragn_size: 0,
+ ll_dst_addr: Ieee802154Address::Absent,
+ ll_src_addr: Ieee802154Address::Absent,
+ },
+ }
+ }
+
+ /// Return `true` when everything is transmitted.
+ #[inline]
+ pub(crate) fn finished(&self) -> bool {
+ self.packet_len == self.sent_bytes
+ }
+
+ /// Returns `true` when there is nothing to transmit.
+ #[inline]
+ pub(crate) fn is_empty(&self) -> bool {
+ self.packet_len == 0
+ }
+
+ // Reset the buffer.
+ pub(crate) fn reset(&mut self) {
+ self.packet_len = 0;
+ self.sent_bytes = 0;
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ {
+ self.ipv4.repr = Ipv4Repr {
+ src_addr: Ipv4Address::default(),
+ dst_addr: Ipv4Address::default(),
+ next_header: IpProtocol::Unknown(0),
+ payload_len: 0,
+ hop_limit: 0,
+ };
+ #[cfg(feature = "medium-ethernet")]
+ {
+ self.ipv4.dst_hardware_addr = EthernetAddress::default();
+ }
+ }
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ {
+ self.sixlowpan.datagram_size = 0;
+ self.sixlowpan.datagram_tag = 0;
+ self.sixlowpan.fragn_size = 0;
+ self.sixlowpan.ll_dst_addr = Ieee802154Address::Absent;
+ self.sixlowpan.ll_src_addr = Ieee802154Address::Absent;
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ struct Key {
+ id: usize,
+ }
+
+ #[test]
+ fn packet_assembler_overlap() {
+ let mut p_assembler = PacketAssembler::<Key>::new();
+
+ p_assembler.set_total_size(5).unwrap();
+
+ let data = b"Rust";
+ p_assembler.add(&data[..], 0);
+ p_assembler.add(&data[..], 1);
+
+ assert_eq!(p_assembler.assemble(), Some(&b"RRust"[..]))
+ }
+
+ #[test]
+ fn packet_assembler_assemble() {
+ let mut p_assembler = PacketAssembler::<Key>::new();
+
+ let data = b"Hello World!";
+
+ p_assembler.set_total_size(data.len()).unwrap();
+
+ p_assembler.add(b"Hello ", 0).unwrap();
+ assert_eq!(p_assembler.assemble(), None);
+
+ p_assembler.add(b"World!", b"Hello ".len()).unwrap();
+
+ assert_eq!(p_assembler.assemble(), Some(&b"Hello World!"[..]));
+ }
+
+ #[test]
+ fn packet_assembler_out_of_order_assemble() {
+ let mut p_assembler = PacketAssembler::<Key>::new();
+
+ let data = b"Hello World!";
+
+ p_assembler.set_total_size(data.len()).unwrap();
+
+ p_assembler.add(b"World!", b"Hello ".len()).unwrap();
+ assert_eq!(p_assembler.assemble(), None);
+
+ p_assembler.add(b"Hello ", 0).unwrap();
+
+ assert_eq!(p_assembler.assemble(), Some(&b"Hello World!"[..]));
+ }
+
+ #[test]
+ fn packet_assembler_set() {
+ let key = Key { id: 1 };
+
+ let mut set = PacketAssemblerSet::new();
+
+ assert!(set.get(&key, Instant::ZERO).is_ok());
+ }
+
+ #[test]
+ fn packet_assembler_set_full() {
+ let mut set = PacketAssemblerSet::new();
+ for i in 0..REASSEMBLY_BUFFER_COUNT {
+ set.get(&Key { id: i }, Instant::ZERO).unwrap();
+ }
+ assert!(set.get(&Key { id: 4 }, Instant::ZERO).is_err());
+ }
+
+ #[test]
+ fn packet_assembler_set_assembling_many() {
+ let mut set = PacketAssemblerSet::new();
+
+ let key = Key { id: 0 };
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assert_eq!(assr.assemble(), None);
+ assr.set_total_size(0).unwrap();
+ assr.assemble().unwrap();
+
+ // Test that `.assemble()` effectively deletes it.
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assert_eq!(assr.assemble(), None);
+ assr.set_total_size(0).unwrap();
+ assr.assemble().unwrap();
+
+ let key = Key { id: 1 };
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assr.set_total_size(0).unwrap();
+ assr.assemble().unwrap();
+
+ let key = Key { id: 2 };
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assr.set_total_size(0).unwrap();
+ assr.assemble().unwrap();
+
+ let key = Key { id: 2 };
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assr.set_total_size(2).unwrap();
+ assr.add(&[0x00], 0).unwrap();
+ assert_eq!(assr.assemble(), None);
+ let assr = set.get(&key, Instant::ZERO).unwrap();
+ assr.add(&[0x01], 1).unwrap();
+ assert_eq!(assr.assemble(), Some(&[0x00, 0x01][..]));
+ }
+}
diff --git a/src/iface/interface/ethernet.rs b/src/iface/interface/ethernet.rs
new file mode 100644
index 0000000..e2555a1
--- /dev/null
+++ b/src/iface/interface/ethernet.rs
@@ -0,0 +1,76 @@
+use super::check;
+use super::DispatchError;
+use super::EthernetPacket;
+use super::FragmentsBuffer;
+use super::InterfaceInner;
+use super::SocketSet;
+use core::result::Result;
+
+use crate::phy::TxToken;
+use crate::wire::*;
+
+impl InterfaceInner {
+ #[cfg(feature = "medium-ethernet")]
+ pub(super) fn process_ethernet<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: crate::phy::PacketMeta,
+ frame: &'frame [u8],
+ fragments: &'frame mut FragmentsBuffer,
+ ) -> Option<EthernetPacket<'frame>> {
+ let eth_frame = check!(EthernetFrame::new_checked(frame));
+
+ // Ignore any packets not directed to our hardware address or any of the multicast groups.
+ if !eth_frame.dst_addr().is_broadcast()
+ && !eth_frame.dst_addr().is_multicast()
+ && HardwareAddress::Ethernet(eth_frame.dst_addr()) != self.hardware_addr
+ {
+ return None;
+ }
+
+ match eth_frame.ethertype() {
+ #[cfg(feature = "proto-ipv4")]
+ EthernetProtocol::Arp => self.process_arp(self.now, &eth_frame),
+ #[cfg(feature = "proto-ipv4")]
+ EthernetProtocol::Ipv4 => {
+ let ipv4_packet = check!(Ipv4Packet::new_checked(eth_frame.payload()));
+
+ self.process_ipv4(sockets, meta, &ipv4_packet, fragments)
+ .map(EthernetPacket::Ip)
+ }
+ #[cfg(feature = "proto-ipv6")]
+ EthernetProtocol::Ipv6 => {
+ let ipv6_packet = check!(Ipv6Packet::new_checked(eth_frame.payload()));
+ self.process_ipv6(sockets, meta, &ipv6_packet)
+ .map(EthernetPacket::Ip)
+ }
+ // Drop all other traffic.
+ _ => None,
+ }
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ pub(super) fn dispatch_ethernet<Tx, F>(
+ &mut self,
+ tx_token: Tx,
+ buffer_len: usize,
+ f: F,
+ ) -> Result<(), DispatchError>
+ where
+ Tx: TxToken,
+ F: FnOnce(EthernetFrame<&mut [u8]>),
+ {
+ let tx_len = EthernetFrame::<&[u8]>::buffer_len(buffer_len);
+ tx_token.consume(tx_len, |tx_buffer| {
+ debug_assert!(tx_buffer.as_ref().len() == tx_len);
+ let mut frame = EthernetFrame::new_unchecked(tx_buffer);
+
+ let src_addr = self.hardware_addr.ethernet_or_panic();
+ frame.set_src_addr(src_addr);
+
+ f(frame);
+
+ Ok(())
+ })
+ }
+}
diff --git a/src/iface/interface/ieee802154.rs b/src/iface/interface/ieee802154.rs
new file mode 100644
index 0000000..0feca5e
--- /dev/null
+++ b/src/iface/interface/ieee802154.rs
@@ -0,0 +1,94 @@
+use super::*;
+
+use crate::phy::TxToken;
+use crate::wire::*;
+
+impl InterfaceInner {
+ pub(super) fn process_ieee802154<'output, 'payload: 'output>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ sixlowpan_payload: &'payload [u8],
+ _fragments: &'output mut FragmentsBuffer,
+ ) -> Option<Packet<'output>> {
+ let ieee802154_frame = check!(Ieee802154Frame::new_checked(sixlowpan_payload));
+ let ieee802154_repr = check!(Ieee802154Repr::parse(&ieee802154_frame));
+
+ if ieee802154_repr.frame_type != Ieee802154FrameType::Data {
+ return None;
+ }
+
+ // Drop frames when the user has set a PAN id and the PAN id from frame is not equal to this
+ // When the user didn't set a PAN id (so it is None), then we accept all PAN id's.
+ // We always accept the broadcast PAN id.
+ if self.pan_id.is_some()
+ && ieee802154_repr.dst_pan_id != self.pan_id
+ && ieee802154_repr.dst_pan_id != Some(Ieee802154Pan::BROADCAST)
+ {
+ net_debug!(
+ "IEEE802.15.4: dropping {:?} because not our PAN id (or not broadcast)",
+ ieee802154_repr
+ );
+ return None;
+ }
+
+ match ieee802154_frame.payload() {
+ Some(payload) => {
+ self.process_sixlowpan(sockets, meta, &ieee802154_repr, payload, _fragments)
+ }
+ None => None,
+ }
+ }
+
+ pub(super) fn dispatch_ieee802154<Tx: TxToken>(
+ &mut self,
+ ll_dst_a: Ieee802154Address,
+ tx_token: Tx,
+ meta: PacketMeta,
+ packet: Packet,
+ frag: &mut Fragmenter,
+ ) {
+ let ll_src_a = self.hardware_addr.ieee802154_or_panic();
+
+ // Create the IEEE802.15.4 header.
+ let ieee_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(self.get_sequence_number()),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: self.pan_id,
+ dst_addr: Some(ll_dst_a),
+ src_pan_id: self.pan_id,
+ src_addr: Some(ll_src_a),
+ };
+
+ self.dispatch_sixlowpan(tx_token, meta, packet, ieee_repr, frag);
+ }
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ pub(super) fn dispatch_ieee802154_frag<Tx: TxToken>(
+ &mut self,
+ tx_token: Tx,
+ frag: &mut Fragmenter,
+ ) {
+ // Create the IEEE802.15.4 header.
+ let ieee_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(self.get_sequence_number()),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: self.pan_id,
+ dst_addr: Some(frag.sixlowpan.ll_dst_addr),
+ src_pan_id: self.pan_id,
+ src_addr: Some(frag.sixlowpan.ll_src_addr),
+ };
+
+ self.dispatch_sixlowpan_frag(tx_token, ieee_repr, frag);
+ }
+}
diff --git a/src/iface/interface/igmp.rs b/src/iface/interface/igmp.rs
new file mode 100644
index 0000000..14856ca
--- /dev/null
+++ b/src/iface/interface/igmp.rs
@@ -0,0 +1,275 @@
+use super::*;
+
+use crate::phy::{Device, PacketMeta};
+use crate::time::{Duration, Instant};
+use crate::wire::*;
+
+use core::result::Result;
+
+/// Error type for `join_multicast_group`, `leave_multicast_group`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum MulticastError {
+ /// The hardware device transmit buffer is full. Try again later.
+ Exhausted,
+ /// The table of joined multicast groups is already full.
+ GroupTableFull,
+ /// IPv6 multicast is not yet supported.
+ Ipv6NotSupported,
+}
+
+impl core::fmt::Display for MulticastError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ MulticastError::Exhausted => write!(f, "Exhausted"),
+ MulticastError::GroupTableFull => write!(f, "GroupTableFull"),
+ MulticastError::Ipv6NotSupported => write!(f, "Ipv6NotSupported"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for MulticastError {}
+
+impl Interface {
+ /// Add an address to a list of subscribed multicast IP addresses.
+ ///
+ /// Returns `Ok(announce_sent)` if the address was added successfully, where `announce_sent`
+ /// indicates whether an initial immediate announcement has been sent.
+ pub fn join_multicast_group<D, T: Into<IpAddress>>(
+ &mut self,
+ device: &mut D,
+ addr: T,
+ timestamp: Instant,
+ ) -> Result<bool, MulticastError>
+ where
+ D: Device + ?Sized,
+ {
+ self.inner.now = timestamp;
+
+ match addr.into() {
+ IpAddress::Ipv4(addr) => {
+ let is_not_new = self
+ .inner
+ .ipv4_multicast_groups
+ .insert(addr, ())
+ .map_err(|_| MulticastError::GroupTableFull)?
+ .is_some();
+ if is_not_new {
+ Ok(false)
+ } else if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr)
+ {
+ // Send initial membership report
+ let tx_token = device
+ .transmit(timestamp)
+ .ok_or(MulticastError::Exhausted)?;
+
+ // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
+ self.inner
+ .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
+ .unwrap();
+
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+ // Multicast is not yet implemented for other address families
+ #[allow(unreachable_patterns)]
+ _ => Err(MulticastError::Ipv6NotSupported),
+ }
+ }
+
+ /// Remove an address from the subscribed multicast IP addresses.
+ ///
+ /// Returns `Ok(leave_sent)` if the address was removed successfully, where `leave_sent`
+ /// indicates whether an immediate leave packet has been sent.
+ pub fn leave_multicast_group<D, T: Into<IpAddress>>(
+ &mut self,
+ device: &mut D,
+ addr: T,
+ timestamp: Instant,
+ ) -> Result<bool, MulticastError>
+ where
+ D: Device + ?Sized,
+ {
+ self.inner.now = timestamp;
+
+ match addr.into() {
+ IpAddress::Ipv4(addr) => {
+ let was_not_present = self.inner.ipv4_multicast_groups.remove(&addr).is_none();
+ if was_not_present {
+ Ok(false)
+ } else if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
+ // Send group leave packet
+ let tx_token = device
+ .transmit(timestamp)
+ .ok_or(MulticastError::Exhausted)?;
+
+ // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
+ self.inner
+ .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
+ .unwrap();
+
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+ // Multicast is not yet implemented for other address families
+ #[allow(unreachable_patterns)]
+ _ => Err(MulticastError::Ipv6NotSupported),
+ }
+ }
+
+ /// Check whether the interface listens to given destination multicast IP address.
+ pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
+ self.inner.has_multicast_group(addr)
+ }
+
+ /// Depending on `igmp_report_state` and the therein contained
+ /// timeouts, send IGMP membership reports.
+ pub(crate) fn igmp_egress<D>(&mut self, device: &mut D) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ match self.inner.igmp_report_state {
+ IgmpReportState::ToSpecificQuery {
+ version,
+ timeout,
+ group,
+ } if self.inner.now >= timeout => {
+ if let Some(pkt) = self.inner.igmp_report_packet(version, group) {
+ // Send initial membership report
+ if let Some(tx_token) = device.transmit(self.inner.now) {
+ // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
+ self.inner
+ .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
+ .unwrap();
+ } else {
+ return false;
+ }
+ }
+
+ self.inner.igmp_report_state = IgmpReportState::Inactive;
+ true
+ }
+ IgmpReportState::ToGeneralQuery {
+ version,
+ timeout,
+ interval,
+ next_index,
+ } if self.inner.now >= timeout => {
+ let addr = self
+ .inner
+ .ipv4_multicast_groups
+ .iter()
+ .nth(next_index)
+ .map(|(addr, ())| *addr);
+
+ match addr {
+ Some(addr) => {
+ if let Some(pkt) = self.inner.igmp_report_packet(version, addr) {
+ // Send initial membership report
+ if let Some(tx_token) = device.transmit(self.inner.now) {
+ // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
+ self.inner
+ .dispatch_ip(
+ tx_token,
+ PacketMeta::default(),
+ pkt,
+ &mut self.fragmenter,
+ )
+ .unwrap();
+ } else {
+ return false;
+ }
+ }
+
+ let next_timeout = (timeout + interval).max(self.inner.now);
+ self.inner.igmp_report_state = IgmpReportState::ToGeneralQuery {
+ version,
+ timeout: next_timeout,
+ interval,
+ next_index: next_index + 1,
+ };
+ true
+ }
+
+ None => {
+ self.inner.igmp_report_state = IgmpReportState::Inactive;
+ false
+ }
+ }
+ }
+ _ => false,
+ }
+ }
+}
+
+impl InterfaceInner {
+ /// Host duties of the **IGMPv2** protocol.
+ ///
+ /// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries.
+ /// Membership must not be reported immediately in order to avoid flooding the network
+ /// after a query is broadcasted by a router; this is not currently done.
+ pub(super) fn process_igmp<'frame>(
+ &mut self,
+ ipv4_repr: Ipv4Repr,
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ let igmp_packet = check!(IgmpPacket::new_checked(ip_payload));
+ let igmp_repr = check!(IgmpRepr::parse(&igmp_packet));
+
+ // FIXME: report membership after a delay
+ match igmp_repr {
+ IgmpRepr::MembershipQuery {
+ group_addr,
+ version,
+ max_resp_time,
+ } => {
+ // General query
+ if group_addr.is_unspecified()
+ && ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS
+ {
+ // Are we member in any groups?
+ if self.ipv4_multicast_groups.iter().next().is_some() {
+ let interval = match version {
+ IgmpVersion::Version1 => Duration::from_millis(100),
+ IgmpVersion::Version2 => {
+ // No dependence on a random generator
+ // (see [#24](https://github.com/m-labs/smoltcp/issues/24))
+ // but at least spread reports evenly across max_resp_time.
+ let intervals = self.ipv4_multicast_groups.len() as u32 + 1;
+ max_resp_time / intervals
+ }
+ };
+ self.igmp_report_state = IgmpReportState::ToGeneralQuery {
+ version,
+ timeout: self.now + interval,
+ interval,
+ next_index: 0,
+ };
+ }
+ } else {
+ // Group-specific query
+ if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr {
+ // Don't respond immediately
+ let timeout = max_resp_time / 4;
+ self.igmp_report_state = IgmpReportState::ToSpecificQuery {
+ version,
+ timeout: self.now + timeout,
+ group: group_addr,
+ };
+ }
+ }
+ }
+ // Ignore membership reports
+ IgmpRepr::MembershipReport { .. } => (),
+ // Ignore hosts leaving groups
+ IgmpRepr::LeaveGroup { .. } => (),
+ }
+
+ None
+ }
+}
diff --git a/src/iface/interface/ipv4.rs b/src/iface/interface/ipv4.rs
new file mode 100644
index 0000000..7569572
--- /dev/null
+++ b/src/iface/interface/ipv4.rs
@@ -0,0 +1,445 @@
+use super::*;
+
+#[cfg(feature = "socket-dhcpv4")]
+use crate::socket::dhcpv4;
+#[cfg(feature = "socket-icmp")]
+use crate::socket::icmp;
+use crate::socket::AnySocket;
+
+use crate::phy::{Medium, TxToken};
+use crate::time::Instant;
+use crate::wire::*;
+
+impl InterfaceInner {
+ pub(super) fn process_ipv4<'a>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ipv4_packet: &Ipv4Packet<&'a [u8]>,
+ frag: &'a mut FragmentsBuffer,
+ ) -> Option<Packet<'a>> {
+ let ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum));
+ if !self.is_unicast_v4(ipv4_repr.src_addr) && !ipv4_repr.src_addr.is_unspecified() {
+ // Discard packets with non-unicast source addresses but allow unspecified
+ net_debug!("non-unicast or unspecified source address");
+ return None;
+ }
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ let ip_payload = {
+ if ipv4_packet.more_frags() || ipv4_packet.frag_offset() != 0 {
+ let key = FragKey::Ipv4(ipv4_packet.get_key());
+
+ let f = match frag.assembler.get(&key, self.now + frag.reassembly_timeout) {
+ Ok(f) => f,
+ Err(_) => {
+ net_debug!("No available packet assembler for fragmented packet");
+ return None;
+ }
+ };
+
+ if !ipv4_packet.more_frags() {
+ // This is the last fragment, so we know the total size
+ check!(f.set_total_size(
+ ipv4_packet.total_len() as usize - ipv4_packet.header_len() as usize
+ + ipv4_packet.frag_offset() as usize,
+ ));
+ }
+
+ if let Err(e) = f.add(ipv4_packet.payload(), ipv4_packet.frag_offset() as usize) {
+ net_debug!("fragmentation error: {:?}", e);
+ return None;
+ }
+
+ // NOTE: according to the standard, the total length needs to be
+ // recomputed, as well as the checksum. However, we don't really use
+ // the IPv4 header after the packet is reassembled.
+ match f.assemble() {
+ Some(payload) => payload,
+ None => return None,
+ }
+ } else {
+ ipv4_packet.payload()
+ }
+ };
+
+ #[cfg(not(feature = "proto-ipv4-fragmentation"))]
+ let ip_payload = ipv4_packet.payload();
+
+ let ip_repr = IpRepr::Ipv4(ipv4_repr);
+
+ #[cfg(feature = "socket-raw")]
+ let handled_by_raw_socket = self.raw_socket_filter(sockets, &ip_repr, ip_payload);
+ #[cfg(not(feature = "socket-raw"))]
+ let handled_by_raw_socket = false;
+
+ #[cfg(feature = "socket-dhcpv4")]
+ {
+ if ipv4_repr.next_header == IpProtocol::Udp
+ && matches!(self.caps.medium, Medium::Ethernet)
+ {
+ let udp_packet = check!(UdpPacket::new_checked(ip_payload));
+ if let Some(dhcp_socket) = sockets
+ .items_mut()
+ .find_map(|i| dhcpv4::Socket::downcast_mut(&mut i.socket))
+ {
+ // First check for source and dest ports, then do `UdpRepr::parse` if they match.
+ // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`)
+ if udp_packet.src_port() == dhcp_socket.server_port
+ && udp_packet.dst_port() == dhcp_socket.client_port
+ {
+ let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr());
+ let udp_repr = check!(UdpRepr::parse(
+ &udp_packet,
+ &src_addr,
+ &dst_addr,
+ &self.caps.checksum
+ ));
+ let udp_payload = udp_packet.payload();
+
+ dhcp_socket.process(self, &ipv4_repr, &udp_repr, udp_payload);
+ return None;
+ }
+ }
+ }
+ }
+
+ if !self.has_ip_addr(ipv4_repr.dst_addr)
+ && !self.has_multicast_group(ipv4_repr.dst_addr)
+ && !self.is_broadcast_v4(ipv4_repr.dst_addr)
+ {
+ // Ignore IP packets not directed at us, or broadcast, or any of the multicast groups.
+ // If AnyIP is enabled, also check if the packet is routed locally.
+ if !self.any_ip
+ || !ipv4_repr.dst_addr.is_unicast()
+ || self
+ .routes
+ .lookup(&IpAddress::Ipv4(ipv4_repr.dst_addr), self.now)
+ .map_or(true, |router_addr| !self.has_ip_addr(router_addr))
+ {
+ return None;
+ }
+ }
+
+ match ipv4_repr.next_header {
+ IpProtocol::Icmp => self.process_icmpv4(sockets, ip_repr, ip_payload),
+
+ #[cfg(feature = "proto-igmp")]
+ IpProtocol::Igmp => self.process_igmp(ipv4_repr, ip_payload),
+
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ IpProtocol::Udp => {
+ let udp_packet = check!(UdpPacket::new_checked(ip_payload));
+ let udp_repr = check!(UdpRepr::parse(
+ &udp_packet,
+ &ipv4_repr.src_addr.into(),
+ &ipv4_repr.dst_addr.into(),
+ &self.checksum_caps(),
+ ));
+
+ self.process_udp(
+ sockets,
+ meta,
+ ip_repr,
+ udp_repr,
+ handled_by_raw_socket,
+ udp_packet.payload(),
+ ip_payload,
+ )
+ }
+
+ #[cfg(feature = "socket-tcp")]
+ IpProtocol::Tcp => self.process_tcp(sockets, ip_repr, ip_payload),
+
+ _ if handled_by_raw_socket => None,
+
+ _ => {
+ // Send back as much of the original payload as we can.
+ let payload_len =
+ icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len());
+ let icmp_reply_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::ProtoUnreachable,
+ header: ipv4_repr,
+ data: &ip_payload[0..payload_len],
+ };
+ self.icmpv4_reply(ipv4_repr, icmp_reply_repr)
+ }
+ }
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ pub(super) fn process_arp<'frame>(
+ &mut self,
+ timestamp: Instant,
+ eth_frame: &EthernetFrame<&'frame [u8]>,
+ ) -> Option<EthernetPacket<'frame>> {
+ let arp_packet = check!(ArpPacket::new_checked(eth_frame.payload()));
+ let arp_repr = check!(ArpRepr::parse(&arp_packet));
+
+ match arp_repr {
+ ArpRepr::EthernetIpv4 {
+ operation,
+ source_hardware_addr,
+ source_protocol_addr,
+ target_protocol_addr,
+ ..
+ } => {
+ // Only process ARP packets for us.
+ if !self.has_ip_addr(target_protocol_addr) {
+ return None;
+ }
+
+ // Only process REQUEST and RESPONSE.
+ if let ArpOperation::Unknown(_) = operation {
+ net_debug!("arp: unknown operation code");
+ return None;
+ }
+
+ // Discard packets with non-unicast source addresses.
+ if !source_protocol_addr.is_unicast() || !source_hardware_addr.is_unicast() {
+ net_debug!("arp: non-unicast source address");
+ return None;
+ }
+
+ if !self.in_same_network(&IpAddress::Ipv4(source_protocol_addr)) {
+ net_debug!("arp: source IP address not in same network as us");
+ return None;
+ }
+
+ // Fill the ARP cache from any ARP packet aimed at us (both request or response).
+ // We fill from requests too because if someone is requesting our address they
+ // are probably going to talk to us, so we avoid having to request their address
+ // when we later reply to them.
+ self.neighbor_cache.fill(
+ source_protocol_addr.into(),
+ source_hardware_addr.into(),
+ timestamp,
+ );
+
+ if operation == ArpOperation::Request {
+ let src_hardware_addr = self.hardware_addr.ethernet_or_panic();
+
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: src_hardware_addr,
+ source_protocol_addr: target_protocol_addr,
+ target_hardware_addr: source_hardware_addr,
+ target_protocol_addr: source_protocol_addr,
+ }))
+ } else {
+ None
+ }
+ }
+ }
+ }
+
+ pub(super) fn process_icmpv4<'frame>(
+ &mut self,
+ _sockets: &mut SocketSet,
+ ip_repr: IpRepr,
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ let icmp_packet = check!(Icmpv4Packet::new_checked(ip_payload));
+ let icmp_repr = check!(Icmpv4Repr::parse(&icmp_packet, &self.caps.checksum));
+
+ #[cfg(feature = "socket-icmp")]
+ let mut handled_by_icmp_socket = false;
+
+ #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))]
+ for icmp_socket in _sockets
+ .items_mut()
+ .filter_map(|i| icmp::Socket::downcast_mut(&mut i.socket))
+ {
+ if icmp_socket.accepts(self, &ip_repr, &icmp_repr.into()) {
+ icmp_socket.process(self, &ip_repr, &icmp_repr.into());
+ handled_by_icmp_socket = true;
+ }
+ }
+
+ match icmp_repr {
+ // Respond to echo requests.
+ #[cfg(feature = "proto-ipv4")]
+ Icmpv4Repr::EchoRequest {
+ ident,
+ seq_no,
+ data,
+ } => {
+ let icmp_reply_repr = Icmpv4Repr::EchoReply {
+ ident,
+ seq_no,
+ data,
+ };
+ match ip_repr {
+ IpRepr::Ipv4(ipv4_repr) => self.icmpv4_reply(ipv4_repr, icmp_reply_repr),
+ #[allow(unreachable_patterns)]
+ _ => unreachable!(),
+ }
+ }
+
+ // Ignore any echo replies.
+ Icmpv4Repr::EchoReply { .. } => None,
+
+ // Don't report an error if a packet with unknown type
+ // has been handled by an ICMP socket
+ #[cfg(feature = "socket-icmp")]
+ _ if handled_by_icmp_socket => None,
+
+ // FIXME: do something correct here?
+ _ => None,
+ }
+ }
+
+ pub(super) fn icmpv4_reply<'frame, 'icmp: 'frame>(
+ &self,
+ ipv4_repr: Ipv4Repr,
+ icmp_repr: Icmpv4Repr<'icmp>,
+ ) -> Option<Packet<'frame>> {
+ if !self.is_unicast_v4(ipv4_repr.src_addr) {
+ // Do not send ICMP replies to non-unicast sources
+ None
+ } else if self.is_unicast_v4(ipv4_repr.dst_addr) {
+ // Reply as normal when src_addr and dst_addr are both unicast
+ let ipv4_reply_repr = Ipv4Repr {
+ src_addr: ipv4_repr.dst_addr,
+ dst_addr: ipv4_repr.src_addr,
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ };
+ Some(Packet::new_ipv4(
+ ipv4_reply_repr,
+ IpPayload::Icmpv4(icmp_repr),
+ ))
+ } else if self.is_broadcast_v4(ipv4_repr.dst_addr) {
+ // Only reply to broadcasts for echo replies and not other ICMP messages
+ match icmp_repr {
+ Icmpv4Repr::EchoReply { .. } => match self.ipv4_addr() {
+ Some(src_addr) => {
+ let ipv4_reply_repr = Ipv4Repr {
+ src_addr,
+ dst_addr: ipv4_repr.src_addr,
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ };
+ Some(Packet::new_ipv4(
+ ipv4_reply_repr,
+ IpPayload::Icmpv4(icmp_repr),
+ ))
+ }
+ None => None,
+ },
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ pub(super) fn dispatch_ipv4_frag<Tx: TxToken>(&mut self, tx_token: Tx, frag: &mut Fragmenter) {
+ let caps = self.caps.clone();
+
+ let mtu_max = self.ip_mtu();
+ let ip_len = (frag.packet_len - frag.sent_bytes + frag.ipv4.repr.buffer_len()).min(mtu_max);
+ let payload_len = ip_len - frag.ipv4.repr.buffer_len();
+
+ let more_frags = (frag.packet_len - frag.sent_bytes) != payload_len;
+ frag.ipv4.repr.payload_len = payload_len;
+ frag.sent_bytes += payload_len;
+
+ let mut tx_len = ip_len;
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(caps.medium, Medium::Ethernet) {
+ tx_len += EthernetFrame::<&[u8]>::header_len();
+ }
+
+ // Emit function for the Ethernet header.
+ #[cfg(feature = "medium-ethernet")]
+ let emit_ethernet = |repr: &IpRepr, tx_buffer: &mut [u8]| {
+ let mut frame = EthernetFrame::new_unchecked(tx_buffer);
+
+ let src_addr = self.hardware_addr.ethernet_or_panic();
+ frame.set_src_addr(src_addr);
+ frame.set_dst_addr(frag.ipv4.dst_hardware_addr);
+
+ match repr.version() {
+ #[cfg(feature = "proto-ipv4")]
+ IpVersion::Ipv4 => frame.set_ethertype(EthernetProtocol::Ipv4),
+ #[cfg(feature = "proto-ipv6")]
+ IpVersion::Ipv6 => frame.set_ethertype(EthernetProtocol::Ipv6),
+ }
+ };
+
+ tx_token.consume(tx_len, |mut tx_buffer| {
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(self.caps.medium, Medium::Ethernet) {
+ emit_ethernet(&IpRepr::Ipv4(frag.ipv4.repr), tx_buffer);
+ tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
+ }
+
+ let mut packet =
+ Ipv4Packet::new_unchecked(&mut tx_buffer[..frag.ipv4.repr.buffer_len()]);
+ frag.ipv4.repr.emit(&mut packet, &caps.checksum);
+ packet.set_ident(frag.ipv4.ident);
+ packet.set_more_frags(more_frags);
+ packet.set_dont_frag(false);
+ packet.set_frag_offset(frag.ipv4.frag_offset);
+
+ if caps.checksum.ipv4.tx() {
+ packet.fill_checksum();
+ }
+
+ tx_buffer[frag.ipv4.repr.buffer_len()..][..payload_len].copy_from_slice(
+ &frag.buffer[frag.ipv4.frag_offset as usize + frag.ipv4.repr.buffer_len()..]
+ [..payload_len],
+ );
+
+ // Update the frag offset for the next fragment.
+ frag.ipv4.frag_offset += payload_len as u16;
+ })
+ }
+
+ #[cfg(feature = "proto-igmp")]
+ pub(super) fn igmp_report_packet<'any>(
+ &self,
+ version: IgmpVersion,
+ group_addr: Ipv4Address,
+ ) -> Option<Packet<'any>> {
+ let iface_addr = self.ipv4_addr()?;
+ let igmp_repr = IgmpRepr::MembershipReport {
+ group_addr,
+ version,
+ };
+ let pkt = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: iface_addr,
+ // Send to the group being reported
+ dst_addr: group_addr,
+ next_header: IpProtocol::Igmp,
+ payload_len: igmp_repr.buffer_len(),
+ hop_limit: 1,
+ // [#183](https://github.com/m-labs/smoltcp/issues/183).
+ },
+ IpPayload::Igmp(igmp_repr),
+ );
+ Some(pkt)
+ }
+
+ #[cfg(feature = "proto-igmp")]
+ pub(super) fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option<Packet<'any>> {
+ self.ipv4_addr().map(|iface_addr| {
+ let igmp_repr = IgmpRepr::LeaveGroup { group_addr };
+ Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: iface_addr,
+ dst_addr: Ipv4Address::MULTICAST_ALL_ROUTERS,
+ next_header: IpProtocol::Igmp,
+ payload_len: igmp_repr.buffer_len(),
+ hop_limit: 1,
+ },
+ IpPayload::Igmp(igmp_repr),
+ )
+ })
+ }
+}
diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs
new file mode 100644
index 0000000..22e65b9
--- /dev/null
+++ b/src/iface/interface/ipv6.rs
@@ -0,0 +1,355 @@
+use super::*;
+
+#[cfg(feature = "socket-icmp")]
+use crate::socket::icmp;
+use crate::socket::AnySocket;
+
+use crate::phy::PacketMeta;
+use crate::wire::*;
+
+/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMMP
+/// parameter problem message needs to be transmitted to the source of the address. In other cases,
+/// the processing of the IP packet can continue.
+#[allow(clippy::large_enum_variant)]
+enum HopByHopResponse<'frame> {
+ /// Continue processing the IPv6 packet.
+ Continue((IpProtocol, &'frame [u8])),
+ /// Discard the packet and maybe send back an ICMPv6 packet.
+ Discard(Option<Packet<'frame>>),
+}
+
+// We implement `Default` such that we can use the check! macro.
+impl Default for HopByHopResponse<'_> {
+ fn default() -> Self {
+ Self::Discard(None)
+ }
+}
+
+impl InterfaceInner {
+ pub(super) fn process_ipv6<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ipv6_packet: &Ipv6Packet<&'frame [u8]>,
+ ) -> Option<Packet<'frame>> {
+ let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet));
+
+ if !ipv6_repr.src_addr.is_unicast() {
+ // Discard packets with non-unicast source addresses.
+ net_debug!("non-unicast source address");
+ return None;
+ }
+
+ let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop {
+ match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) {
+ HopByHopResponse::Discard(e) => return e,
+ HopByHopResponse::Continue(next) => next,
+ }
+ } else {
+ (ipv6_repr.next_header, ipv6_packet.payload())
+ };
+
+ if !self.has_ip_addr(ipv6_repr.dst_addr)
+ && !self.has_multicast_group(ipv6_repr.dst_addr)
+ && !ipv6_repr.dst_addr.is_loopback()
+ {
+ net_trace!("packet IP address not for this interface");
+ return None;
+ }
+
+ #[cfg(feature = "socket-raw")]
+ let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload);
+ #[cfg(not(feature = "socket-raw"))]
+ let handled_by_raw_socket = false;
+
+ self.process_nxt_hdr(
+ sockets,
+ meta,
+ ipv6_repr,
+ next_header,
+ handled_by_raw_socket,
+ ip_payload,
+ )
+ }
+
+ fn process_hopbyhop<'frame>(
+ &mut self,
+ ipv6_repr: Ipv6Repr,
+ ip_payload: &'frame [u8],
+ ) -> HopByHopResponse<'frame> {
+ let param_problem = || {
+ let payload_len =
+ icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
+ self.icmpv6_reply(
+ ipv6_repr,
+ Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedOption,
+ pointer: ipv6_repr.buffer_len() as u32,
+ header: ipv6_repr,
+ data: &ip_payload[0..payload_len],
+ },
+ )
+ };
+
+ let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload));
+ let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr));
+ let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data));
+ let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr));
+
+ for opt_repr in &hbh_repr.options {
+ match opt_repr {
+ Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (),
+ #[cfg(feature = "proto-rpl")]
+ Ipv6OptionRepr::Rpl(_) => {}
+
+ Ipv6OptionRepr::Unknown { type_, .. } => {
+ match Ipv6OptionFailureType::from(*type_) {
+ Ipv6OptionFailureType::Skip => (),
+ Ipv6OptionFailureType::Discard => {
+ return HopByHopResponse::Discard(None);
+ }
+ Ipv6OptionFailureType::DiscardSendAll => {
+ return HopByHopResponse::Discard(param_problem());
+ }
+ Ipv6OptionFailureType::DiscardSendUnicast
+ if !ipv6_repr.dst_addr.is_multicast() =>
+ {
+ return HopByHopResponse::Discard(param_problem());
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ }
+
+ HopByHopResponse::Continue((
+ ext_repr.next_header,
+ &ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
+ ))
+ }
+
+ /// Given the next header value forward the payload onto the correct process
+ /// function.
+ fn process_nxt_hdr<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ipv6_repr: Ipv6Repr,
+ nxt_hdr: IpProtocol,
+ handled_by_raw_socket: bool,
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ match nxt_hdr {
+ IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr.into(), ip_payload),
+
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ IpProtocol::Udp => {
+ let udp_packet = check!(UdpPacket::new_checked(ip_payload));
+ let udp_repr = check!(UdpRepr::parse(
+ &udp_packet,
+ &ipv6_repr.src_addr.into(),
+ &ipv6_repr.dst_addr.into(),
+ &self.checksum_caps(),
+ ));
+
+ self.process_udp(
+ sockets,
+ meta,
+ ipv6_repr.into(),
+ udp_repr,
+ handled_by_raw_socket,
+ udp_packet.payload(),
+ ip_payload,
+ )
+ }
+
+ #[cfg(feature = "socket-tcp")]
+ IpProtocol::Tcp => self.process_tcp(sockets, ipv6_repr.into(), ip_payload),
+
+ #[cfg(feature = "socket-raw")]
+ _ if handled_by_raw_socket => None,
+
+ _ => {
+ // Send back as much of the original payload as we can.
+ let payload_len =
+ icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
+ let icmp_reply_repr = Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
+ // The offending packet is after the IPv6 header.
+ pointer: ipv6_repr.buffer_len() as u32,
+ header: ipv6_repr,
+ data: &ip_payload[0..payload_len],
+ };
+ self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
+ }
+ }
+ }
+
+ pub(super) fn process_icmpv6<'frame>(
+ &mut self,
+ _sockets: &mut SocketSet,
+ ip_repr: IpRepr,
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload));
+ let icmp_repr = check!(Icmpv6Repr::parse(
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ &icmp_packet,
+ &self.caps.checksum,
+ ));
+
+ #[cfg(feature = "socket-icmp")]
+ let mut handled_by_icmp_socket = false;
+
+ #[cfg(feature = "socket-icmp")]
+ for icmp_socket in _sockets
+ .items_mut()
+ .filter_map(|i| icmp::Socket::downcast_mut(&mut i.socket))
+ {
+ if icmp_socket.accepts(self, &ip_repr, &icmp_repr.into()) {
+ icmp_socket.process(self, &ip_repr, &icmp_repr.into());
+ handled_by_icmp_socket = true;
+ }
+ }
+
+ match icmp_repr {
+ // Respond to echo requests.
+ Icmpv6Repr::EchoRequest {
+ ident,
+ seq_no,
+ data,
+ } => match ip_repr {
+ IpRepr::Ipv6(ipv6_repr) => {
+ let icmp_reply_repr = Icmpv6Repr::EchoReply {
+ ident,
+ seq_no,
+ data,
+ };
+ self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
+ }
+ #[allow(unreachable_patterns)]
+ _ => unreachable!(),
+ },
+
+ // Ignore any echo replies.
+ Icmpv6Repr::EchoReply { .. } => None,
+
+ // Forward any NDISC packets to the ndisc packet handler
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit() == 0xff => match ip_repr {
+ IpRepr::Ipv6(ipv6_repr) => match self.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => self.process_ndisc(ipv6_repr, repr),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => self.process_ndisc(ipv6_repr, repr),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => None,
+ },
+ #[allow(unreachable_patterns)]
+ _ => unreachable!(),
+ },
+
+ // Don't report an error if a packet with unknown type
+ // has been handled by an ICMP socket
+ #[cfg(feature = "socket-icmp")]
+ _ if handled_by_icmp_socket => None,
+
+ // FIXME: do something correct here?
+ _ => None,
+ }
+ }
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ pub(super) fn process_ndisc<'frame>(
+ &mut self,
+ ip_repr: Ipv6Repr,
+ repr: NdiscRepr<'frame>,
+ ) -> Option<Packet<'frame>> {
+ match repr {
+ NdiscRepr::NeighborAdvert {
+ lladdr,
+ target_addr,
+ flags,
+ } => {
+ let ip_addr = ip_repr.src_addr.into();
+ if let Some(lladdr) = lladdr {
+ let lladdr = check!(lladdr.parse(self.caps.medium));
+ if !lladdr.is_unicast() || !target_addr.is_unicast() {
+ return None;
+ }
+ if flags.contains(NdiscNeighborFlags::OVERRIDE)
+ || !self.neighbor_cache.lookup(&ip_addr, self.now).found()
+ {
+ self.neighbor_cache.fill(ip_addr, lladdr, self.now)
+ }
+ }
+ None
+ }
+ NdiscRepr::NeighborSolicit {
+ target_addr,
+ lladdr,
+ ..
+ } => {
+ if let Some(lladdr) = lladdr {
+ let lladdr = check!(lladdr.parse(self.caps.medium));
+ if !lladdr.is_unicast() || !target_addr.is_unicast() {
+ return None;
+ }
+ self.neighbor_cache
+ .fill(ip_repr.src_addr.into(), lladdr, self.now);
+ }
+
+ if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
+ let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr,
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ lladdr: Some(self.hardware_addr.into()),
+ });
+ let ip_repr = Ipv6Repr {
+ src_addr: target_addr,
+ dst_addr: ip_repr.src_addr,
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 0xff,
+ payload_len: advert.buffer_len(),
+ };
+ Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert)))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }
+
+ pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
+ &self,
+ ipv6_repr: Ipv6Repr,
+ icmp_repr: Icmpv6Repr<'icmp>,
+ ) -> Option<Packet<'frame>> {
+ let src_addr = ipv6_repr.dst_addr;
+ let dst_addr = ipv6_repr.src_addr;
+
+ let src_addr = if src_addr.is_unicast() {
+ src_addr
+ } else if let Some(addr) = self.get_source_address_ipv6(&dst_addr) {
+ addr
+ } else {
+ net_debug!("no suitable source address found");
+ return None;
+ };
+
+ let ipv6_reply_repr = Ipv6Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ };
+ Some(Packet::new_ipv6(
+ ipv6_reply_repr,
+ IpPayload::Icmpv6(icmp_repr),
+ ))
+ }
+}
diff --git a/src/iface/interface/mod.rs b/src/iface/interface/mod.rs
new file mode 100644
index 0000000..7d6bdc8
--- /dev/null
+++ b/src/iface/interface/mod.rs
@@ -0,0 +1,1644 @@
+// Heads up! Before working on this file you should read the parts
+// of RFC 1122 that discuss Ethernet, ARP and IP for any IPv4 work
+// and RFCs 8200 and 4861 for any IPv6 and NDISC work.
+
+#[cfg(test)]
+mod tests;
+
+#[cfg(feature = "medium-ethernet")]
+mod ethernet;
+#[cfg(feature = "medium-ieee802154")]
+mod ieee802154;
+
+#[cfg(feature = "proto-ipv4")]
+mod ipv4;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6;
+#[cfg(feature = "proto-sixlowpan")]
+mod sixlowpan;
+
+#[cfg(feature = "proto-igmp")]
+mod igmp;
+
+#[cfg(feature = "proto-igmp")]
+pub use igmp::MulticastError;
+
+use super::packet::*;
+
+use core::result::Result;
+use heapless::{LinearMap, Vec};
+
+#[cfg(feature = "_proto-fragmentation")]
+use super::fragmentation::FragKey;
+#[cfg(any(feature = "proto-ipv4", feature = "proto-sixlowpan"))]
+use super::fragmentation::PacketAssemblerSet;
+use super::fragmentation::{Fragmenter, FragmentsBuffer};
+
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+use super::neighbor::{Answer as NeighborAnswer, Cache as NeighborCache};
+use super::socket_set::SocketSet;
+use crate::config::{
+ IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT,
+ IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT,
+};
+use crate::iface::Routes;
+use crate::phy::PacketMeta;
+use crate::phy::{ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken};
+use crate::rand::Rand;
+#[cfg(feature = "socket-dns")]
+use crate::socket::dns;
+use crate::socket::*;
+use crate::time::{Duration, Instant};
+
+use crate::wire::*;
+
+macro_rules! check {
+ ($e:expr) => {
+ match $e {
+ Ok(x) => x,
+ Err(_) => {
+ // concat!/stringify! doesn't work with defmt macros
+ #[cfg(not(feature = "defmt"))]
+ net_trace!(concat!("iface: malformed ", stringify!($e)));
+ #[cfg(feature = "defmt")]
+ net_trace!("iface: malformed");
+ return Default::default();
+ }
+ }
+ };
+}
+use check;
+
+/// A network interface.
+///
+/// The network interface logically owns a number of other data structures; to avoid
+/// a dependency on heap allocation, it instead owns a `BorrowMut<[T]>`, which can be
+/// a `&mut [T]`, or `Vec<T>` if a heap is available.
+pub struct Interface {
+ pub(crate) inner: InterfaceInner,
+ fragments: FragmentsBuffer,
+ fragmenter: Fragmenter,
+}
+
+/// The device independent part of an Ethernet network interface.
+///
+/// Separating the device from the data required for processing and dispatching makes
+/// it possible to borrow them independently. For example, the tx and rx tokens borrow
+/// the `device` mutably until they're used, which makes it impossible to call other
+/// methods on the `Interface` in this time (since its `device` field is borrowed
+/// exclusively). However, it is still possible to call methods on its `inner` field.
+pub struct InterfaceInner {
+ caps: DeviceCapabilities,
+ now: Instant,
+ rand: Rand,
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ neighbor_cache: NeighborCache,
+ hardware_addr: HardwareAddress,
+ #[cfg(feature = "medium-ieee802154")]
+ sequence_no: u8,
+ #[cfg(feature = "medium-ieee802154")]
+ pan_id: Option<Ieee802154Pan>,
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ ipv4_id: u16,
+ #[cfg(feature = "proto-sixlowpan")]
+ sixlowpan_address_context:
+ Vec<SixlowpanAddressContext, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT>,
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ tag: u16,
+ ip_addrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT>,
+ #[cfg(feature = "proto-ipv4")]
+ any_ip: bool,
+ routes: Routes,
+ #[cfg(feature = "proto-igmp")]
+ ipv4_multicast_groups: LinearMap<Ipv4Address, (), IFACE_MAX_MULTICAST_GROUP_COUNT>,
+ /// When to report for (all or) the next multicast group membership via IGMP
+ #[cfg(feature = "proto-igmp")]
+ igmp_report_state: IgmpReportState,
+}
+
+/// Configuration structure used for creating a network interface.
+#[non_exhaustive]
+pub struct Config {
+ /// Random seed.
+ ///
+ /// It is strongly recommended that the random seed is different on each boot,
+ /// to avoid problems with TCP port/sequence collisions.
+ ///
+ /// The seed doesn't have to be cryptographically secure.
+ pub random_seed: u64,
+
+ /// Set the Hardware address the interface will use.
+ ///
+ /// # Panics
+ /// Creating the interface panics if the address is not unicast.
+ pub hardware_addr: HardwareAddress,
+
+ /// Set the IEEE802.15.4 PAN ID the interface will use.
+ ///
+ /// **NOTE**: we use the same PAN ID for destination and source.
+ #[cfg(feature = "medium-ieee802154")]
+ pub pan_id: Option<Ieee802154Pan>,
+}
+
+impl Config {
+ pub fn new(hardware_addr: HardwareAddress) -> Self {
+ Config {
+ random_seed: 0,
+ hardware_addr,
+ #[cfg(feature = "medium-ieee802154")]
+ pan_id: None,
+ }
+ }
+}
+
+impl Interface {
+ /// Create a network interface using the previously provided configuration.
+ ///
+ /// # Panics
+ /// This function panics if the [`Config::hardware_address`] does not match
+ /// the medium of the device.
+ pub fn new<D>(config: Config, device: &mut D, now: Instant) -> Self
+ where
+ D: Device + ?Sized,
+ {
+ let caps = device.capabilities();
+ assert_eq!(
+ config.hardware_addr.medium(),
+ caps.medium,
+ "The hardware address does not match the medium of the interface."
+ );
+
+ let mut rand = Rand::new(config.random_seed);
+
+ #[cfg(feature = "medium-ieee802154")]
+ let mut sequence_no;
+ #[cfg(feature = "medium-ieee802154")]
+ loop {
+ sequence_no = (rand.rand_u32() & 0xff) as u8;
+ if sequence_no != 0 {
+ break;
+ }
+ }
+
+ #[cfg(feature = "proto-sixlowpan")]
+ let mut tag;
+
+ #[cfg(feature = "proto-sixlowpan")]
+ loop {
+ tag = rand.rand_u16();
+ if tag != 0 {
+ break;
+ }
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ let mut ipv4_id;
+
+ #[cfg(feature = "proto-ipv4")]
+ loop {
+ ipv4_id = rand.rand_u16();
+ if ipv4_id != 0 {
+ break;
+ }
+ }
+
+ Interface {
+ fragments: FragmentsBuffer {
+ #[cfg(feature = "proto-sixlowpan")]
+ decompress_buf: [0u8; sixlowpan::MAX_DECOMPRESSED_LEN],
+
+ #[cfg(feature = "_proto-fragmentation")]
+ assembler: PacketAssemblerSet::new(),
+ #[cfg(feature = "_proto-fragmentation")]
+ reassembly_timeout: Duration::from_secs(60),
+ },
+ fragmenter: Fragmenter::new(),
+ inner: InterfaceInner {
+ now,
+ caps,
+ hardware_addr: config.hardware_addr,
+ ip_addrs: Vec::new(),
+ #[cfg(feature = "proto-ipv4")]
+ any_ip: false,
+ routes: Routes::new(),
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ neighbor_cache: NeighborCache::new(),
+ #[cfg(feature = "proto-igmp")]
+ ipv4_multicast_groups: LinearMap::new(),
+ #[cfg(feature = "proto-igmp")]
+ igmp_report_state: IgmpReportState::Inactive,
+ #[cfg(feature = "medium-ieee802154")]
+ sequence_no,
+ #[cfg(feature = "medium-ieee802154")]
+ pan_id: config.pan_id,
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ tag,
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ ipv4_id,
+ #[cfg(feature = "proto-sixlowpan")]
+ sixlowpan_address_context: Vec::new(),
+ rand,
+ },
+ }
+ }
+
+ /// Get the socket context.
+ ///
+ /// The context is needed for some socket methods.
+ pub fn context(&mut self) -> &mut InterfaceInner {
+ &mut self.inner
+ }
+
+ /// Get the HardwareAddress address of the interface.
+ ///
+ /// # Panics
+ /// This function panics if the medium is not Ethernet or Ieee802154.
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ pub fn hardware_addr(&self) -> HardwareAddress {
+ #[cfg(all(feature = "medium-ethernet", not(feature = "medium-ieee802154")))]
+ assert!(self.inner.caps.medium == Medium::Ethernet);
+ #[cfg(all(feature = "medium-ieee802154", not(feature = "medium-ethernet")))]
+ assert!(self.inner.caps.medium == Medium::Ieee802154);
+
+ #[cfg(all(feature = "medium-ieee802154", feature = "medium-ethernet"))]
+ assert!(
+ self.inner.caps.medium == Medium::Ethernet
+ || self.inner.caps.medium == Medium::Ieee802154
+ );
+
+ self.inner.hardware_addr
+ }
+
+ /// Set the HardwareAddress address of the interface.
+ ///
+ /// # Panics
+ /// This function panics if the address is not unicast, and if the medium is not Ethernet or
+ /// Ieee802154.
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ pub fn set_hardware_addr(&mut self, addr: HardwareAddress) {
+ #[cfg(all(feature = "medium-ethernet", not(feature = "medium-ieee802154")))]
+ assert!(self.inner.caps.medium == Medium::Ethernet);
+ #[cfg(all(feature = "medium-ieee802154", not(feature = "medium-ethernet")))]
+ assert!(self.inner.caps.medium == Medium::Ieee802154);
+
+ #[cfg(all(feature = "medium-ieee802154", feature = "medium-ethernet"))]
+ assert!(
+ self.inner.caps.medium == Medium::Ethernet
+ || self.inner.caps.medium == Medium::Ieee802154
+ );
+
+ InterfaceInner::check_hardware_addr(&addr);
+ self.inner.hardware_addr = addr;
+ }
+
+ /// Get the IP addresses of the interface.
+ pub fn ip_addrs(&self) -> &[IpCidr] {
+ self.inner.ip_addrs.as_ref()
+ }
+
+ /// Get the first IPv4 address if present.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn ipv4_addr(&self) -> Option<Ipv4Address> {
+ self.inner.ipv4_addr()
+ }
+
+ /// Get the first IPv6 address if present.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
+ self.inner.ipv6_addr()
+ }
+
+ /// Get an address from the interface that could be used as source address. For IPv4, this is
+ /// the first IPv4 address from the list of addresses. For IPv6, the address is based on the
+ /// destination address and uses RFC6724 for selecting the source address.
+ pub fn get_source_address(&self, dst_addr: &IpAddress) -> Option<IpAddress> {
+ self.inner.get_source_address(dst_addr)
+ }
+
+ /// Get an address from the interface that could be used as source address. This is the first
+ /// IPv4 address from the list of addresses in the interface.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn get_source_address_ipv4(&self, dst_addr: &Ipv4Address) -> Option<Ipv4Address> {
+ self.inner.get_source_address_ipv4(dst_addr)
+ }
+
+ /// Get an address from the interface that could be used as source address. The selection is
+ /// based on RFC6724.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Option<Ipv6Address> {
+ self.inner.get_source_address_ipv6(dst_addr)
+ }
+
+ /// Update the IP addresses of the interface.
+ ///
+ /// # Panics
+ /// This function panics if any of the addresses are not unicast.
+ pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr, IFACE_MAX_ADDR_COUNT>)>(&mut self, f: F) {
+ f(&mut self.inner.ip_addrs);
+ InterfaceInner::flush_cache(&mut self.inner);
+ InterfaceInner::check_ip_addrs(&self.inner.ip_addrs)
+ }
+
+ /// Check whether the interface has the given IP address assigned.
+ pub fn has_ip_addr<T: Into<IpAddress>>(&self, addr: T) -> bool {
+ self.inner.has_ip_addr(addr)
+ }
+
+ pub fn routes(&self) -> &Routes {
+ &self.inner.routes
+ }
+
+ pub fn routes_mut(&mut self) -> &mut Routes {
+ &mut self.inner.routes
+ }
+
+ /// Enable or disable the AnyIP capability.
+ ///
+ /// AnyIP allowins packets to be received
+ /// locally on IPv4 addresses other than the interface's configured [ip_addrs].
+ /// When AnyIP is enabled and a route prefix in [`routes`](Self::routes) specifies one of
+ /// the interface's [`ip_addrs`](Self::ip_addrs) as its gateway, the interface will accept
+ /// packets addressed to that prefix.
+ ///
+ /// # IPv6
+ ///
+ /// This option is not available or required for IPv6 as packets sent to
+ /// the interface are not filtered by IPv6 address.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn set_any_ip(&mut self, any_ip: bool) {
+ self.inner.any_ip = any_ip;
+ }
+
+ /// Get whether AnyIP is enabled.
+ ///
+ /// See [`set_any_ip`](Self::set_any_ip) for details on AnyIP
+ #[cfg(feature = "proto-ipv4")]
+ pub fn any_ip(&self) -> bool {
+ self.inner.any_ip
+ }
+
+ /// Get the 6LoWPAN address contexts.
+ #[cfg(feature = "proto-sixlowpan")]
+ pub fn sixlowpan_address_context(
+ &self,
+ ) -> &Vec<SixlowpanAddressContext, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT> {
+ &self.inner.sixlowpan_address_context
+ }
+
+ /// Get a mutable reference to the 6LoWPAN address contexts.
+ #[cfg(feature = "proto-sixlowpan")]
+ pub fn sixlowpan_address_context_mut(
+ &mut self,
+ ) -> &mut Vec<SixlowpanAddressContext, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT> {
+ &mut self.inner.sixlowpan_address_context
+ }
+
+ /// Get the packet reassembly timeout.
+ #[cfg(feature = "_proto-fragmentation")]
+ pub fn reassembly_timeout(&self) -> Duration {
+ self.fragments.reassembly_timeout
+ }
+
+ /// Set the packet reassembly timeout.
+ #[cfg(feature = "_proto-fragmentation")]
+ pub fn set_reassembly_timeout(&mut self, timeout: Duration) {
+ if timeout > Duration::from_secs(60) {
+ net_debug!("RFC 4944 specifies that the reassembly timeout MUST be set to a maximum of 60 seconds");
+ }
+ self.fragments.reassembly_timeout = timeout;
+ }
+
+ /// Transmit packets queued in the given sockets, and receive packets queued
+ /// in the device.
+ ///
+ /// This function returns a boolean value indicating whether any packets were
+ /// processed or emitted, and thus, whether the readiness of any socket might
+ /// have changed.
+ pub fn poll<D>(
+ &mut self,
+ timestamp: Instant,
+ device: &mut D,
+ sockets: &mut SocketSet<'_>,
+ ) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ self.inner.now = timestamp;
+
+ #[cfg(feature = "_proto-fragmentation")]
+ self.fragments.assembler.remove_expired(timestamp);
+
+ match self.inner.caps.medium {
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 =>
+ {
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ if self.sixlowpan_egress(device) {
+ return true;
+ }
+ }
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ip"))]
+ _ =>
+ {
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ if self.ipv4_egress(device) {
+ return true;
+ }
+ }
+ }
+
+ let mut readiness_may_have_changed = false;
+
+ loop {
+ let mut did_something = false;
+ did_something |= self.socket_ingress(device, sockets);
+ did_something |= self.socket_egress(device, sockets);
+
+ #[cfg(feature = "proto-igmp")]
+ {
+ did_something |= self.igmp_egress(device);
+ }
+
+ if did_something {
+ readiness_may_have_changed = true;
+ } else {
+ break;
+ }
+ }
+
+ readiness_may_have_changed
+ }
+
+ /// Return a _soft deadline_ for calling [poll] the next time.
+ /// The [Instant] returned is the time at which you should call [poll] next.
+ /// It is harmless (but wastes energy) to call it before the [Instant], and
+ /// potentially harmful (impacting quality of service) to call it after the
+ /// [Instant]
+ ///
+ /// [poll]: #method.poll
+ /// [Instant]: struct.Instant.html
+ pub fn poll_at(&mut self, timestamp: Instant, sockets: &SocketSet<'_>) -> Option<Instant> {
+ self.inner.now = timestamp;
+
+ #[cfg(feature = "_proto-fragmentation")]
+ if !self.fragmenter.is_empty() {
+ return Some(Instant::from_millis(0));
+ }
+
+ let inner = &mut self.inner;
+
+ sockets
+ .items()
+ .filter_map(move |item| {
+ let socket_poll_at = item.socket.poll_at(inner);
+ match item
+ .meta
+ .poll_at(socket_poll_at, |ip_addr| inner.has_neighbor(&ip_addr))
+ {
+ PollAt::Ingress => None,
+ PollAt::Time(instant) => Some(instant),
+ PollAt::Now => Some(Instant::from_millis(0)),
+ }
+ })
+ .min()
+ }
+
+ /// Return an _advisory wait time_ for calling [poll] the next time.
+ /// The [Duration] returned is the time left to wait before calling [poll] next.
+ /// It is harmless (but wastes energy) to call it before the [Duration] has passed,
+ /// and potentially harmful (impacting quality of service) to call it after the
+ /// [Duration] has passed.
+ ///
+ /// [poll]: #method.poll
+ /// [Duration]: struct.Duration.html
+ pub fn poll_delay(&mut self, timestamp: Instant, sockets: &SocketSet<'_>) -> Option<Duration> {
+ match self.poll_at(timestamp, sockets) {
+ Some(poll_at) if timestamp < poll_at => Some(poll_at - timestamp),
+ Some(_) => Some(Duration::from_millis(0)),
+ _ => None,
+ }
+ }
+
+ fn socket_ingress<D>(&mut self, device: &mut D, sockets: &mut SocketSet<'_>) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ let mut processed_any = false;
+
+ while let Some((rx_token, tx_token)) = device.receive(self.inner.now) {
+ let rx_meta = rx_token.meta();
+ rx_token.consume(|frame| {
+ if frame.is_empty() {
+ return;
+ }
+
+ match self.inner.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ if let Some(packet) = self.inner.process_ethernet(
+ sockets,
+ rx_meta,
+ frame,
+ &mut self.fragments,
+ ) {
+ if let Err(err) =
+ self.inner.dispatch(tx_token, packet, &mut self.fragmenter)
+ {
+ net_debug!("Failed to send response: {:?}", err);
+ }
+ }
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => {
+ if let Some(packet) =
+ self.inner
+ .process_ip(sockets, rx_meta, frame, &mut self.fragments)
+ {
+ if let Err(err) = self.inner.dispatch_ip(
+ tx_token,
+ PacketMeta::default(),
+ packet,
+ &mut self.fragmenter,
+ ) {
+ net_debug!("Failed to send response: {:?}", err);
+ }
+ }
+ }
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => {
+ if let Some(packet) = self.inner.process_ieee802154(
+ sockets,
+ rx_meta,
+ frame,
+ &mut self.fragments,
+ ) {
+ if let Err(err) = self.inner.dispatch_ip(
+ tx_token,
+ PacketMeta::default(),
+ packet,
+ &mut self.fragmenter,
+ ) {
+ net_debug!("Failed to send response: {:?}", err);
+ }
+ }
+ }
+ }
+ processed_any = true;
+ });
+ }
+
+ processed_any
+ }
+
+ fn socket_egress<D>(&mut self, device: &mut D, sockets: &mut SocketSet<'_>) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ let _caps = device.capabilities();
+
+ enum EgressError {
+ Exhausted,
+ Dispatch(DispatchError),
+ }
+
+ let mut emitted_any = false;
+ for item in sockets.items_mut() {
+ if !item
+ .meta
+ .egress_permitted(self.inner.now, |ip_addr| self.inner.has_neighbor(&ip_addr))
+ {
+ continue;
+ }
+
+ let mut neighbor_addr = None;
+ let mut respond = |inner: &mut InterfaceInner, meta: PacketMeta, response: Packet| {
+ neighbor_addr = Some(response.ip_repr().dst_addr());
+ let t = device.transmit(inner.now).ok_or_else(|| {
+ net_debug!("failed to transmit IP: device exhausted");
+ EgressError::Exhausted
+ })?;
+
+ inner
+ .dispatch_ip(t, meta, response, &mut self.fragmenter)
+ .map_err(EgressError::Dispatch)?;
+
+ emitted_any = true;
+
+ Ok(())
+ };
+
+ let result = match &mut item.socket {
+ #[cfg(feature = "socket-raw")]
+ Socket::Raw(socket) => socket.dispatch(&mut self.inner, |inner, (ip, raw)| {
+ respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new(ip, IpPayload::Raw(raw)),
+ )
+ }),
+ #[cfg(feature = "socket-icmp")]
+ Socket::Icmp(socket) => {
+ socket.dispatch(&mut self.inner, |inner, response| match response {
+ #[cfg(feature = "proto-ipv4")]
+ (IpRepr::Ipv4(ipv4_repr), IcmpRepr::Ipv4(icmpv4_repr)) => respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new_ipv4(ipv4_repr, IpPayload::Icmpv4(icmpv4_repr)),
+ ),
+ #[cfg(feature = "proto-ipv6")]
+ (IpRepr::Ipv6(ipv6_repr), IcmpRepr::Ipv6(icmpv6_repr)) => respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(icmpv6_repr)),
+ ),
+ #[allow(unreachable_patterns)]
+ _ => unreachable!(),
+ })
+ }
+ #[cfg(feature = "socket-udp")]
+ Socket::Udp(socket) => {
+ socket.dispatch(&mut self.inner, |inner, meta, (ip, udp, payload)| {
+ respond(inner, meta, Packet::new(ip, IpPayload::Udp(udp, payload)))
+ })
+ }
+ #[cfg(feature = "socket-tcp")]
+ Socket::Tcp(socket) => socket.dispatch(&mut self.inner, |inner, (ip, tcp)| {
+ respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new(ip, IpPayload::Tcp(tcp)),
+ )
+ }),
+ #[cfg(feature = "socket-dhcpv4")]
+ Socket::Dhcpv4(socket) => {
+ socket.dispatch(&mut self.inner, |inner, (ip, udp, dhcp)| {
+ respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new_ipv4(ip, IpPayload::Dhcpv4(udp, dhcp)),
+ )
+ })
+ }
+ #[cfg(feature = "socket-dns")]
+ Socket::Dns(socket) => socket.dispatch(&mut self.inner, |inner, (ip, udp, dns)| {
+ respond(
+ inner,
+ PacketMeta::default(),
+ Packet::new(ip, IpPayload::Udp(udp, dns)),
+ )
+ }),
+ };
+
+ match result {
+ Err(EgressError::Exhausted) => break, // Device buffer full.
+ Err(EgressError::Dispatch(_)) => {
+ // `NeighborCache` already takes care of rate limiting the neighbor discovery
+ // requests from the socket. However, without an additional rate limiting
+ // mechanism, we would spin on every socket that has yet to discover its
+ // neighbor.
+ item.meta.neighbor_missing(
+ self.inner.now,
+ neighbor_addr.expect("non-IP response packet"),
+ );
+ }
+ Ok(()) => {}
+ }
+ }
+ emitted_any
+ }
+
+ /// Process fragments that still need to be sent for IPv4 packets.
+ ///
+ /// This function returns a boolean value indicating whether any packets were
+ /// processed or emitted, and thus, whether the readiness of any socket might
+ /// have changed.
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ fn ipv4_egress<D>(&mut self, device: &mut D) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ // Reset the buffer when we transmitted everything.
+ if self.fragmenter.finished() {
+ self.fragmenter.reset();
+ }
+
+ if self.fragmenter.is_empty() {
+ return false;
+ }
+
+ let pkt = &self.fragmenter;
+ if pkt.packet_len > pkt.sent_bytes {
+ if let Some(tx_token) = device.transmit(self.inner.now) {
+ self.inner
+ .dispatch_ipv4_frag(tx_token, &mut self.fragmenter);
+ return true;
+ }
+ }
+ false
+ }
+
+ /// Process fragments that still need to be sent for 6LoWPAN packets.
+ ///
+ /// This function returns a boolean value indicating whether any packets were
+ /// processed or emitted, and thus, whether the readiness of any socket might
+ /// have changed.
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ fn sixlowpan_egress<D>(&mut self, device: &mut D) -> bool
+ where
+ D: Device + ?Sized,
+ {
+ // Reset the buffer when we transmitted everything.
+ if self.fragmenter.finished() {
+ self.fragmenter.reset();
+ }
+
+ if self.fragmenter.is_empty() {
+ return false;
+ }
+
+ let pkt = &self.fragmenter;
+ if pkt.packet_len > pkt.sent_bytes {
+ if let Some(tx_token) = device.transmit(self.inner.now) {
+ self.inner
+ .dispatch_ieee802154_frag(tx_token, &mut self.fragmenter);
+ return true;
+ }
+ }
+ false
+ }
+}
+
+impl InterfaceInner {
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn now(&self) -> Instant {
+ self.now
+ }
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn hardware_addr(&self) -> HardwareAddress {
+ self.hardware_addr
+ }
+
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn checksum_caps(&self) -> ChecksumCapabilities {
+ self.caps.checksum.clone()
+ }
+
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn ip_mtu(&self) -> usize {
+ self.caps.ip_mtu()
+ }
+
+ #[allow(unused)] // unused depending on which sockets are enabled, and in tests
+ pub(crate) fn rand(&mut self) -> &mut Rand {
+ &mut self.rand
+ }
+
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn get_source_address(&self, dst_addr: &IpAddress) -> Option<IpAddress> {
+ match dst_addr {
+ #[cfg(feature = "proto-ipv4")]
+ IpAddress::Ipv4(addr) => self.get_source_address_ipv4(addr).map(|a| a.into()),
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(addr) => self.get_source_address_ipv6(addr).map(|a| a.into()),
+ }
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ #[allow(unused)]
+ pub(crate) fn get_source_address_ipv4(&self, _dst_addr: &Ipv4Address) -> Option<Ipv4Address> {
+ for cidr in self.ip_addrs.iter() {
+ #[allow(irrefutable_let_patterns)] // if only ipv4 is enabled
+ if let IpCidr::Ipv4(cidr) = cidr {
+ return Some(cidr.address());
+ }
+ }
+ None
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ #[allow(unused)]
+ pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Option<Ipv6Address> {
+ // RFC 6724 describes how to select the correct source address depending on the destination
+ // address.
+
+ // See RFC 6724 Section 4: Candidate source address
+ fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
+ // For all multicast and link-local destination addresses, the candidate address MUST
+ // only be an address from the same link.
+ if dst_addr.is_link_local() && !src_addr.is_link_local() {
+ return false;
+ }
+
+ if dst_addr.is_multicast()
+ && matches!(dst_addr.scope(), Ipv6AddressScope::LinkLocal)
+ && src_addr.is_multicast()
+ && !matches!(src_addr.scope(), Ipv6AddressScope::LinkLocal)
+ {
+ return false;
+ }
+
+ // Loopback addresses and multicast address can not be in the candidate source address
+ // list. Except when the destination multicast address has a link-local scope, then the
+ // source address can also be link-local multicast.
+ if src_addr.is_loopback() || src_addr.is_multicast() {
+ return false;
+ }
+
+ true
+ }
+
+ // See RFC 6724 Section 2.2: Common Prefix Length
+ fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize {
+ let addr = dst_addr.address();
+ let mut bits = 0;
+ for (l, r) in addr.as_bytes().iter().zip(src_addr.as_bytes().iter()) {
+ if l == r {
+ bits += 8;
+ } else {
+ bits += (l ^ r).leading_zeros();
+ break;
+ }
+ }
+
+ bits = bits.min(dst_addr.prefix_len() as u32);
+
+ bits as usize
+ }
+
+ // Get the first address that is a candidate address.
+ let mut candidate = self
+ .ip_addrs
+ .iter()
+ .filter_map(|a| match a {
+ #[cfg(feature = "proto-ipv4")]
+ IpCidr::Ipv4(_) => None,
+ #[cfg(feature = "proto-ipv6")]
+ IpCidr::Ipv6(a) => Some(a),
+ })
+ .find(|a| is_candidate_source_address(dst_addr, &a.address()))
+ .unwrap();
+
+ for addr in self.ip_addrs.iter().filter_map(|a| match a {
+ #[cfg(feature = "proto-ipv4")]
+ IpCidr::Ipv4(_) => None,
+ #[cfg(feature = "proto-ipv6")]
+ IpCidr::Ipv6(a) => Some(a),
+ }) {
+ if !is_candidate_source_address(dst_addr, &addr.address()) {
+ continue;
+ }
+
+ // Rule 1: prefer the address that is the same as the output destination address.
+ if candidate.address() != *dst_addr && addr.address() == *dst_addr {
+ candidate = addr;
+ }
+
+ // Rule 2: prefer appropriate scope.
+ if (candidate.address().scope() as u8) < (addr.address().scope() as u8) {
+ if (candidate.address().scope() as u8) < (dst_addr.scope() as u8) {
+ candidate = addr;
+ }
+ } else if (addr.address().scope() as u8) > (dst_addr.scope() as u8) {
+ candidate = addr;
+ }
+
+ // Rule 3: avoid deprecated addresses (TODO)
+ // Rule 4: prefer home addresses (TODO)
+ // Rule 5: prefer outgoing interfaces (TODO)
+ // Rule 5.5: prefer addresses in a prefix advertises by the next-hop (TODO).
+ // Rule 6: prefer matching label (TODO)
+ // Rule 7: prefer temporary addresses (TODO)
+ // Rule 8: use longest matching prefix
+ if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) {
+ candidate = addr;
+ }
+ }
+
+ Some(candidate.address())
+ }
+
+ #[cfg(test)]
+ #[allow(unused)] // unused depending on which sockets are enabled
+ pub(crate) fn set_now(&mut self, now: Instant) {
+ self.now = now
+ }
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ fn check_hardware_addr(addr: &HardwareAddress) {
+ if !addr.is_unicast() {
+ panic!("Hardware address {addr} is not unicast")
+ }
+ }
+
+ fn check_ip_addrs(addrs: &[IpCidr]) {
+ for cidr in addrs {
+ if !cidr.address().is_unicast() && !cidr.address().is_unspecified() {
+ panic!("IP address {} is not unicast", cidr.address())
+ }
+ }
+ }
+
+ #[cfg(feature = "medium-ieee802154")]
+ fn get_sequence_number(&mut self) -> u8 {
+ let no = self.sequence_no;
+ self.sequence_no = self.sequence_no.wrapping_add(1);
+ no
+ }
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ fn get_ipv4_ident(&mut self) -> u16 {
+ let ipv4_id = self.ipv4_id;
+ self.ipv4_id = self.ipv4_id.wrapping_add(1);
+ ipv4_id
+ }
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ fn get_sixlowpan_fragment_tag(&mut self) -> u16 {
+ let tag = self.tag;
+ self.tag = self.tag.wrapping_add(1);
+ tag
+ }
+
+ /// Determine if the given `Ipv6Address` is the solicited node
+ /// multicast address for a IPv6 addresses assigned to the interface.
+ /// See [RFC 4291 § 2.7.1] for more details.
+ ///
+ /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
+ #[cfg(feature = "proto-ipv6")]
+ pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
+ self.ip_addrs.iter().any(|cidr| {
+ match *cidr {
+ IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK => {
+ // Take the lower order 24 bits of the IPv6 address and
+ // append those bits to FF02:0:0:0:0:1:FF00::/104.
+ addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
+ }
+ _ => false,
+ }
+ })
+ }
+
+ /// Check whether the interface has the given IP address assigned.
+ fn has_ip_addr<T: Into<IpAddress>>(&self, addr: T) -> bool {
+ let addr = addr.into();
+ self.ip_addrs.iter().any(|probe| probe.address() == addr)
+ }
+
+ /// Get the first IPv4 address of the interface.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn ipv4_addr(&self) -> Option<Ipv4Address> {
+ self.ip_addrs.iter().find_map(|addr| match *addr {
+ IpCidr::Ipv4(cidr) => Some(cidr.address()),
+ #[allow(unreachable_patterns)]
+ _ => None,
+ })
+ }
+
+ /// Get the first IPv6 address if present.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
+ self.ip_addrs.iter().find_map(|addr| match *addr {
+ IpCidr::Ipv6(cidr) => Some(cidr.address()),
+ #[allow(unreachable_patterns)]
+ _ => None,
+ })
+ }
+
+ /// Check whether the interface listens to given destination multicast IP address.
+ ///
+ /// If built without feature `proto-igmp` this function will
+ /// always return `false` when using IPv4.
+ fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
+ match addr.into() {
+ #[cfg(feature = "proto-igmp")]
+ IpAddress::Ipv4(key) => {
+ key == Ipv4Address::MULTICAST_ALL_SYSTEMS
+ || self.ipv4_multicast_groups.get(&key).is_some()
+ }
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(Ipv6Address::LINK_LOCAL_ALL_NODES) => true,
+ #[cfg(feature = "proto-rpl")]
+ IpAddress::Ipv6(Ipv6Address::LINK_LOCAL_ALL_RPL_NODES) => true,
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(addr) => self.has_solicited_node(addr),
+ #[allow(unreachable_patterns)]
+ _ => false,
+ }
+ }
+
+ #[cfg(feature = "medium-ip")]
+ fn process_ip<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ip_payload: &'frame [u8],
+ frag: &'frame mut FragmentsBuffer,
+ ) -> Option<Packet<'frame>> {
+ match IpVersion::of_packet(ip_payload) {
+ #[cfg(feature = "proto-ipv4")]
+ Ok(IpVersion::Ipv4) => {
+ let ipv4_packet = check!(Ipv4Packet::new_checked(ip_payload));
+
+ self.process_ipv4(sockets, meta, &ipv4_packet, frag)
+ }
+ #[cfg(feature = "proto-ipv6")]
+ Ok(IpVersion::Ipv6) => {
+ let ipv6_packet = check!(Ipv6Packet::new_checked(ip_payload));
+ self.process_ipv6(sockets, meta, &ipv6_packet)
+ }
+ // Drop all other traffic.
+ _ => None,
+ }
+ }
+
+ #[cfg(feature = "socket-raw")]
+ fn raw_socket_filter(
+ &mut self,
+ sockets: &mut SocketSet,
+ ip_repr: &IpRepr,
+ ip_payload: &[u8],
+ ) -> bool {
+ let mut handled_by_raw_socket = false;
+
+ // Pass every IP packet to all raw sockets we have registered.
+ for raw_socket in sockets
+ .items_mut()
+ .filter_map(|i| raw::Socket::downcast_mut(&mut i.socket))
+ {
+ if raw_socket.accepts(ip_repr) {
+ raw_socket.process(self, ip_repr, ip_payload);
+ handled_by_raw_socket = true;
+ }
+ }
+ handled_by_raw_socket
+ }
+
+ /// Checks if an address is broadcast, taking into account ipv4 subnet-local
+ /// broadcast addresses.
+ pub(crate) fn is_broadcast(&self, address: &IpAddress) -> bool {
+ match address {
+ #[cfg(feature = "proto-ipv4")]
+ IpAddress::Ipv4(address) => self.is_broadcast_v4(*address),
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(_) => false,
+ }
+ }
+
+ /// Checks if an address is broadcast, taking into account ipv4 subnet-local
+ /// broadcast addresses.
+ #[cfg(feature = "proto-ipv4")]
+ pub(crate) fn is_broadcast_v4(&self, address: Ipv4Address) -> bool {
+ if address.is_broadcast() {
+ return true;
+ }
+
+ self.ip_addrs
+ .iter()
+ .filter_map(|own_cidr| match own_cidr {
+ IpCidr::Ipv4(own_ip) => Some(own_ip.broadcast()?),
+ #[cfg(feature = "proto-ipv6")]
+ IpCidr::Ipv6(_) => None,
+ })
+ .any(|broadcast_address| address == broadcast_address)
+ }
+
+ /// Checks if an ipv4 address is unicast, taking into account subnet broadcast addresses
+ #[cfg(feature = "proto-ipv4")]
+ fn is_unicast_v4(&self, address: Ipv4Address) -> bool {
+ address.is_unicast() && !self.is_broadcast_v4(address)
+ }
+
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ #[allow(clippy::too_many_arguments)]
+ fn process_udp<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ip_repr: IpRepr,
+ udp_repr: UdpRepr,
+ handled_by_raw_socket: bool,
+ udp_payload: &'frame [u8],
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ #[cfg(feature = "socket-udp")]
+ for udp_socket in sockets
+ .items_mut()
+ .filter_map(|i| udp::Socket::downcast_mut(&mut i.socket))
+ {
+ if udp_socket.accepts(self, &ip_repr, &udp_repr) {
+ udp_socket.process(self, meta, &ip_repr, &udp_repr, udp_payload);
+ return None;
+ }
+ }
+
+ #[cfg(feature = "socket-dns")]
+ for dns_socket in sockets
+ .items_mut()
+ .filter_map(|i| dns::Socket::downcast_mut(&mut i.socket))
+ {
+ if dns_socket.accepts(&ip_repr, &udp_repr) {
+ dns_socket.process(self, &ip_repr, &udp_repr, udp_payload);
+ return None;
+ }
+ }
+
+ // The packet wasn't handled by a socket, send an ICMP port unreachable packet.
+ match ip_repr {
+ #[cfg(feature = "proto-ipv4")]
+ IpRepr::Ipv4(_) if handled_by_raw_socket => None,
+ #[cfg(feature = "proto-ipv6")]
+ IpRepr::Ipv6(_) if handled_by_raw_socket => None,
+ #[cfg(feature = "proto-ipv4")]
+ IpRepr::Ipv4(ipv4_repr) => {
+ let payload_len =
+ icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len());
+ let icmpv4_reply_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: ipv4_repr,
+ data: &ip_payload[0..payload_len],
+ };
+ self.icmpv4_reply(ipv4_repr, icmpv4_reply_repr)
+ }
+ #[cfg(feature = "proto-ipv6")]
+ IpRepr::Ipv6(ipv6_repr) => {
+ let payload_len =
+ icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
+ let icmpv6_reply_repr = Icmpv6Repr::DstUnreachable {
+ reason: Icmpv6DstUnreachable::PortUnreachable,
+ header: ipv6_repr,
+ data: &ip_payload[0..payload_len],
+ };
+ self.icmpv6_reply(ipv6_repr, icmpv6_reply_repr)
+ }
+ }
+ }
+
+ #[cfg(feature = "socket-tcp")]
+ pub(crate) fn process_tcp<'frame>(
+ &mut self,
+ sockets: &mut SocketSet,
+ ip_repr: IpRepr,
+ ip_payload: &'frame [u8],
+ ) -> Option<Packet<'frame>> {
+ let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr());
+ let tcp_packet = check!(TcpPacket::new_checked(ip_payload));
+ let tcp_repr = check!(TcpRepr::parse(
+ &tcp_packet,
+ &src_addr,
+ &dst_addr,
+ &self.caps.checksum
+ ));
+
+ for tcp_socket in sockets
+ .items_mut()
+ .filter_map(|i| tcp::Socket::downcast_mut(&mut i.socket))
+ {
+ if tcp_socket.accepts(self, &ip_repr, &tcp_repr) {
+ return tcp_socket
+ .process(self, &ip_repr, &tcp_repr)
+ .map(|(ip, tcp)| Packet::new(ip, IpPayload::Tcp(tcp)));
+ }
+ }
+
+ if tcp_repr.control == TcpControl::Rst
+ || ip_repr.dst_addr().is_unspecified()
+ || ip_repr.src_addr().is_unspecified()
+ {
+ // Never reply to a TCP RST packet with another TCP RST packet. We also never want to
+ // send a TCP RST packet with unspecified addresses.
+ None
+ } else {
+ // The packet wasn't handled by a socket, send a TCP RST packet.
+ let (ip, tcp) = tcp::Socket::rst_reply(&ip_repr, &tcp_repr);
+ Some(Packet::new(ip, IpPayload::Tcp(tcp)))
+ }
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ fn dispatch<Tx>(
+ &mut self,
+ tx_token: Tx,
+ packet: EthernetPacket,
+ frag: &mut Fragmenter,
+ ) -> Result<(), DispatchError>
+ where
+ Tx: TxToken,
+ {
+ match packet {
+ #[cfg(feature = "proto-ipv4")]
+ EthernetPacket::Arp(arp_repr) => {
+ let dst_hardware_addr = match arp_repr {
+ ArpRepr::EthernetIpv4 {
+ target_hardware_addr,
+ ..
+ } => target_hardware_addr,
+ };
+
+ self.dispatch_ethernet(tx_token, arp_repr.buffer_len(), |mut frame| {
+ frame.set_dst_addr(dst_hardware_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ arp_repr.emit(&mut packet);
+ })
+ }
+ EthernetPacket::Ip(packet) => {
+ self.dispatch_ip(tx_token, PacketMeta::default(), packet, frag)
+ }
+ }
+ }
+
+ fn in_same_network(&self, addr: &IpAddress) -> bool {
+ self.ip_addrs.iter().any(|cidr| cidr.contains_addr(addr))
+ }
+
+ fn route(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
+ // Send directly.
+ // note: no need to use `self.is_broadcast()` to check for subnet-local broadcast addrs
+ // here because `in_same_network` will already return true.
+ if self.in_same_network(addr) || addr.is_broadcast() {
+ return Some(*addr);
+ }
+
+ // Route via a router.
+ self.routes.lookup(addr, timestamp)
+ }
+
+ fn has_neighbor(&self, addr: &IpAddress) -> bool {
+ match self.route(addr, self.now) {
+ Some(_routed_addr) => match self.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => self.neighbor_cache.lookup(&_routed_addr, self.now).found(),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => self.neighbor_cache.lookup(&_routed_addr, self.now).found(),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => true,
+ },
+ None => false,
+ }
+ }
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ fn lookup_hardware_addr<Tx>(
+ &mut self,
+ tx_token: Tx,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ fragmenter: &mut Fragmenter,
+ ) -> Result<(HardwareAddress, Tx), DispatchError>
+ where
+ Tx: TxToken,
+ {
+ if self.is_broadcast(dst_addr) {
+ let hardware_addr = match self.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress::BROADCAST),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::BROADCAST),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => unreachable!(),
+ };
+
+ return Ok((hardware_addr, tx_token));
+ }
+
+ if dst_addr.is_multicast() {
+ let b = dst_addr.as_bytes();
+ let hardware_addr = match *dst_addr {
+ #[cfg(feature = "proto-ipv4")]
+ IpAddress::Ipv4(_addr) => match self.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
+ 0x01,
+ 0x00,
+ 0x5e,
+ b[1] & 0x7F,
+ b[2],
+ b[3],
+ ])),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => unreachable!(),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => unreachable!(),
+ },
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(_addr) => match self.caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
+ 0x33, 0x33, b[12], b[13], b[14], b[15],
+ ])),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => {
+ // Not sure if this is correct
+ HardwareAddress::Ieee802154(Ieee802154Address::BROADCAST)
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => unreachable!(),
+ },
+ };
+
+ return Ok((hardware_addr, tx_token));
+ }
+
+ let dst_addr = self
+ .route(dst_addr, self.now)
+ .ok_or(DispatchError::NoRoute)?;
+
+ match self.neighbor_cache.lookup(&dst_addr, self.now) {
+ NeighborAnswer::Found(hardware_addr) => return Ok((hardware_addr, tx_token)),
+ NeighborAnswer::RateLimited => return Err(DispatchError::NeighborPending),
+ _ => (), // XXX
+ }
+
+ match (src_addr, dst_addr) {
+ #[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))]
+ (&IpAddress::Ipv4(src_addr), IpAddress::Ipv4(dst_addr))
+ if matches!(self.caps.medium, Medium::Ethernet) =>
+ {
+ net_debug!(
+ "address {} not in neighbor cache, sending ARP request",
+ dst_addr
+ );
+ let src_hardware_addr = self.hardware_addr.ethernet_or_panic();
+
+ let arp_repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: src_hardware_addr,
+ source_protocol_addr: src_addr,
+ target_hardware_addr: EthernetAddress::BROADCAST,
+ target_protocol_addr: dst_addr,
+ };
+
+ if let Err(e) =
+ self.dispatch_ethernet(tx_token, arp_repr.buffer_len(), |mut frame| {
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_ethertype(EthernetProtocol::Arp);
+
+ arp_repr.emit(&mut ArpPacket::new_unchecked(frame.payload_mut()))
+ })
+ {
+ net_debug!("Failed to dispatch ARP request: {:?}", e);
+ return Err(DispatchError::NeighborPending);
+ }
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ (&IpAddress::Ipv6(src_addr), IpAddress::Ipv6(dst_addr)) => {
+ net_debug!(
+ "address {} not in neighbor cache, sending Neighbor Solicitation",
+ dst_addr
+ );
+
+ let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
+ target_addr: dst_addr,
+ lladdr: Some(self.hardware_addr.into()),
+ });
+
+ let packet = Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr,
+ dst_addr: dst_addr.solicited_node(),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: solicit.buffer_len(),
+ hop_limit: 0xff,
+ },
+ IpPayload::Icmpv6(solicit),
+ );
+
+ if let Err(e) =
+ self.dispatch_ip(tx_token, PacketMeta::default(), packet, fragmenter)
+ {
+ net_debug!("Failed to dispatch NDISC solicit: {:?}", e);
+ return Err(DispatchError::NeighborPending);
+ }
+ }
+
+ #[allow(unreachable_patterns)]
+ _ => (),
+ }
+
+ // The request got dispatched, limit the rate on the cache.
+ self.neighbor_cache.limit_rate(self.now);
+ Err(DispatchError::NeighborPending)
+ }
+
+ fn flush_cache(&mut self) {
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ self.neighbor_cache.flush()
+ }
+
+ fn dispatch_ip<Tx: TxToken>(
+ &mut self,
+ // NOTE(unused_mut): tx_token isn't always mutated, depending on
+ // the feature set that is used.
+ #[allow(unused_mut)] mut tx_token: Tx,
+ meta: PacketMeta,
+ packet: Packet,
+ frag: &mut Fragmenter,
+ ) -> Result<(), DispatchError> {
+ let mut ip_repr = packet.ip_repr();
+ assert!(!ip_repr.dst_addr().is_unspecified());
+
+ // Dispatch IEEE802.15.4:
+
+ #[cfg(feature = "medium-ieee802154")]
+ if matches!(self.caps.medium, Medium::Ieee802154) {
+ let (addr, tx_token) = self.lookup_hardware_addr(
+ tx_token,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ frag,
+ )?;
+ let addr = addr.ieee802154_or_panic();
+
+ self.dispatch_ieee802154(addr, tx_token, meta, packet, frag);
+ return Ok(());
+ }
+
+ // Dispatch IP/Ethernet:
+
+ let caps = self.caps.clone();
+
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ let ipv4_id = self.get_ipv4_ident();
+
+ // First we calculate the total length that we will have to emit.
+ let mut total_len = ip_repr.buffer_len();
+
+ // Add the size of the Ethernet header if the medium is Ethernet.
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(self.caps.medium, Medium::Ethernet) {
+ total_len = EthernetFrame::<&[u8]>::buffer_len(total_len);
+ }
+
+ // If the medium is Ethernet, then we need to retrieve the destination hardware address.
+ #[cfg(feature = "medium-ethernet")]
+ let (dst_hardware_addr, mut tx_token) = match self.caps.medium {
+ Medium::Ethernet => {
+ match self.lookup_hardware_addr(
+ tx_token,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ frag,
+ )? {
+ (HardwareAddress::Ethernet(addr), tx_token) => (addr, tx_token),
+ (_, _) => unreachable!(),
+ }
+ }
+ _ => (EthernetAddress([0; 6]), tx_token),
+ };
+
+ // Emit function for the Ethernet header.
+ #[cfg(feature = "medium-ethernet")]
+ let emit_ethernet = |repr: &IpRepr, tx_buffer: &mut [u8]| {
+ let mut frame = EthernetFrame::new_unchecked(tx_buffer);
+
+ let src_addr = self.hardware_addr.ethernet_or_panic();
+ frame.set_src_addr(src_addr);
+ frame.set_dst_addr(dst_hardware_addr);
+
+ match repr.version() {
+ #[cfg(feature = "proto-ipv4")]
+ IpVersion::Ipv4 => frame.set_ethertype(EthernetProtocol::Ipv4),
+ #[cfg(feature = "proto-ipv6")]
+ IpVersion::Ipv6 => frame.set_ethertype(EthernetProtocol::Ipv6),
+ }
+
+ Ok(())
+ };
+
+ // Emit function for the IP header and payload.
+ let emit_ip = |repr: &IpRepr, mut tx_buffer: &mut [u8]| {
+ repr.emit(&mut tx_buffer, &self.caps.checksum);
+
+ let payload = &mut tx_buffer[repr.header_len()..];
+ packet.emit_payload(repr, payload, &caps)
+ };
+
+ let total_ip_len = ip_repr.buffer_len();
+
+ match &mut ip_repr {
+ #[cfg(feature = "proto-ipv4")]
+ IpRepr::Ipv4(repr) => {
+ // If we have an IPv4 packet, then we need to check if we need to fragment it.
+ if total_ip_len > self.caps.max_transmission_unit {
+ #[cfg(feature = "proto-ipv4-fragmentation")]
+ {
+ net_debug!("start fragmentation");
+
+ // Calculate how much we will send now (including the Ethernet header).
+ let tx_len = self.caps.max_transmission_unit;
+
+ let ip_header_len = repr.buffer_len();
+ let first_frag_ip_len = self.caps.ip_mtu();
+
+ if frag.buffer.len() < total_ip_len {
+ net_debug!(
+ "Fragmentation buffer is too small, at least {} needed. Dropping",
+ total_ip_len
+ );
+ return Ok(());
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ {
+ frag.ipv4.dst_hardware_addr = dst_hardware_addr;
+ }
+
+ // Save the total packet len (without the Ethernet header, but with the first
+ // IP header).
+ frag.packet_len = total_ip_len;
+
+ // Save the IP header for other fragments.
+ frag.ipv4.repr = *repr;
+
+ // Save how much bytes we will send now.
+ frag.sent_bytes = first_frag_ip_len;
+
+ // Modify the IP header
+ repr.payload_len = first_frag_ip_len - repr.buffer_len();
+
+ // Emit the IP header to the buffer.
+ emit_ip(&ip_repr, &mut frag.buffer);
+
+ let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut frag.buffer[..]);
+ frag.ipv4.ident = ipv4_id;
+ ipv4_packet.set_ident(ipv4_id);
+ ipv4_packet.set_more_frags(true);
+ ipv4_packet.set_dont_frag(false);
+ ipv4_packet.set_frag_offset(0);
+
+ if caps.checksum.ipv4.tx() {
+ ipv4_packet.fill_checksum();
+ }
+
+ // Transmit the first packet.
+ tx_token.consume(tx_len, |mut tx_buffer| {
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(self.caps.medium, Medium::Ethernet) {
+ emit_ethernet(&ip_repr, tx_buffer)?;
+ tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
+ }
+
+ // Change the offset for the next packet.
+ frag.ipv4.frag_offset = (first_frag_ip_len - ip_header_len) as u16;
+
+ // Copy the IP header and the payload.
+ tx_buffer[..first_frag_ip_len]
+ .copy_from_slice(&frag.buffer[..first_frag_ip_len]);
+
+ Ok(())
+ })
+ }
+
+ #[cfg(not(feature = "proto-ipv4-fragmentation"))]
+ {
+ net_debug!("Enable the `proto-ipv4-fragmentation` feature for fragmentation support.");
+ Ok(())
+ }
+ } else {
+ tx_token.set_meta(meta);
+
+ // No fragmentation is required.
+ tx_token.consume(total_len, |mut tx_buffer| {
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(self.caps.medium, Medium::Ethernet) {
+ emit_ethernet(&ip_repr, tx_buffer)?;
+ tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
+ }
+
+ emit_ip(&ip_repr, tx_buffer);
+ Ok(())
+ })
+ }
+ }
+ // We don't support IPv6 fragmentation yet.
+ #[cfg(feature = "proto-ipv6")]
+ IpRepr::Ipv6(_) => tx_token.consume(total_len, |mut tx_buffer| {
+ #[cfg(feature = "medium-ethernet")]
+ if matches!(self.caps.medium, Medium::Ethernet) {
+ emit_ethernet(&ip_repr, tx_buffer)?;
+ tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
+ }
+
+ emit_ip(&ip_repr, tx_buffer);
+ Ok(())
+ }),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+enum DispatchError {
+ /// No route to dispatch this packet. Retrying won't help unless
+ /// configuration is changed.
+ NoRoute,
+ /// We do have a route to dispatch this packet, but we haven't discovered
+ /// the neighbor for it yet. Discovery has been initiated, dispatch
+ /// should be retried later.
+ NeighborPending,
+}
diff --git a/src/iface/interface/sixlowpan.rs b/src/iface/interface/sixlowpan.rs
new file mode 100644
index 0000000..de2c3e9
--- /dev/null
+++ b/src/iface/interface/sixlowpan.rs
@@ -0,0 +1,922 @@
+use super::*;
+
+use crate::phy::ChecksumCapabilities;
+use crate::wire::*;
+
+// Max len of non-fragmented packets after decompression (including ipv6 header and payload)
+// TODO: lower. Should be (6lowpan mtu) - (min 6lowpan header size) + (max ipv6 header size)
+pub(crate) const MAX_DECOMPRESSED_LEN: usize = 1500;
+
+impl InterfaceInner {
+ pub(super) fn process_sixlowpan<'output, 'payload: 'output>(
+ &mut self,
+ sockets: &mut SocketSet,
+ meta: PacketMeta,
+ ieee802154_repr: &Ieee802154Repr,
+ payload: &'payload [u8],
+ f: &'output mut FragmentsBuffer,
+ ) -> Option<Packet<'output>> {
+ let payload = match check!(SixlowpanPacket::dispatch(payload)) {
+ #[cfg(not(feature = "proto-sixlowpan-fragmentation"))]
+ SixlowpanPacket::FragmentHeader => {
+ net_debug!(
+ "Fragmentation is not supported, \
+ use the `proto-sixlowpan-fragmentation` feature to add support."
+ );
+ return None;
+ }
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ SixlowpanPacket::FragmentHeader => {
+ match self.process_sixlowpan_fragment(ieee802154_repr, payload, f) {
+ Some(payload) => payload,
+ None => return None,
+ }
+ }
+ SixlowpanPacket::IphcHeader => {
+ match Self::sixlowpan_to_ipv6(
+ &self.sixlowpan_address_context,
+ ieee802154_repr,
+ payload,
+ None,
+ &mut f.decompress_buf,
+ ) {
+ Ok(len) => &f.decompress_buf[..len],
+ Err(e) => {
+ net_debug!("sixlowpan decompress failed: {:?}", e);
+ return None;
+ }
+ }
+ }
+ };
+
+ self.process_ipv6(sockets, meta, &check!(Ipv6Packet::new_checked(payload)))
+ }
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ fn process_sixlowpan_fragment<'output, 'payload: 'output>(
+ &mut self,
+ ieee802154_repr: &Ieee802154Repr,
+ payload: &'payload [u8],
+ f: &'output mut FragmentsBuffer,
+ ) -> Option<&'output [u8]> {
+ use crate::iface::fragmentation::{AssemblerError, AssemblerFullError};
+
+ // We have a fragment header, which means we cannot process the 6LoWPAN packet,
+ // unless we have a complete one after processing this fragment.
+ let frag = check!(SixlowpanFragPacket::new_checked(payload));
+
+ // The key specifies to which 6LoWPAN fragment it belongs too.
+ // It is based on the link layer addresses, the tag and the size.
+ let key = FragKey::Sixlowpan(frag.get_key(ieee802154_repr));
+
+ // The offset of this fragment in increments of 8 octets.
+ let offset = frag.datagram_offset() as usize * 8;
+
+ // We reserve a spot in the packet assembler set and add the required
+ // information to the packet assembler.
+ // This information is the total size of the packet when it is fully assmbled.
+ // We also pass the header size, since this is needed when other fragments
+ // (other than the first one) are added.
+ let frag_slot = match f.assembler.get(&key, self.now + f.reassembly_timeout) {
+ Ok(frag) => frag,
+ Err(AssemblerFullError) => {
+ net_debug!("No available packet assembler for fragmented packet");
+ return None;
+ }
+ };
+
+ if frag.is_first_fragment() {
+ // The first fragment contains the total size of the IPv6 packet.
+ // However, we received a packet that is compressed following the 6LoWPAN
+ // standard. This means we need to convert the IPv6 packet size to a 6LoWPAN
+ // packet size. The packet size can be different because of first the
+ // compression of the IP header and when UDP is used (because the UDP header
+ // can also be compressed). Other headers are not compressed by 6LoWPAN.
+
+ // First segment tells us the total size.
+ let total_size = frag.datagram_size() as usize;
+ if frag_slot.set_total_size(total_size).is_err() {
+ net_debug!("No available packet assembler for fragmented packet");
+ return None;
+ }
+
+ // Decompress headers+payload into the assembler.
+ if let Err(e) = frag_slot.add_with(0, |buffer| {
+ Self::sixlowpan_to_ipv6(
+ &self.sixlowpan_address_context,
+ ieee802154_repr,
+ frag.payload(),
+ Some(total_size),
+ buffer,
+ )
+ .map_err(|_| AssemblerError)
+ }) {
+ net_debug!("fragmentation error: {:?}", e);
+ return None;
+ }
+ } else {
+ // Add the fragment to the packet assembler.
+ if let Err(e) = frag_slot.add(frag.payload(), offset) {
+ net_debug!("fragmentation error: {:?}", e);
+ return None;
+ }
+ }
+
+ match frag_slot.assemble() {
+ Some(payload) => {
+ net_trace!("6LoWPAN: fragmented packet now complete");
+ Some(payload)
+ }
+ None => None,
+ }
+ }
+
+ fn sixlowpan_to_ipv6(
+ address_context: &[SixlowpanAddressContext],
+ ieee802154_repr: &Ieee802154Repr,
+ iphc_payload: &[u8],
+ total_size: Option<usize>,
+ buffer: &mut [u8],
+ ) -> core::result::Result<usize, crate::wire::Error> {
+ let iphc = SixlowpanIphcPacket::new_checked(iphc_payload)?;
+ let iphc_repr = SixlowpanIphcRepr::parse(
+ &iphc,
+ ieee802154_repr.src_addr,
+ ieee802154_repr.dst_addr,
+ address_context,
+ )?;
+
+ let first_next_header = match iphc_repr.next_header {
+ SixlowpanNextHeader::Compressed => {
+ match SixlowpanNhcPacket::dispatch(iphc.payload())? {
+ SixlowpanNhcPacket::ExtHeader => {
+ SixlowpanExtHeaderPacket::new_checked(iphc.payload())?
+ .extension_header_id()
+ .into()
+ }
+ SixlowpanNhcPacket::UdpHeader => IpProtocol::Udp,
+ }
+ }
+ SixlowpanNextHeader::Uncompressed(proto) => proto,
+ };
+
+ let mut decompressed_size = 40 + iphc.payload().len();
+ let mut next_header = Some(iphc_repr.next_header);
+ let mut data = iphc.payload();
+
+ while let Some(nh) = next_header {
+ match nh {
+ SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(data)? {
+ SixlowpanNhcPacket::ExtHeader => {
+ let ext_hdr = SixlowpanExtHeaderPacket::new_checked(data)?;
+ let ext_repr = SixlowpanExtHeaderRepr::parse(&ext_hdr)?;
+ decompressed_size += 2;
+ decompressed_size -= ext_repr.buffer_len();
+ next_header = Some(ext_repr.next_header);
+
+ if ext_repr.buffer_len() + ext_repr.length as usize > data.len() {
+ return Err(Error);
+ }
+
+ data = &data[ext_repr.buffer_len() + ext_repr.length as usize..];
+ }
+ SixlowpanNhcPacket::UdpHeader => {
+ let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?;
+ let udp_repr = SixlowpanUdpNhcRepr::parse(
+ &udp_packet,
+ &iphc_repr.src_addr,
+ &iphc_repr.dst_addr,
+ &crate::phy::ChecksumCapabilities::ignored(),
+ )?;
+
+ decompressed_size += 8;
+ decompressed_size -= udp_repr.header_len();
+ break;
+ }
+ },
+ SixlowpanNextHeader::Uncompressed(proto) => match proto {
+ IpProtocol::Tcp => break,
+ IpProtocol::Udp => break,
+ IpProtocol::Icmpv6 => break,
+ proto => {
+ net_debug!("unable to decompress Uncompressed({})", proto);
+ return Err(Error);
+ }
+ },
+ }
+ }
+
+ if buffer.len() < decompressed_size {
+ net_debug!("sixlowpan decompress: buffer too short");
+ return Err(crate::wire::Error);
+ }
+ let buffer = &mut buffer[..decompressed_size];
+
+ let total_size = if let Some(size) = total_size {
+ size
+ } else {
+ decompressed_size
+ };
+
+ let mut rest_size = total_size;
+
+ let ipv6_repr = Ipv6Repr {
+ src_addr: iphc_repr.src_addr,
+ dst_addr: iphc_repr.dst_addr,
+ next_header: first_next_header,
+ payload_len: total_size - 40,
+ hop_limit: iphc_repr.hop_limit,
+ };
+ rest_size -= 40;
+
+ // Emit the decompressed IPHC header (decompressed to an IPv6 header).
+ let mut ipv6_packet = Ipv6Packet::new_unchecked(&mut buffer[..ipv6_repr.buffer_len()]);
+ ipv6_repr.emit(&mut ipv6_packet);
+ let mut buffer = &mut buffer[ipv6_repr.buffer_len()..];
+
+ let mut next_header = Some(iphc_repr.next_header);
+ let mut data = iphc.payload();
+
+ while let Some(nh) = next_header {
+ match nh {
+ SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(data)? {
+ SixlowpanNhcPacket::ExtHeader => {
+ let ext_hdr = SixlowpanExtHeaderPacket::new_checked(data)?;
+ let ext_repr = SixlowpanExtHeaderRepr::parse(&ext_hdr)?;
+
+ let nh = match ext_repr.next_header {
+ SixlowpanNextHeader::Compressed => {
+ let d = &data[ext_repr.length as usize + ext_repr.buffer_len()..];
+ match SixlowpanNhcPacket::dispatch(d)? {
+ SixlowpanNhcPacket::ExtHeader => {
+ SixlowpanExtHeaderPacket::new_checked(d)?
+ .extension_header_id()
+ .into()
+ }
+ SixlowpanNhcPacket::UdpHeader => IpProtocol::Udp,
+ }
+ }
+ SixlowpanNextHeader::Uncompressed(proto) => proto,
+ };
+ next_header = Some(ext_repr.next_header);
+
+ let ipv6_ext_hdr = Ipv6ExtHeaderRepr {
+ next_header: nh,
+ length: ext_repr.length / 8,
+ data: &ext_hdr.payload()[..ext_repr.length as usize],
+ };
+
+ ipv6_ext_hdr.emit(&mut Ipv6ExtHeader::new_unchecked(
+ &mut buffer[..ipv6_ext_hdr.header_len()],
+ ));
+ buffer[ipv6_ext_hdr.header_len()..][..ipv6_ext_hdr.data.len()]
+ .copy_from_slice(ipv6_ext_hdr.data);
+
+ buffer = &mut buffer[ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len()..];
+
+ rest_size -= ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len();
+ data = &data[ext_repr.buffer_len() + ext_repr.length as usize..];
+ }
+ SixlowpanNhcPacket::UdpHeader => {
+ let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?;
+ let payload = udp_packet.payload();
+ let udp_repr = SixlowpanUdpNhcRepr::parse(
+ &udp_packet,
+ &iphc_repr.src_addr,
+ &iphc_repr.dst_addr,
+ &ChecksumCapabilities::ignored(),
+ )?;
+
+ if payload.len() + 8 > buffer.len() {
+ return Err(Error);
+ }
+
+ let mut udp = UdpPacket::new_unchecked(&mut buffer[..payload.len() + 8]);
+ udp_repr
+ .0
+ .emit_header(&mut udp, rest_size - udp_repr.0.header_len());
+ buffer[8..][..payload.len()].copy_from_slice(payload);
+
+ break;
+ }
+ },
+ SixlowpanNextHeader::Uncompressed(proto) => match proto {
+ IpProtocol::HopByHop => unreachable!(),
+ IpProtocol::Tcp => {
+ buffer.copy_from_slice(data);
+ break;
+ }
+ IpProtocol::Udp => {
+ buffer.copy_from_slice(data);
+ break;
+ }
+ IpProtocol::Icmpv6 => {
+ buffer.copy_from_slice(data);
+ break;
+ }
+ _ => unreachable!(),
+ },
+ }
+ }
+
+ Ok(decompressed_size)
+ }
+
+ pub(super) fn dispatch_sixlowpan<Tx: TxToken>(
+ &mut self,
+ mut tx_token: Tx,
+ meta: PacketMeta,
+ packet: Packet,
+ ieee_repr: Ieee802154Repr,
+ frag: &mut Fragmenter,
+ ) {
+ let packet = match packet {
+ #[cfg(feature = "proto-ipv4")]
+ Packet::Ipv4(_) => unreachable!(),
+ Packet::Ipv6(packet) => packet,
+ };
+
+ // First we calculate the size we are going to need. If the size is bigger than the MTU,
+ // then we use fragmentation.
+ let (total_size, compressed_size, uncompressed_size) =
+ Self::compressed_packet_size(&packet, &ieee_repr);
+
+ let ieee_len = ieee_repr.buffer_len();
+
+ // TODO(thvdveld): use the MTU of the device.
+ if total_size + ieee_len > 125 {
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ {
+ // The packet does not fit in one Ieee802154 frame, so we need fragmentation.
+ // We do this by emitting everything in the `frag.buffer` from the interface.
+ // After emitting everything into that buffer, we send the first fragment heere.
+ // When `poll` is called again, we check if frag was fully sent, otherwise we
+ // call `dispatch_ieee802154_frag`, which will transmit the other fragments.
+
+ // `dispatch_ieee802154_frag` requires some information about the total packet size,
+ // the link local source and destination address...
+
+ let pkt = frag;
+ if pkt.buffer.len() < total_size {
+ net_debug!(
+ "dispatch_ieee802154: dropping, \
+ fragmentation buffer is too small, at least {} needed",
+ total_size
+ );
+ return;
+ }
+
+ let payload_length = packet.header.payload_len;
+
+ Self::ipv6_to_sixlowpan(
+ &self.checksum_caps(),
+ packet,
+ &ieee_repr,
+ &mut pkt.buffer[..],
+ );
+
+ pkt.sixlowpan.ll_dst_addr = ieee_repr.dst_addr.unwrap();
+ pkt.sixlowpan.ll_src_addr = ieee_repr.src_addr.unwrap();
+ pkt.packet_len = total_size;
+
+ // The datagram size that we need to set in the first fragment header is equal to the
+ // IPv6 payload length + 40.
+ pkt.sixlowpan.datagram_size = (payload_length + 40) as u16;
+
+ let tag = self.get_sixlowpan_fragment_tag();
+ // We save the tag for the other fragments that will be created when calling `poll`
+ // multiple times.
+ pkt.sixlowpan.datagram_tag = tag;
+
+ let frag1 = SixlowpanFragRepr::FirstFragment {
+ size: pkt.sixlowpan.datagram_size,
+ tag,
+ };
+ let fragn = SixlowpanFragRepr::Fragment {
+ size: pkt.sixlowpan.datagram_size,
+ tag,
+ offset: 0,
+ };
+
+ // We calculate how much data we can send in the first fragment and the other
+ // fragments. The eventual IPv6 sizes of these fragments need to be a multiple of eight
+ // (except for the last fragment) since the offset field in the fragment is an offset
+ // in multiples of 8 octets. This is explained in [RFC 4944 § 5.3].
+ //
+ // [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
+
+ let header_diff = uncompressed_size - compressed_size;
+ let frag1_size =
+ (125 - ieee_len - frag1.buffer_len() + header_diff) / 8 * 8 - header_diff;
+
+ pkt.sixlowpan.fragn_size = (125 - ieee_len - fragn.buffer_len()) / 8 * 8;
+ pkt.sent_bytes = frag1_size;
+ pkt.sixlowpan.datagram_offset = frag1_size + header_diff;
+
+ tx_token.set_meta(meta);
+ tx_token.consume(ieee_len + frag1.buffer_len() + frag1_size, |mut tx_buf| {
+ // Add the IEEE header.
+ let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]);
+ ieee_repr.emit(&mut ieee_packet);
+ tx_buf = &mut tx_buf[ieee_len..];
+
+ // Add the first fragment header
+ let mut frag1_packet = SixlowpanFragPacket::new_unchecked(&mut tx_buf);
+ frag1.emit(&mut frag1_packet);
+ tx_buf = &mut tx_buf[frag1.buffer_len()..];
+
+ // Add the buffer part.
+ tx_buf[..frag1_size].copy_from_slice(&pkt.buffer[..frag1_size]);
+ });
+ }
+
+ #[cfg(not(feature = "proto-sixlowpan-fragmentation"))]
+ {
+ net_debug!(
+ "Enable the `proto-sixlowpan-fragmentation` feature for fragmentation support."
+ );
+ return;
+ }
+ } else {
+ tx_token.set_meta(meta);
+
+ // We don't need fragmentation, so we emit everything to the TX token.
+ tx_token.consume(total_size + ieee_len, |mut tx_buf| {
+ let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]);
+ ieee_repr.emit(&mut ieee_packet);
+ tx_buf = &mut tx_buf[ieee_len..];
+
+ Self::ipv6_to_sixlowpan(&self.checksum_caps(), packet, &ieee_repr, tx_buf);
+ });
+ }
+ }
+
+ fn ipv6_to_sixlowpan(
+ checksum_caps: &ChecksumCapabilities,
+ mut packet: PacketV6,
+ ieee_repr: &Ieee802154Repr,
+ mut buffer: &mut [u8],
+ ) {
+ let last_header = packet.payload.as_sixlowpan_next_header();
+ let next_header = last_header;
+
+ #[cfg(feature = "proto-ipv6-hbh")]
+ let next_header = if packet.hop_by_hop.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ next_header
+ };
+
+ #[cfg(feature = "proto-ipv6-routing")]
+ let next_header = if packet.routing.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ next_header
+ };
+
+ let iphc_repr = SixlowpanIphcRepr {
+ src_addr: packet.header.src_addr,
+ ll_src_addr: ieee_repr.src_addr,
+ dst_addr: packet.header.dst_addr,
+ ll_dst_addr: ieee_repr.dst_addr,
+ next_header,
+ hop_limit: packet.header.hop_limit,
+ ecn: None,
+ dscp: None,
+ flow_label: None,
+ };
+
+ iphc_repr.emit(&mut SixlowpanIphcPacket::new_unchecked(
+ &mut buffer[..iphc_repr.buffer_len()],
+ ));
+ buffer = &mut buffer[iphc_repr.buffer_len()..];
+
+ // Emit the Hop-by-Hop header
+ #[cfg(feature = "proto-ipv6-hbh")]
+ if let Some(hbh) = packet.hop_by_hop {
+ #[allow(unused)]
+ let next_header = last_header;
+
+ #[cfg(feature = "proto-ipv6-routing")]
+ let next_header = if packet.routing.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ last_header
+ };
+
+ let ext_hdr = SixlowpanExtHeaderRepr {
+ ext_header_id: SixlowpanExtHeaderId::HopByHopHeader,
+ next_header,
+ length: hbh.options.iter().map(|o| o.buffer_len()).sum::<usize>() as u8,
+ };
+ ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked(
+ &mut buffer[..ext_hdr.buffer_len()],
+ ));
+ buffer = &mut buffer[ext_hdr.buffer_len()..];
+
+ for opt in &hbh.options {
+ opt.emit(&mut Ipv6Option::new_unchecked(
+ &mut buffer[..opt.buffer_len()],
+ ));
+
+ buffer = &mut buffer[opt.buffer_len()..];
+ }
+ }
+
+ // Emit the Routing header
+ #[cfg(feature = "proto-ipv6-routing")]
+ if let Some(routing) = &packet.routing {
+ let ext_hdr = SixlowpanExtHeaderRepr {
+ ext_header_id: SixlowpanExtHeaderId::RoutingHeader,
+ next_header,
+ length: routing.buffer_len() as u8,
+ };
+ ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked(
+ &mut buffer[..ext_hdr.buffer_len()],
+ ));
+ buffer = &mut buffer[ext_hdr.buffer_len()..];
+
+ routing.emit(&mut Ipv6RoutingHeader::new_unchecked(
+ &mut buffer[..routing.buffer_len()],
+ ));
+ buffer = &mut buffer[routing.buffer_len()..];
+ }
+
+ match &mut packet.payload {
+ IpPayload::Icmpv6(icmp_repr) => {
+ icmp_repr.emit(
+ &packet.header.src_addr.into(),
+ &packet.header.dst_addr.into(),
+ &mut Icmpv6Packet::new_unchecked(&mut buffer[..icmp_repr.buffer_len()]),
+ checksum_caps,
+ );
+ }
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ IpPayload::Udp(udp_repr, payload) => {
+ let udp_repr = SixlowpanUdpNhcRepr(*udp_repr);
+ udp_repr.emit(
+ &mut SixlowpanUdpNhcPacket::new_unchecked(
+ &mut buffer[..udp_repr.header_len() + payload.len()],
+ ),
+ &iphc_repr.src_addr,
+ &iphc_repr.dst_addr,
+ payload.len(),
+ |buf| buf.copy_from_slice(payload),
+ checksum_caps,
+ );
+ }
+ #[cfg(feature = "socket-tcp")]
+ IpPayload::Tcp(tcp_repr) => {
+ tcp_repr.emit(
+ &mut TcpPacket::new_unchecked(&mut buffer[..tcp_repr.buffer_len()]),
+ &packet.header.src_addr.into(),
+ &packet.header.dst_addr.into(),
+ checksum_caps,
+ );
+ }
+ #[cfg(feature = "socket-raw")]
+ IpPayload::Raw(_raw) => todo!(),
+
+ #[allow(unreachable_patterns)]
+ _ => unreachable!(),
+ }
+ }
+
+ /// Calculates three sizes:
+ /// - total size: the size of a compressed IPv6 packet
+ /// - compressed header size: the size of the compressed headers
+ /// - uncompressed header size: the size of the headers that are not compressed
+ /// They are returned as a tuple in the same order.
+ fn compressed_packet_size(
+ packet: &PacketV6,
+ ieee_repr: &Ieee802154Repr,
+ ) -> (usize, usize, usize) {
+ let last_header = packet.payload.as_sixlowpan_next_header();
+ let next_header = last_header;
+
+ #[cfg(feature = "proto-ipv6-hbh")]
+ let next_header = if packet.hop_by_hop.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ next_header
+ };
+
+ #[cfg(feature = "proto-ipv6-routing")]
+ let next_header = if packet.routing.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ next_header
+ };
+
+ let iphc = SixlowpanIphcRepr {
+ src_addr: packet.header.src_addr,
+ ll_src_addr: ieee_repr.src_addr,
+ dst_addr: packet.header.dst_addr,
+ ll_dst_addr: ieee_repr.dst_addr,
+ next_header,
+ hop_limit: packet.header.hop_limit,
+ ecn: None,
+ dscp: None,
+ flow_label: None,
+ };
+
+ let mut total_size = iphc.buffer_len();
+ let mut compressed_hdr_size = iphc.buffer_len();
+ let mut uncompressed_hdr_size = packet.header.buffer_len();
+
+ // Add the hop-by-hop to the sizes.
+ #[cfg(feature = "proto-ipv6-hbh")]
+ if let Some(hbh) = &packet.hop_by_hop {
+ #[allow(unused)]
+ let next_header = last_header;
+
+ #[cfg(feature = "proto-ipv6-routing")]
+ let next_header = if packet.routing.is_some() {
+ SixlowpanNextHeader::Compressed
+ } else {
+ last_header
+ };
+
+ let options_size = hbh.options.iter().map(|o| o.buffer_len()).sum::<usize>();
+
+ let ext_hdr = SixlowpanExtHeaderRepr {
+ ext_header_id: SixlowpanExtHeaderId::HopByHopHeader,
+ next_header,
+ length: hbh.buffer_len() as u8 + options_size as u8,
+ };
+
+ total_size += ext_hdr.buffer_len() + options_size;
+ compressed_hdr_size += ext_hdr.buffer_len() + options_size;
+ uncompressed_hdr_size += hbh.buffer_len() + options_size;
+ }
+
+ // Add the routing header to the sizes.
+ #[cfg(feature = "proto-ipv6-routing")]
+ if let Some(routing) = &packet.routing {
+ let ext_hdr = SixlowpanExtHeaderRepr {
+ ext_header_id: SixlowpanExtHeaderId::RoutingHeader,
+ next_header,
+ length: routing.buffer_len() as u8,
+ };
+ total_size += ext_hdr.buffer_len() + routing.buffer_len();
+ compressed_hdr_size += ext_hdr.buffer_len() + routing.buffer_len();
+ uncompressed_hdr_size += routing.buffer_len();
+ }
+
+ match packet.payload {
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ IpPayload::Udp(udp_hdr, payload) => {
+ uncompressed_hdr_size += udp_hdr.header_len();
+
+ let udp_hdr = SixlowpanUdpNhcRepr(udp_hdr);
+ compressed_hdr_size += udp_hdr.header_len();
+
+ total_size += udp_hdr.header_len() + payload.len();
+ }
+ _ => {
+ total_size += packet.header.payload_len;
+ }
+ }
+
+ (total_size, compressed_hdr_size, uncompressed_hdr_size)
+ }
+
+ #[cfg(feature = "proto-sixlowpan-fragmentation")]
+ pub(super) fn dispatch_sixlowpan_frag<Tx: TxToken>(
+ &mut self,
+ tx_token: Tx,
+ ieee_repr: Ieee802154Repr,
+ frag: &mut Fragmenter,
+ ) {
+ // Create the FRAG_N header.
+ let fragn = SixlowpanFragRepr::Fragment {
+ size: frag.sixlowpan.datagram_size,
+ tag: frag.sixlowpan.datagram_tag,
+ offset: (frag.sixlowpan.datagram_offset / 8) as u8,
+ };
+
+ let ieee_len = ieee_repr.buffer_len();
+ let frag_size = (frag.packet_len - frag.sent_bytes).min(frag.sixlowpan.fragn_size);
+
+ tx_token.consume(
+ ieee_repr.buffer_len() + fragn.buffer_len() + frag_size,
+ |mut tx_buf| {
+ let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]);
+ ieee_repr.emit(&mut ieee_packet);
+ tx_buf = &mut tx_buf[ieee_len..];
+
+ let mut frag_packet =
+ SixlowpanFragPacket::new_unchecked(&mut tx_buf[..fragn.buffer_len()]);
+ fragn.emit(&mut frag_packet);
+ tx_buf = &mut tx_buf[fragn.buffer_len()..];
+
+ // Add the buffer part
+ tx_buf[..frag_size].copy_from_slice(&frag.buffer[frag.sent_bytes..][..frag_size]);
+
+ frag.sent_bytes += frag_size;
+ frag.sixlowpan.datagram_offset += frag_size;
+ },
+ );
+ }
+}
+
+#[cfg(test)]
+#[cfg(all(feature = "proto-rpl", feature = "proto-ipv6-hbh"))]
+mod tests {
+ use super::*;
+
+ static SIXLOWPAN_COMPRESSED_RPL_DAO: [u8; 99] = [
+ 0x61, 0xdc, 0x45, 0xcd, 0xab, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x7e, 0xf7, 0x00, 0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00,
+ 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00,
+ 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03,
+ 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ ];
+
+ static SIXLOWPAN_UNCOMPRESSED_RPL_DAO: [u8; 114] = [
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x40, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x3a, 0x00, 0x63, 0x04, 0x00,
+ 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00,
+ 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03,
+ 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ ];
+
+ #[test]
+ fn test_sixlowpan_decompress_hop_by_hop_with_icmpv6() {
+ let address_context = [SixlowpanAddressContext([
+ 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ])];
+
+ let ieee_frame = Ieee802154Frame::new_checked(&SIXLOWPAN_COMPRESSED_RPL_DAO).unwrap();
+ let ieee_repr = Ieee802154Repr::parse(&ieee_frame).unwrap();
+
+ let mut buffer = [0u8; 256];
+ let len = InterfaceInner::sixlowpan_to_ipv6(
+ &address_context,
+ &ieee_repr,
+ ieee_frame.payload().unwrap(),
+ None,
+ &mut buffer[..],
+ )
+ .unwrap();
+
+ assert_eq!(&buffer[..len], &SIXLOWPAN_UNCOMPRESSED_RPL_DAO);
+ }
+
+ #[test]
+ fn test_sixlowpan_compress_hop_by_hop_with_icmpv6() {
+ let ieee_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: true,
+ sequence_number: Some(69),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2006,
+ dst_pan_id: Some(Ieee802154Pan(43981)),
+ dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])),
+ src_pan_id: None,
+ src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])),
+ };
+
+ let mut ip_packet = PacketV6 {
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::from_bytes(&[
+ 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3,
+ ]),
+ dst_addr: Ipv6Address::from_bytes(&[
+ 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1,
+ ]),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 66,
+ hop_limit: 64,
+ },
+ #[cfg(feature = "proto-ipv6-hbh")]
+ hop_by_hop: None,
+ #[cfg(feature = "proto-ipv6-fragmentation")]
+ fragment: None,
+ #[cfg(feature = "proto-ipv6-routing")]
+ routing: None,
+ payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject {
+ rpl_instance_id: RplInstanceId::Global(30),
+ expect_ack: false,
+ sequence: 241,
+ dodag_id: Some(Ipv6Address::from_bytes(&[
+ 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1,
+ ])),
+ options: &[],
+ })),
+ };
+
+ let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr);
+ let mut buffer = vec![0u8; total_size];
+
+ InterfaceInner::ipv6_to_sixlowpan(
+ &ChecksumCapabilities::default(),
+ ip_packet,
+ &ieee_repr,
+ &mut buffer[..total_size],
+ );
+
+ let result = [
+ 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0,
+ 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1,
+ 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40,
+ 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0,
+ 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3,
+ 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1,
+ ];
+
+ assert_eq!(&result, &result);
+ }
+
+ #[test]
+ fn test_sixlowpan_compress_hop_by_hop_with_udp() {
+ let ieee_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: true,
+ sequence_number: Some(69),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2006,
+ dst_pan_id: Some(Ieee802154Pan(43981)),
+ dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])),
+ src_pan_id: None,
+ src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])),
+ };
+
+ let addr = Ipv6Address::from_bytes(&[253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3]);
+ let parent_address =
+ Ipv6Address::from_bytes(&[253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1]);
+
+ let mut hbh_options = heapless::Vec::new();
+ hbh_options
+ .push(Ipv6OptionRepr::Rpl(RplHopByHopRepr {
+ down: false,
+ rank_error: false,
+ forwarding_error: false,
+ instance_id: RplInstanceId::from(0x1e),
+ sender_rank: 0x300,
+ }))
+ .unwrap();
+
+ let mut ip_packet = PacketV6 {
+ header: Ipv6Repr {
+ src_addr: addr,
+ dst_addr: parent_address,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 66,
+ hop_limit: 64,
+ },
+ #[cfg(feature = "proto-ipv6-hbh")]
+ hop_by_hop: Some(Ipv6HopByHopRepr {
+ options: hbh_options,
+ }),
+ #[cfg(feature = "proto-ipv6-fragmentation")]
+ fragment: None,
+ #[cfg(feature = "proto-ipv6-routing")]
+ routing: None,
+ payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject {
+ rpl_instance_id: RplInstanceId::Global(30),
+ expect_ack: false,
+ sequence: 241,
+ dodag_id: Some(Ipv6Address::from_bytes(&[
+ 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1,
+ ])),
+ options: &[
+ 5, 18, 0, 128, 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3, 6, 20, 0, 0,
+ 0, 30, 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1,
+ ],
+ })),
+ };
+
+ let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr);
+ let mut buffer = vec![0u8; total_size];
+
+ InterfaceInner::ipv6_to_sixlowpan(
+ &ChecksumCapabilities::default(),
+ ip_packet,
+ &ieee_repr,
+ &mut buffer[..total_size],
+ );
+
+ let result = [
+ 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0,
+ 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1,
+ 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40,
+ 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0,
+ 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3,
+ 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1,
+ ];
+
+ assert_eq!(&buffer[..total_size], &result);
+ }
+}
diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs
new file mode 100644
index 0000000..d685f37
--- /dev/null
+++ b/src/iface/interface/tests/ipv4.rs
@@ -0,0 +1,968 @@
+use super::*;
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_no_icmp_no_unicast(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ // Unknown Ipv4 Protocol
+ //
+ // Because the destination is the broadcast address
+ // this should not trigger and Destination Unreachable
+ // response. See RFC 1122 § 3.2.2.
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 54];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // Ensure that the unknown protocol frame does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_icmp_error_no_payload(#[case] medium: Medium) {
+ static NO_BYTES: [u8; 0] = [];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ // Unknown Ipv4 Protocol with no payload
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 34];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // The expected Destination Unreachable response due to the
+ // unknown protocol
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::ProtoUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(12),
+ payload_len: 0,
+ hop_limit: 64,
+ },
+ data: &NO_BYTES,
+ };
+
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_repr)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_local_subnet_broadcasts(#[case] medium: Medium) {
+ let (mut iface, _, _device) = setup(medium);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 23]), 24));
+ });
+ });
+
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 254])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 16));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 255])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 8));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 255])));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "medium-ip", feature = "socket-udp"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "medium-ethernet", feature = "socket-udp"))]
+fn test_icmp_error_port_unreachable(#[case] medium: Medium) {
+ static UDP_PAYLOAD: [u8; 12] = [
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x6c, 0x64, 0x21,
+ ];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut udp_bytes_unicast = vec![0u8; 20];
+ let mut udp_bytes_broadcast = vec![0u8; 20];
+ let mut packet_unicast = UdpPacket::new_unchecked(&mut udp_bytes_unicast);
+ let mut packet_broadcast = UdpPacket::new_unchecked(&mut udp_bytes_broadcast);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_unicast,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ let data = packet_unicast.into_inner();
+
+ // The expected Destination Unreachable ICMPv4 error response due
+ // to no sockets listening on the destination port.
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ },
+ data,
+ };
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ data
+ ),
+ Some(expected_repr)
+ );
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_broadcast,
+ &ip_repr.src_addr(),
+ &IpAddress::Ipv4(Ipv4Address::BROADCAST),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ // Ensure that the port unreachable error does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address and no socket is bound to the port.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ packet_broadcast.into_inner(),
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_ipv4_broadcast(#[case] medium: Medium) {
+ use crate::wire::{Icmpv4Packet, Icmpv4Repr};
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let our_ipv4_addr = iface.ipv4_addr().unwrap();
+ let src_ipv4_addr = Ipv4Address([127, 0, 0, 2]);
+
+ // ICMPv4 echo request
+ let icmpv4_data: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+ let icmpv4_repr = Icmpv4Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+
+ // Send to IPv4 broadcast address
+ let ipv4_repr = Ipv4Repr {
+ src_addr: src_ipv4_addr,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: icmpv4_repr.buffer_len(),
+ };
+
+ // Emit to ip frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + icmpv4_repr.buffer_len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes[..]),
+ &ChecksumCapabilities::default(),
+ );
+ icmpv4_repr.emit(
+ &mut Icmpv4Packet::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ // Expected ICMPv4 echo reply
+ let expected_icmpv4_repr = Icmpv4Repr::EchoReply {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+ let expected_ipv4_repr = Ipv4Repr {
+ src_addr: our_ipv4_addr,
+ dst_addr: src_ipv4_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmpv4_repr.buffer_len(),
+ };
+ let expected_packet =
+ Packet::new_ipv4(expected_ipv4_repr, IpPayload::Icmpv4(expected_icmpv4_repr));
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_packet)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_valid_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: local_ip_addr,
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_other_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x03]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for someone else does not trigger an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Ensure the address of the requester was NOT entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(Ipv4Address([0x7f, 0x00, 0x00, 0x01])),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Err(DispatchError::NeighborPending)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_arp_flush_after_update_ip(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ {
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+ }
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+
+ // Update IP addrs to trigger ARP cache flush
+ let local_ip_addr_new = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(local_ip_addr_new, 24));
+ });
+ });
+
+ // ARP cache flush after address change
+ assert!(!iface.inner.has_neighbor(&IpAddress::Ipv4(remote_ip_addr)));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ethernet"))]
+fn test_icmpv4_socket(#[case] medium: Medium) {
+ use crate::wire::Icmpv4Packet;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+ let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+
+ let icmpv4_socket = icmp::Socket::new(rx_buffer, tx_buffer);
+
+ let socket_handle = sockets.add(icmpv4_socket);
+
+ let ident = 0x1234;
+ let seq_no = 0x5432;
+ let echo_data = &[0xff; 16];
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ // Bind to the ID 0x1234
+ assert_eq!(socket.bind(icmp::Endpoint::Ident(ident)), Ok(()));
+
+ // Ensure the ident we bound to and the ident of the packet are the same.
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]);
+ let echo_repr = Icmpv4Repr::EchoRequest {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ echo_repr.emit(&mut packet, &ChecksumCapabilities::default());
+ let icmp_data = &*packet.into_inner();
+
+ let ipv4_repr = Ipv4Repr {
+ src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02),
+ dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01),
+ next_header: IpProtocol::Icmp,
+ payload_len: 24,
+ hop_limit: 64,
+ };
+ let ip_repr = IpRepr::Ipv4(ipv4_repr);
+
+ // Open a socket and ensure the packet is handled due to the listening
+ // socket.
+ assert!(!sockets.get_mut::<icmp::Socket>(socket_handle).can_recv());
+
+ // Confirm we still get EchoReply from `smoltcp` even with the ICMP socket listening
+ let echo_reply = Icmpv4Repr::EchoReply {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ let ipv4_reply = Ipv4Repr {
+ src_addr: ipv4_repr.dst_addr,
+ dst_addr: ipv4_repr.src_addr,
+ ..ipv4_repr
+ };
+ assert_eq!(
+ iface.inner.process_icmpv4(&mut sockets, ip_repr, icmp_data),
+ Some(Packet::new_ipv4(ipv4_reply, IpPayload::Icmpv4(echo_reply)))
+ );
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ icmp_data,
+ IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02))
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ethernet"))]
+fn test_handle_igmp(#[case] medium: Medium) {
+ fn recv_igmp(
+ device: &mut crate::tests::TestingDevice,
+ timestamp: Instant,
+ ) -> Vec<(Ipv4Repr, IgmpRepr)> {
+ let caps = device.capabilities();
+ let checksum_caps = &caps.checksum;
+ recv_all(device, timestamp)
+ .iter()
+ .filter_map(|frame| {
+ let ipv4_packet = match caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ let eth_frame = EthernetFrame::new_checked(frame).ok()?;
+ Ipv4Packet::new_checked(eth_frame.payload()).ok()?
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => Ipv4Packet::new_checked(&frame[..]).ok()?,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+ let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, checksum_caps).ok()?;
+ let ip_payload = ipv4_packet.payload();
+ let igmp_packet = IgmpPacket::new_checked(ip_payload).ok()?;
+ let igmp_repr = IgmpRepr::parse(&igmp_packet).ok()?;
+ Some((ipv4_repr, igmp_repr))
+ })
+ .collect::<Vec<_>>()
+ }
+
+ let groups = [
+ Ipv4Address::new(224, 0, 0, 22),
+ Ipv4Address::new(224, 0, 0, 56),
+ ];
+
+ let (mut iface, mut sockets, mut device) = setup(medium);
+
+ // Join multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .join_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let reports = recv_igmp(&mut device, timestamp);
+ assert_eq!(reports.len(), 2);
+ for (i, group_addr) in groups.iter().enumerate() {
+ assert_eq!(reports[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(reports[i].0.dst_addr, *group_addr);
+ assert_eq!(
+ reports[i].1,
+ IgmpRepr::MembershipReport {
+ group_addr: *group_addr,
+ version: IgmpVersion::Version2,
+ }
+ );
+ }
+
+ // General query
+ let timestamp = Instant::ZERO;
+ const GENERAL_QUERY_BYTES: &[u8] = &[
+ 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63,
+ 0x04, 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ ];
+ {
+ // Transmit GENERAL_QUERY_BYTES into loopback
+ let tx_token = device.transmit(timestamp).unwrap();
+ tx_token.consume(GENERAL_QUERY_BYTES.len(), |buffer| {
+ buffer.copy_from_slice(GENERAL_QUERY_BYTES);
+ });
+ }
+ // Trigger processing until all packets received through the
+ // loopback have been processed, including responses to
+ // GENERAL_QUERY_BYTES. Therefore `recv_all()` would return 0
+ // pkts that could be checked.
+ iface.socket_ingress(&mut device, &mut sockets);
+
+ // Leave multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .leave_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let leaves = recv_igmp(&mut device, timestamp);
+ assert_eq!(leaves.len(), 2);
+ for (i, group_addr) in groups.iter().cloned().enumerate() {
+ assert_eq!(leaves[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(leaves[i].0.dst_addr, Ipv4Address::MULTICAST_ALL_ROUTERS);
+ assert_eq!(leaves[i].1, IgmpRepr::LeaveGroup { group_addr });
+ }
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))]
+fn test_raw_socket_no_reply(#[case] medium: Medium) {
+ use crate::wire::{IpVersion, UdpPacket, UdpRepr};
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let packets = 1;
+ let rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ const PAYLOAD_LEN: usize = 10;
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + PAYLOAD_LEN,
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(
+ feature = "socket-raw",
+ feature = "socket-udp",
+ feature = "medium-ethernet"
+))]
+fn test_raw_socket_with_udp_socket(#[case] medium: Medium) {
+ use crate::wire::{IpEndpoint, IpVersion, UdpPacket, UdpRepr};
+
+ static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_socket_handle = sockets.add(udp_socket);
+
+ // Bind the socket to port 68
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert_eq!(socket.bind(68), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+
+ let packets = 1;
+ let raw_rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let raw_tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Udp,
+ raw_rx_buffer,
+ raw_tx_buffer,
+ );
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Make sure the UDP socket can still receive in presence of a Raw socket that handles UDP
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ &UDP_PAYLOAD[..],
+ IpEndpoint::new(src_addr.into(), 67).into()
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))]
+fn test_icmp_reply_size(#[case] medium: Medium) {
+ use crate::wire::IPV4_MIN_MTU as MIN_MTU;
+ const MAX_PAYLOAD_LEN: usize = 528;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let src_addr = Ipv4Address([192, 168, 1, 1]);
+ let dst_addr = Ipv4Address([192, 168, 1, 2]);
+
+ // UDP packet that if not tructated will cause a icmp port unreachable reply
+ // to exceed the minimum mtu bytes in length.
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ MAX_PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+
+ let ip_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN,
+ };
+ let payload = packet.into_inner();
+
+ let expected_icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: ip_repr,
+ data: &payload[..MAX_PAYLOAD_LEN],
+ };
+
+ let expected_ip_repr = Ipv4Repr {
+ src_addr: dst_addr,
+ dst_addr: src_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmp_repr.buffer_len(),
+ };
+
+ assert_eq!(
+ expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(),
+ MIN_MTU
+ );
+
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr.into(),
+ udp_repr,
+ false,
+ &vec![0x2a; MAX_PAYLOAD_LEN],
+ payload,
+ ),
+ Some(Packet::new_ipv4(
+ expected_ip_repr,
+ IpPayload::Icmpv4(expected_icmp_repr)
+ ))
+ );
+}
diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs
new file mode 100644
index 0000000..9c5f099
--- /dev/null
+++ b/src/iface/interface/tests/ipv6.rs
@@ -0,0 +1,988 @@
+use super::*;
+
+fn parse_ipv6(data: &[u8]) -> crate::wire::Result<Packet<'_>> {
+ let ipv6_header = Ipv6Packet::new_checked(data)?;
+ let ipv6 = Ipv6Repr::parse(&ipv6_header)?;
+
+ match ipv6.next_header {
+ IpProtocol::HopByHop => todo!(),
+ IpProtocol::Icmp => todo!(),
+ IpProtocol::Igmp => todo!(),
+ IpProtocol::Tcp => todo!(),
+ IpProtocol::Udp => todo!(),
+ IpProtocol::Ipv6Route => todo!(),
+ IpProtocol::Ipv6Frag => todo!(),
+ IpProtocol::IpSecEsp => todo!(),
+ IpProtocol::IpSecAh => todo!(),
+ IpProtocol::Icmpv6 => {
+ let icmp = Icmpv6Repr::parse(
+ &ipv6.src_addr.into(),
+ &ipv6.dst_addr.into(),
+ &Icmpv6Packet::new_checked(ipv6_header.payload())?,
+ &Default::default(),
+ )?;
+ Ok(Packet::new_ipv6(ipv6, IpPayload::Icmpv6(icmp)))
+ }
+ IpProtocol::Ipv6NoNxt => todo!(),
+ IpProtocol::Ipv6Opts => todo!(),
+ IpProtocol::Unknown(_) => todo!(),
+ }
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn multicast_source_address(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn hop_by_hop_skip_with_icmp(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (skipped)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0xf, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+fn hop_by_hop_discard_param_problem(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard + ParamProblem)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 75,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedOption,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::HopByHop,
+ payload_len: 27,
+ hop_limit: 64,
+ },
+ data: &[
+ 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
+ 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard (0b11) + ParamProblem)
+ // - ICMP echo request
+ //
+ // In this case, even if the destination address is a multicast address, an ICMPv6 ParamProblem
+ // should be transmitted.
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 75,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedOption,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::HopByHop,
+ payload_len: 27,
+ hop_limit: 64,
+ },
+ data: &[
+ 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
+ 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn imcp_empty_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x8, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x84, 0x3c, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 8,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoRequest {
+ ident: 0,
+ seq_no: 0,
+ data: b"",
+ })
+ ))
+ );
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 8,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 0,
+ seq_no: 0,
+ data: b"",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72,
+ 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoRequest {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ })
+ ))
+ );
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_reply_as_input(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x81, 0x0, 0x2d, 0x56, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x6f, 0x72, 0x65,
+ 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 0,
+ seq_no: 0,
+ data: b"Lorem Ipsum",
+ })
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 48,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ },
+ data: &[],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn unknown_proto(#[case] medium: Medium) {
+ // Since the destination address is multicast, we should answer with an ICMPv6 message.
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 48,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ },
+ data: &[],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn ndsic_neighbor_advertisement_ethernet(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x9f, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 32,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 1])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::Found(HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
+ 0, 0, 0, 0, 0, 1
+ ]))),
+ );
+}
+
+#[rstest]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn ndsic_neighbor_advertisement_ethernet_multicast_addr(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0xa0, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 32,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ ])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::NotFound,
+ );
+}
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn ndsic_neighbor_advertisement_ieee802154(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x28, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x96, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 40,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 0, 0, 1])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::Found(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
+ &[0, 0, 0, 0, 0, 0, 0, 1]
+ ))),
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_valid_ndisc_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 86];
+
+ let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1);
+ let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
+ target_addr: local_ip_addr,
+ lladdr: Some(remote_hw_addr.into()),
+ });
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: remote_ip_addr,
+ dst_addr: local_ip_addr.solicited_node(),
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 0xff,
+ payload_len: solicit.buffer_len(),
+ });
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00]));
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Ipv6);
+ ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
+ solicit.emit(
+ &remote_ip_addr.into(),
+ &local_ip_addr.solicited_node().into(),
+ &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]),
+ &ChecksumCapabilities::default(),
+ );
+
+ let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: local_ip_addr,
+ lladdr: Some(local_hw_addr.into()),
+ });
+
+ let ipv6_expected = Ipv6Repr {
+ src_addr: local_ip_addr,
+ dst_addr: remote_ip_addr,
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 0xff,
+ payload_len: icmpv6_expected.buffer_len(),
+ };
+
+ // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Ip(Packet::new_ipv6(
+ ipv6_expected,
+ IpPayload::Icmpv6(icmpv6_expected)
+ )))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv6(local_ip_addr),
+ &IpAddress::Ipv6(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn test_solicited_node_addrs(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut new_addrs = heapless::Vec::<IpCidr, IFACE_MAX_ADDR_COUNT>::new();
+ new_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 1, 2, 0, 2), 64))
+ .unwrap();
+ new_addrs
+ .push(IpCidr::new(
+ IpAddress::v6(0xfe80, 0, 0, 0, 3, 4, 0, 0xffff),
+ 64,
+ ))
+ .unwrap();
+ iface.update_ip_addrs(|addrs| {
+ new_addrs.extend(addrs.to_vec());
+ *addrs = new_addrs;
+ });
+ assert!(iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
+ assert!(iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
+ assert!(!iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003)));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))]
+#[case(Medium::Ieee802154)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ieee802154"))]
+fn test_icmp_reply_size(#[case] medium: Medium) {
+ use crate::wire::Icmpv6DstUnreachable;
+ use crate::wire::IPV6_MIN_MTU as MIN_MTU;
+ const MAX_PAYLOAD_LEN: usize = 1192;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let src_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ let dst_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
+
+ // UDP packet that if not tructated will cause a icmp port unreachable reply
+ // to exceed the minimum mtu bytes in length.
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ MAX_PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+
+ let ip_repr = Ipv6Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN,
+ };
+ let payload = packet.into_inner();
+
+ let expected_icmp_repr = Icmpv6Repr::DstUnreachable {
+ reason: Icmpv6DstUnreachable::PortUnreachable,
+ header: ip_repr,
+ data: &payload[..MAX_PAYLOAD_LEN],
+ };
+
+ let expected_ip_repr = Ipv6Repr {
+ src_addr: dst_addr,
+ dst_addr: src_addr,
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 64,
+ payload_len: expected_icmp_repr.buffer_len(),
+ };
+
+ assert_eq!(
+ expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(),
+ MIN_MTU
+ );
+
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr.into(),
+ udp_repr,
+ false,
+ &vec![0x2a; MAX_PAYLOAD_LEN],
+ payload,
+ ),
+ Some(Packet::new_ipv6(
+ expected_ip_repr,
+ IpPayload::Icmpv6(expected_icmp_repr)
+ ))
+ );
+}
+
+#[cfg(feature = "medium-ip")]
+#[test]
+fn get_source_address() {
+ let (mut iface, _, _) = setup(Medium::Ip);
+
+ const OWN_LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ const OWN_UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 2);
+ const OWN_UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 2);
+ const OWN_GLOBAL_UNICAST_ADDR1: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 1);
+
+ // List of addresses of the interface:
+ // fe80::1/64
+ // fd00::201:1:1:1:2/64
+ // fd01::201:1:1:1:2/64
+ // 2001:db8:3::1/64
+ // ::1/128
+ // ::/128
+ iface.update_ip_addrs(|addrs| {
+ addrs.clear();
+
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_LINK_LOCAL_ADDR, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_GLOBAL_UNICAST_ADDR1, 64)))
+ .unwrap();
+
+ // These should never be used:
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128)))
+ .unwrap();
+ });
+
+ // List of addresses we test:
+ // fe80::42 -> fe80::1
+ // fd00::201:1:1:1:1 -> fd00::201:1:1:1:2
+ // fd01::201:1:1:1:1 -> fd01::201:1:1:1:2
+ // fd02::201:1:1:1:1 -> fd00::201:1:1:1:2 (because first added in the list)
+ // ff02::1 -> fe80::1 (same scope)
+ // 2001:db8:3::2 -> 2001:db8:3::1
+ // 2001:db9:3::2 -> 2001:db8:3::1
+ const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42);
+ const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
+ const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1);
+ const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1);
+ const GLOBAL_UNICAST_ADDR1: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2);
+ const GLOBAL_UNICAST_ADDR2: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2);
+
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2),
+ Some(OWN_UNIQUE_LOCAL_ADDR2)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface
+ .inner
+ .get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+
+ assert_eq!(
+ iface.get_source_address_ipv6(&LINK_LOCAL_ADDR),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2),
+ Some(OWN_UNIQUE_LOCAL_ADDR2)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+}
diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs
new file mode 100644
index 0000000..b4b4416
--- /dev/null
+++ b/src/iface/interface/tests/mod.rs
@@ -0,0 +1,235 @@
+#[cfg(feature = "proto-ipv4")]
+mod ipv4;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6;
+#[cfg(feature = "proto-sixlowpan")]
+mod sixlowpan;
+
+#[cfg(feature = "proto-igmp")]
+use std::vec::Vec;
+
+use crate::tests::setup;
+
+use rstest::*;
+
+use super::*;
+
+use crate::iface::Interface;
+use crate::phy::ChecksumCapabilities;
+#[cfg(feature = "alloc")]
+use crate::phy::Loopback;
+use crate::time::Instant;
+
+#[allow(unused)]
+fn fill_slice(s: &mut [u8], val: u8) {
+ for x in s.iter_mut() {
+ *x = val
+ }
+}
+
+#[cfg(feature = "proto-igmp")]
+fn recv_all(device: &mut crate::tests::TestingDevice, timestamp: Instant) -> Vec<Vec<u8>> {
+ let mut pkts = Vec::new();
+ while let Some((rx, _tx)) = device.receive(timestamp) {
+ rx.consume(|pkt| {
+ pkts.push(pkt.to_vec());
+ });
+ }
+ pkts
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct MockTxToken;
+
+impl TxToken for MockTxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut junk = [0; 1536];
+ f(&mut junk[..len])
+ }
+}
+
+#[test]
+#[should_panic(expected = "The hardware address does not match the medium of the interface.")]
+#[cfg(all(feature = "medium-ip", feature = "medium-ethernet", feature = "alloc"))]
+fn test_new_panic() {
+ let mut device = Loopback::new(Medium::Ethernet);
+ let config = Config::new(HardwareAddress::Ip);
+ Interface::new(config, &mut device, Instant::ZERO);
+}
+
+#[rstest]
+#[cfg(feature = "default")]
+fn test_handle_udp_broadcast(
+ #[values(Medium::Ip, Medium::Ethernet, Medium::Ieee802154)] medium: Medium,
+) {
+ use crate::wire::IpEndpoint;
+
+ static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+
+ let udp_socket = udp::Socket::new(rx_buffer, tx_buffer);
+
+ let mut udp_bytes = vec![0u8; 13];
+ let mut packet = UdpPacket::new_unchecked(&mut udp_bytes);
+
+ let socket_handle = sockets.add(udp_socket);
+
+ #[cfg(feature = "proto-ipv6")]
+ let src_ip = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ let src_ip = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+
+ #[cfg(feature = "proto-ipv6")]
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: src_ip,
+ dst_addr: Ipv6Address::LINK_LOCAL_ALL_NODES,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 0x40,
+ });
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: src_ip,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 0x40,
+ });
+
+ // Bind the socket to port 68
+ let socket = sockets.get_mut::<udp::Socket>(socket_handle);
+ assert_eq!(socket.bind(68), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+
+ udp_repr.emit(
+ &mut packet,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ // Packet should be handled by bound UDP socket
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ packet.into_inner(),
+ ),
+ None
+ );
+
+ // Make sure the payload to the UDP packet processed by process_udp is
+ // appended to the bound sockets rx_buffer
+ let socket = sockets.get_mut::<udp::Socket>(socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((&UDP_PAYLOAD[..], IpEndpoint::new(src_ip.into(), 67).into()))
+ );
+}
+
+#[test]
+#[cfg(all(feature = "medium-ip", feature = "socket-tcp", feature = "proto-ipv6"))]
+pub fn tcp_not_accepted() {
+ let (mut iface, mut sockets, _) = setup(Medium::Ip);
+ let tcp = TcpRepr {
+ src_port: 4242,
+ dst_port: 4243,
+ control: TcpControl::Syn,
+ seq_number: TcpSeqNumber(-10001),
+ ack_number: None,
+ window_len: 256,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+
+ let mut tcp_bytes = vec![0u8; tcp.buffer_len()];
+
+ tcp.emit(
+ &mut TcpPacket::new_unchecked(&mut tcp_bytes),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1).into(),
+ &ChecksumCapabilities::default(),
+ );
+
+ assert_eq!(
+ iface.inner.process_tcp(
+ &mut sockets,
+ IpRepr::Ipv6(Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ }),
+ &tcp_bytes,
+ ),
+ Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Tcp(TcpRepr {
+ src_port: 4243,
+ dst_port: 4242,
+ control: TcpControl::Rst,
+ seq_number: TcpSeqNumber(0),
+ ack_number: Some(TcpSeqNumber(-10000)),
+ window_len: 0,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ })
+ ))
+ );
+ // Unspecified destination address.
+ tcp.emit(
+ &mut TcpPacket::new_unchecked(&mut tcp_bytes),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(),
+ &Ipv6Address::UNSPECIFIED.into(),
+ &ChecksumCapabilities::default(),
+ );
+
+ assert_eq!(
+ iface.inner.process_tcp(
+ &mut sockets,
+ IpRepr::Ipv6(Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::UNSPECIFIED,
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ }),
+ &tcp_bytes,
+ ),
+ None,
+ );
+}
diff --git a/src/iface/interface/tests/sixlowpan.rs b/src/iface/interface/tests/sixlowpan.rs
new file mode 100644
index 0000000..676835e
--- /dev/null
+++ b/src/iface/interface/tests/sixlowpan.rs
@@ -0,0 +1,434 @@
+use super::*;
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn ieee802154_wrong_pan_id(#[case] medium: Medium) {
+ let data = [
+ 0x41, 0xcc, 0x3b, 0xff, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a,
+ 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ieee802154(
+ &mut sockets,
+ PacketMeta::default(),
+ &data[..],
+ &mut iface.fragments
+ ),
+ response,
+ );
+}
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x41, 0xcc, 0x3b, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a,
+ 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, 0x6a, 0x33, 0x0a, 0x62, 0x17, 0x3a, 0x80, 0x00, 0xb0,
+ 0xe3, 0x00, 0x04, 0x00, 0x01, 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]),
+ dst_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x241c, 0x2957, 0x34a6, 0x3a62]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 4,
+ seq_no: 1,
+ data: &[
+ 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]),
+ 10,
+ )))
+ .unwrap();
+ });
+
+ assert_eq!(
+ iface.inner.process_ieee802154(
+ &mut sockets,
+ PacketMeta::default(),
+ &data[..],
+ &mut iface.fragments
+ ),
+ response,
+ );
+}
+
+#[test]
+#[cfg(feature = "proto-sixlowpan-fragmentation")]
+fn test_echo_request_sixlowpan_128_bytes() {
+ use crate::phy::Checksum;
+
+ let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76),
+ 10,
+ )))
+ .unwrap();
+ });
+ // TODO: modify the example, such that we can also test if the checksum is correctly
+ // computed.
+ iface.inner.caps.checksum.icmpv6 = Checksum::None;
+
+ assert_eq!(iface.inner.caps.medium, Medium::Ieee802154);
+ let now = iface.inner.now();
+
+ iface.inner.neighbor_cache.fill(
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x2, 0, 0, 0, 0, 0, 0, 0]).into(),
+ HardwareAddress::Ieee802154(Ieee802154Address::default()),
+ now,
+ );
+
+ let mut ieee802154_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(5),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: Some(Ieee802154Pan(0xbeef)),
+ dst_addr: Some(Ieee802154Address::Extended([
+ 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76,
+ ])),
+ src_pan_id: Some(Ieee802154Pan(0xbeef)),
+ src_addr: Some(Ieee802154Address::Extended([
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a,
+ ])),
+ };
+
+ // NOTE: this data is retrieved from tests with Contiki-NG
+
+ let request_first_part_packet = SixlowpanFragPacket::new_checked(&[
+ 0xc0, 0xb0, 0x00, 0x8e, 0x6a, 0x33, 0x05, 0x25, 0x2c, 0x3a, 0x80, 0x00, 0xe0, 0x71, 0x00,
+ 0x27, 0x00, 0x02, 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ])
+ .unwrap();
+
+ let request_first_part_iphc_packet =
+ SixlowpanIphcPacket::new_checked(request_first_part_packet.payload()).unwrap();
+
+ let request_first_part_iphc_repr = SixlowpanIphcRepr::parse(
+ &request_first_part_iphc_packet,
+ ieee802154_repr.src_addr,
+ ieee802154_repr.dst_addr,
+ &iface.inner.sixlowpan_address_context,
+ )
+ .unwrap();
+
+ assert_eq!(
+ request_first_part_iphc_repr.src_addr,
+ Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb,
+ 0x1a,
+ ]),
+ );
+ assert_eq!(
+ request_first_part_iphc_repr.dst_addr,
+ Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc,
+ 0x76,
+ ]),
+ );
+
+ let request_second_part = [
+ 0xe0, 0xb0, 0x00, 0x8e, 0x10, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ &request_first_part_packet.into_inner()[..],
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ ieee802154_repr.sequence_number = Some(6);
+
+ // data that was generated when using `ping -s 128`
+ let data = &[
+ 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ];
+
+ let result = iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ &request_second_part,
+ &mut iface.fragments,
+ );
+
+ assert_eq!(
+ result,
+ Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41,
+ 0xfc, 0x76,
+ ]),
+ dst_addr: Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42,
+ 0xb, 0x1a,
+ ]),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 136,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 39,
+ seq_no: 2,
+ data,
+ })
+ ))
+ );
+
+ iface.inner.neighbor_cache.fill(
+ IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a,
+ ])),
+ HardwareAddress::Ieee802154(Ieee802154Address::default()),
+ Instant::now(),
+ );
+
+ let tx_token = device.transmit(Instant::now()).unwrap();
+ iface.inner.dispatch_ieee802154(
+ Ieee802154Address::default(),
+ tx_token,
+ PacketMeta::default(),
+ result.unwrap(),
+ &mut iface.fragmenter,
+ );
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb0, 0x5, 0x4e, 0x7a, 0x11, 0x3a, 0x92, 0xfc, 0x48, 0xc2,
+ 0xa4, 0x41, 0xfc, 0x76, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a, 0x81, 0x0, 0x0,
+ 0x0, 0x0, 0x27, 0x0, 0x2, 0xa2, 0xc2, 0x2d, 0x63, 0x0, 0x0, 0x0, 0x0, 0xd9, 0x5e, 0xc,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+ 0x44, 0x45, 0x46, 0x47,
+ ]
+ );
+
+ iface.poll(Instant::now(), &mut device, &mut sockets);
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb0, 0x5, 0x4e, 0xf, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+ 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
+ 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ]
+ );
+}
+
+#[test]
+#[cfg(feature = "proto-sixlowpan-fragmentation")]
+fn test_sixlowpan_udp_with_fragmentation() {
+ use crate::phy::Checksum;
+
+ let mut ieee802154_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(5),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: Some(Ieee802154Pan(0xbeef)),
+ dst_addr: Some(Ieee802154Address::Extended([
+ 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76,
+ ])),
+ src_pan_id: Some(Ieee802154Pan(0xbeef)),
+ src_addr: Some(Ieee802154Address::Extended([
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a,
+ ])),
+ };
+
+ let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76),
+ 10,
+ )))
+ .unwrap();
+ });
+ iface.inner.caps.checksum.udp = Checksum::None;
+
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_socket_handle = sockets.add(udp_socket);
+
+ {
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert_eq!(socket.bind(6969), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+ }
+
+ let udp_first_part = &[
+ 0xc0, 0xbc, 0x00, 0x92, 0x6e, 0x33, 0x07, 0xe7, 0xdc, 0xf0, 0xd3, 0xc9, 0x1b, 0x39, 0xbf,
+ 0xa0, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
+ 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63,
+ 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70,
+ 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x49, 0x6e,
+ 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, 0x20, 0x74, 0x6f, 0x72,
+ 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ udp_first_part,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ ieee802154_repr.sequence_number = Some(6);
+
+ let udp_second_part = &[
+ 0xe0, 0xbc, 0x00, 0x92, 0x11, 0x64, 0x69, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73,
+ 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76,
+ 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20,
+ 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ udp_second_part,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+
+ let udp_data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
+In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo nec.";
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ &udp_data[..],
+ IpEndpoint {
+ addr: IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42,
+ 0xb, 0x1a,
+ ])),
+ port: 54217,
+ }
+ .into()
+ ))
+ );
+
+ let tx_token = device.transmit(Instant::now()).unwrap();
+ iface.inner.dispatch_ieee802154(
+ Ieee802154Address::default(),
+ tx_token,
+ PacketMeta::default(),
+ Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::default(),
+ dst_addr: Ipv6Address::default(),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_data.len(),
+ hop_limit: 64,
+ },
+ IpPayload::Udp(
+ UdpRepr {
+ src_port: 1234,
+ dst_port: 1234,
+ },
+ udp_data,
+ ),
+ ),
+ &mut iface.fragmenter,
+ );
+
+ iface.poll(Instant::now(), &mut device, &mut sockets);
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb4, 0x5, 0x4e, 0x7e, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0x0, 0x0,
+ 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
+ 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20,
+ 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64,
+ 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e,
+ 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73,
+ 0x20, 0x74,
+ ],
+ );
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb4, 0x5, 0x4e, 0xf, 0x6f, 0x72, 0x74, 0x6f, 0x72, 0x2e,
+ 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x64, 0x69, 0x74, 0x20,
+ 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76,
+ 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c,
+ 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e,
+ ]
+ );
+}
diff --git a/src/iface/mod.rs b/src/iface/mod.rs
new file mode 100644
index 0000000..3076088
--- /dev/null
+++ b/src/iface/mod.rs
@@ -0,0 +1,24 @@
+/*! Network interface logic.
+
+The `iface` module deals with the *network interfaces*. It filters incoming frames,
+provides lookup and caching of hardware addresses, and handles management packets.
+*/
+
+mod fragmentation;
+mod interface;
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+mod neighbor;
+mod route;
+#[cfg(feature = "proto-rpl")]
+mod rpl;
+mod socket_meta;
+mod socket_set;
+
+mod packet;
+
+#[cfg(feature = "proto-igmp")]
+pub use self::interface::MulticastError;
+pub use self::interface::{Config, Interface, InterfaceInner as Context};
+
+pub use self::route::{Route, RouteTableFull, Routes};
+pub use self::socket_set::{SocketHandle, SocketSet, SocketStorage};
diff --git a/src/iface/neighbor.rs b/src/iface/neighbor.rs
new file mode 100644
index 0000000..0c451fa
--- /dev/null
+++ b/src/iface/neighbor.rs
@@ -0,0 +1,306 @@
+// Heads up! Before working on this file you should read, at least,
+// the parts of RFC 1122 that discuss ARP.
+
+use heapless::LinearMap;
+
+use crate::config::IFACE_NEIGHBOR_CACHE_COUNT;
+use crate::time::{Duration, Instant};
+use crate::wire::{HardwareAddress, IpAddress};
+
+/// A cached neighbor.
+///
+/// A neighbor mapping translates from a protocol address to a hardware address,
+/// and contains the timestamp past which the mapping should be discarded.
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Neighbor {
+ hardware_addr: HardwareAddress,
+ expires_at: Instant,
+}
+
+/// An answer to a neighbor cache lookup.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) enum Answer {
+ /// The neighbor address is in the cache and not expired.
+ Found(HardwareAddress),
+ /// The neighbor address is not in the cache, or has expired.
+ NotFound,
+ /// The neighbor address is not in the cache, or has expired,
+ /// and a lookup has been made recently.
+ RateLimited,
+}
+
+impl Answer {
+ /// Returns whether a valid address was found.
+ pub(crate) fn found(&self) -> bool {
+ match self {
+ Answer::Found(_) => true,
+ _ => false,
+ }
+ }
+}
+
+/// A neighbor cache backed by a map.
+#[derive(Debug)]
+pub struct Cache {
+ storage: LinearMap<IpAddress, Neighbor, IFACE_NEIGHBOR_CACHE_COUNT>,
+ silent_until: Instant,
+}
+
+impl Cache {
+ /// Minimum delay between discovery requests, in milliseconds.
+ pub(crate) const SILENT_TIME: Duration = Duration::from_millis(1_000);
+
+ /// Neighbor entry lifetime, in milliseconds.
+ pub(crate) const ENTRY_LIFETIME: Duration = Duration::from_millis(60_000);
+
+ /// Create a cache.
+ pub fn new() -> Self {
+ Self {
+ storage: LinearMap::new(),
+ silent_until: Instant::from_millis(0),
+ }
+ }
+
+ pub fn fill(
+ &mut self,
+ protocol_addr: IpAddress,
+ hardware_addr: HardwareAddress,
+ timestamp: Instant,
+ ) {
+ debug_assert!(protocol_addr.is_unicast());
+ debug_assert!(hardware_addr.is_unicast());
+
+ let expires_at = timestamp + Self::ENTRY_LIFETIME;
+ self.fill_with_expiration(protocol_addr, hardware_addr, expires_at);
+ }
+
+ pub fn fill_with_expiration(
+ &mut self,
+ protocol_addr: IpAddress,
+ hardware_addr: HardwareAddress,
+ expires_at: Instant,
+ ) {
+ debug_assert!(protocol_addr.is_unicast());
+ debug_assert!(hardware_addr.is_unicast());
+
+ let neighbor = Neighbor {
+ expires_at,
+ hardware_addr,
+ };
+ match self.storage.insert(protocol_addr, neighbor) {
+ Ok(Some(old_neighbor)) => {
+ if old_neighbor.hardware_addr != hardware_addr {
+ net_trace!(
+ "replaced {} => {} (was {})",
+ protocol_addr,
+ hardware_addr,
+ old_neighbor.hardware_addr
+ );
+ }
+ }
+ Ok(None) => {
+ net_trace!("filled {} => {} (was empty)", protocol_addr, hardware_addr);
+ }
+ Err((protocol_addr, neighbor)) => {
+ // If we're going down this branch, it means the cache is full, and we need to evict an entry.
+ let old_protocol_addr = *self
+ .storage
+ .iter()
+ .min_by_key(|(_, neighbor)| neighbor.expires_at)
+ .expect("empty neighbor cache storage")
+ .0;
+
+ let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap();
+ match self.storage.insert(protocol_addr, neighbor) {
+ Ok(None) => {
+ net_trace!(
+ "filled {} => {} (evicted {} => {})",
+ protocol_addr,
+ hardware_addr,
+ old_protocol_addr,
+ _old_neighbor.hardware_addr
+ );
+ }
+ // We've covered everything else above.
+ _ => unreachable!(),
+ }
+ }
+ }
+ }
+
+ pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
+ assert!(protocol_addr.is_unicast());
+
+ if let Some(&Neighbor {
+ expires_at,
+ hardware_addr,
+ }) = self.storage.get(protocol_addr)
+ {
+ if timestamp < expires_at {
+ return Answer::Found(hardware_addr);
+ }
+ }
+
+ if timestamp < self.silent_until {
+ Answer::RateLimited
+ } else {
+ Answer::NotFound
+ }
+ }
+
+ pub(crate) fn limit_rate(&mut self, timestamp: Instant) {
+ self.silent_until = timestamp + Self::SILENT_TIME;
+ }
+
+ pub(crate) fn flush(&mut self) {
+ self.storage.clear()
+ }
+}
+
+#[cfg(feature = "medium-ethernet")]
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
+
+ use crate::wire::EthernetAddress;
+
+ const HADDR_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 1]));
+ const HADDR_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 2]));
+ const HADDR_C: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 3]));
+ const HADDR_D: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 4]));
+
+ #[test]
+ fn test_fill() {
+ let mut cache = Cache::new();
+
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
+ .found());
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
+ .found());
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::Found(HADDR_A)
+ );
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
+ .found());
+ assert!(!cache
+ .lookup(
+ &MOCK_IP_ADDR_1,
+ Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
+ )
+ .found(),);
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
+ .found());
+ }
+
+ #[test]
+ fn test_expire() {
+ let mut cache = Cache::new();
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::Found(HADDR_A)
+ );
+ assert!(!cache
+ .lookup(
+ &MOCK_IP_ADDR_1,
+ Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
+ )
+ .found(),);
+ }
+
+ #[test]
+ fn test_replace() {
+ let mut cache = Cache::new();
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::Found(HADDR_A)
+ );
+ cache.fill(MOCK_IP_ADDR_1, HADDR_B, Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::Found(HADDR_B)
+ );
+ }
+
+ #[test]
+ fn test_evict() {
+ let mut cache = Cache::new();
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(100));
+ cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
+ cache.fill(MOCK_IP_ADDR_3, HADDR_C, Instant::from_millis(200));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)),
+ Answer::Found(HADDR_B)
+ );
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000))
+ .found());
+
+ cache.fill(MOCK_IP_ADDR_4, HADDR_D, Instant::from_millis(300));
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000))
+ .found());
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)),
+ Answer::Found(HADDR_D)
+ );
+ }
+
+ #[test]
+ fn test_hush() {
+ let mut cache = Cache::new();
+
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::NotFound
+ );
+
+ cache.limit_rate(Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(100)),
+ Answer::RateLimited
+ );
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(2000)),
+ Answer::NotFound
+ );
+ }
+
+ #[test]
+ fn test_flush() {
+ let mut cache = Cache::new();
+
+ cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
+ assert_eq!(
+ cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
+ Answer::Found(HADDR_A)
+ );
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
+ .found());
+
+ cache.flush();
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
+ .found());
+ assert!(!cache
+ .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
+ .found());
+ }
+}
diff --git a/src/iface/packet.rs b/src/iface/packet.rs
new file mode 100644
index 0000000..4fdb19d
--- /dev/null
+++ b/src/iface/packet.rs
@@ -0,0 +1,234 @@
+use crate::phy::DeviceCapabilities;
+use crate::wire::*;
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[cfg(feature = "medium-ethernet")]
+pub(crate) enum EthernetPacket<'a> {
+ #[cfg(feature = "proto-ipv4")]
+ Arp(ArpRepr),
+ Ip(Packet<'a>),
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) enum Packet<'p> {
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4(PacketV4<'p>),
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6(PacketV6<'p>),
+}
+
+impl<'p> Packet<'p> {
+ pub(crate) fn new(ip_repr: IpRepr, payload: IpPayload<'p>) -> Self {
+ match ip_repr {
+ #[cfg(feature = "proto-ipv4")]
+ IpRepr::Ipv4(header) => Self::new_ipv4(header, payload),
+ #[cfg(feature = "proto-ipv6")]
+ IpRepr::Ipv6(header) => Self::new_ipv6(header, payload),
+ }
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ pub(crate) fn new_ipv4(ip_repr: Ipv4Repr, payload: IpPayload<'p>) -> Self {
+ Self::Ipv4(PacketV4 {
+ header: ip_repr,
+ payload,
+ })
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) fn new_ipv6(ip_repr: Ipv6Repr, payload: IpPayload<'p>) -> Self {
+ Self::Ipv6(PacketV6 {
+ header: ip_repr,
+ #[cfg(feature = "proto-ipv6-hbh")]
+ hop_by_hop: None,
+ #[cfg(feature = "proto-ipv6-fragmentation")]
+ fragment: None,
+ #[cfg(feature = "proto-ipv6-routing")]
+ routing: None,
+ payload,
+ })
+ }
+
+ pub(crate) fn ip_repr(&self) -> IpRepr {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Packet::Ipv4(p) => IpRepr::Ipv4(p.header),
+ #[cfg(feature = "proto-ipv6")]
+ Packet::Ipv6(p) => IpRepr::Ipv6(p.header),
+ }
+ }
+
+ pub(crate) fn payload(&self) -> &IpPayload<'p> {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Packet::Ipv4(p) => &p.payload,
+ #[cfg(feature = "proto-ipv6")]
+ Packet::Ipv6(p) => &p.payload,
+ }
+ }
+
+ pub(crate) fn emit_payload(
+ &self,
+ _ip_repr: &IpRepr,
+ payload: &mut [u8],
+ caps: &DeviceCapabilities,
+ ) {
+ match self.payload() {
+ #[cfg(feature = "proto-ipv4")]
+ IpPayload::Icmpv4(icmpv4_repr) => {
+ icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum)
+ }
+ #[cfg(feature = "proto-igmp")]
+ IpPayload::Igmp(igmp_repr) => igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload)),
+ #[cfg(feature = "proto-ipv6")]
+ IpPayload::Icmpv6(icmpv6_repr) => icmpv6_repr.emit(
+ &_ip_repr.src_addr(),
+ &_ip_repr.dst_addr(),
+ &mut Icmpv6Packet::new_unchecked(payload),
+ &caps.checksum,
+ ),
+ #[cfg(feature = "socket-raw")]
+ IpPayload::Raw(raw_packet) => payload.copy_from_slice(raw_packet),
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ IpPayload::Udp(udp_repr, inner_payload) => udp_repr.emit(
+ &mut UdpPacket::new_unchecked(payload),
+ &_ip_repr.src_addr(),
+ &_ip_repr.dst_addr(),
+ inner_payload.len(),
+ |buf| buf.copy_from_slice(inner_payload),
+ &caps.checksum,
+ ),
+ #[cfg(feature = "socket-tcp")]
+ IpPayload::Tcp(mut tcp_repr) => {
+ // This is a terrible hack to make TCP performance more acceptable on systems
+ // where the TCP buffers are significantly larger than network buffers,
+ // e.g. a 64 kB TCP receive buffer (and so, when empty, a 64k window)
+ // together with four 1500 B Ethernet receive buffers. If left untreated,
+ // this would result in our peer pushing our window and sever packet loss.
+ //
+ // I'm really not happy about this "solution" but I don't know what else to do.
+ if let Some(max_burst_size) = caps.max_burst_size {
+ let mut max_segment_size = caps.max_transmission_unit;
+ max_segment_size -= _ip_repr.header_len();
+ max_segment_size -= tcp_repr.header_len();
+
+ let max_window_size = max_burst_size * max_segment_size;
+ if tcp_repr.window_len as usize > max_window_size {
+ tcp_repr.window_len = max_window_size as u16;
+ }
+ }
+
+ tcp_repr.emit(
+ &mut TcpPacket::new_unchecked(payload),
+ &_ip_repr.src_addr(),
+ &_ip_repr.dst_addr(),
+ &caps.checksum,
+ );
+ }
+ #[cfg(feature = "socket-dhcpv4")]
+ IpPayload::Dhcpv4(udp_repr, dhcp_repr) => udp_repr.emit(
+ &mut UdpPacket::new_unchecked(payload),
+ &_ip_repr.src_addr(),
+ &_ip_repr.dst_addr(),
+ dhcp_repr.buffer_len(),
+ |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(),
+ &caps.checksum,
+ ),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[cfg(feature = "proto-ipv4")]
+pub(crate) struct PacketV4<'p> {
+ header: Ipv4Repr,
+ payload: IpPayload<'p>,
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[cfg(feature = "proto-ipv6")]
+pub(crate) struct PacketV6<'p> {
+ pub(crate) header: Ipv6Repr,
+ #[cfg(feature = "proto-ipv6-hbh")]
+ pub(crate) hop_by_hop: Option<Ipv6HopByHopRepr<'p>>,
+ #[cfg(feature = "proto-ipv6-fragmentation")]
+ pub(crate) fragment: Option<Ipv6FragmentRepr>,
+ #[cfg(feature = "proto-ipv6-routing")]
+ pub(crate) routing: Option<Ipv6RoutingRepr<'p>>,
+ pub(crate) payload: IpPayload<'p>,
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) enum IpPayload<'p> {
+ #[cfg(feature = "proto-ipv4")]
+ Icmpv4(Icmpv4Repr<'p>),
+ #[cfg(feature = "proto-igmp")]
+ Igmp(IgmpRepr),
+ #[cfg(feature = "proto-ipv6")]
+ Icmpv6(Icmpv6Repr<'p>),
+ #[cfg(feature = "socket-raw")]
+ Raw(&'p [u8]),
+ #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
+ Udp(UdpRepr, &'p [u8]),
+ #[cfg(feature = "socket-tcp")]
+ Tcp(TcpRepr<'p>),
+ #[cfg(feature = "socket-dhcpv4")]
+ Dhcpv4(UdpRepr, DhcpRepr<'p>),
+}
+
+impl<'p> IpPayload<'p> {
+ #[cfg(feature = "proto-sixlowpan")]
+ pub(crate) fn as_sixlowpan_next_header(&self) -> SixlowpanNextHeader {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Self::Icmpv4(_) => unreachable!(),
+ #[cfg(feature = "socket-dhcpv4")]
+ Self::Dhcpv4(..) => unreachable!(),
+ #[cfg(feature = "proto-ipv6")]
+ Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6),
+ #[cfg(feature = "proto-igmp")]
+ Self::Igmp(_) => unreachable!(),
+ #[cfg(feature = "socket-tcp")]
+ Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp),
+ #[cfg(feature = "socket-udp")]
+ Self::Udp(..) => SixlowpanNextHeader::Compressed,
+ #[cfg(feature = "socket-raw")]
+ Self::Raw(_) => todo!(),
+ }
+ }
+}
+
+#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
+pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
+ // Send back as much of the original payload as will fit within
+ // the minimum MTU required by IPv4. See RFC 1812 § 4.3.2.3 for
+ // more details.
+ //
+ // Since the entire network layer packet must fit within the minimum
+ // MTU supported, the payload must not exceed the following:
+ //
+ // <min mtu> - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size
+ len.min(mtu - header_len * 2 - 8)
+}
+
+#[cfg(feature = "proto-igmp")]
+pub(crate) enum IgmpReportState {
+ Inactive,
+ ToGeneralQuery {
+ version: IgmpVersion,
+ timeout: crate::time::Instant,
+ interval: crate::time::Duration,
+ next_index: usize,
+ },
+ ToSpecificQuery {
+ version: IgmpVersion,
+ timeout: crate::time::Instant,
+ group: Ipv4Address,
+ },
+}
diff --git a/src/iface/route.rs b/src/iface/route.rs
new file mode 100644
index 0000000..123c695
--- /dev/null
+++ b/src/iface/route.rs
@@ -0,0 +1,327 @@
+use heapless::Vec;
+
+use crate::config::IFACE_MAX_ROUTE_COUNT;
+use crate::time::Instant;
+use crate::wire::{IpAddress, IpCidr};
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::{Ipv4Address, Ipv4Cidr};
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::{Ipv6Address, Ipv6Cidr};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct RouteTableFull;
+
+impl core::fmt::Display for RouteTableFull {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "Route table full")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RouteTableFull {}
+
+/// A prefix of addresses that should be routed via a router
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Route {
+ pub cidr: IpCidr,
+ pub via_router: IpAddress,
+ /// `None` means "forever".
+ pub preferred_until: Option<Instant>,
+ /// `None` means "forever".
+ pub expires_at: Option<Instant>,
+}
+
+#[cfg(feature = "proto-ipv4")]
+const IPV4_DEFAULT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(0, 0, 0, 0), 0));
+#[cfg(feature = "proto-ipv6")]
+const IPV6_DEFAULT: IpCidr =
+ IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0), 0));
+
+impl Route {
+ /// Returns a route to 0.0.0.0/0 via the `gateway`, with no expiry.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
+ Route {
+ cidr: IPV4_DEFAULT,
+ via_router: gateway.into(),
+ preferred_until: None,
+ expires_at: None,
+ }
+ }
+
+ /// Returns a route to ::/0 via the `gateway`, with no expiry.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
+ Route {
+ cidr: IPV6_DEFAULT,
+ via_router: gateway.into(),
+ preferred_until: None,
+ expires_at: None,
+ }
+ }
+}
+
+/// A routing table.
+#[derive(Debug)]
+pub struct Routes {
+ storage: Vec<Route, IFACE_MAX_ROUTE_COUNT>,
+}
+
+impl Routes {
+ /// Creates a new empty routing table.
+ pub fn new() -> Self {
+ Self {
+ storage: Vec::new(),
+ }
+ }
+
+ /// Update the routes of this node.
+ pub fn update<F: FnOnce(&mut Vec<Route, IFACE_MAX_ROUTE_COUNT>)>(&mut self, f: F) {
+ f(&mut self.storage);
+ }
+
+ /// Add a default ipv4 gateway (ie. "ip route add 0.0.0.0/0 via `gateway`").
+ ///
+ /// On success, returns the previous default route, if any.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn add_default_ipv4_route(
+ &mut self,
+ gateway: Ipv4Address,
+ ) -> Result<Option<Route>, RouteTableFull> {
+ let old = self.remove_default_ipv4_route();
+ self.storage
+ .push(Route::new_ipv4_gateway(gateway))
+ .map_err(|_| RouteTableFull)?;
+ Ok(old)
+ }
+
+ /// Add a default ipv6 gateway (ie. "ip -6 route add ::/0 via `gateway`").
+ ///
+ /// On success, returns the previous default route, if any.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn add_default_ipv6_route(
+ &mut self,
+ gateway: Ipv6Address,
+ ) -> Result<Option<Route>, RouteTableFull> {
+ let old = self.remove_default_ipv6_route();
+ self.storage
+ .push(Route::new_ipv6_gateway(gateway))
+ .map_err(|_| RouteTableFull)?;
+ Ok(old)
+ }
+
+ /// Remove the default ipv4 gateway
+ ///
+ /// On success, returns the previous default route, if any.
+ #[cfg(feature = "proto-ipv4")]
+ pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
+ if let Some((i, _)) = self
+ .storage
+ .iter()
+ .enumerate()
+ .find(|(_, r)| r.cidr == IPV4_DEFAULT)
+ {
+ Some(self.storage.remove(i))
+ } else {
+ None
+ }
+ }
+
+ /// Remove the default ipv6 gateway
+ ///
+ /// On success, returns the previous default route, if any.
+ #[cfg(feature = "proto-ipv6")]
+ pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
+ if let Some((i, _)) = self
+ .storage
+ .iter()
+ .enumerate()
+ .find(|(_, r)| r.cidr == IPV6_DEFAULT)
+ {
+ Some(self.storage.remove(i))
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
+ assert!(addr.is_unicast());
+
+ self.storage
+ .iter()
+ // Keep only matching routes
+ .filter(|route| {
+ if let Some(expires_at) = route.expires_at {
+ if timestamp > expires_at {
+ return false;
+ }
+ }
+ route.cidr.contains_addr(addr)
+ })
+ // pick the most specific one (highest prefix_len)
+ .max_by_key(|route| route.cidr.prefix_len())
+ .map(|route| route.via_router)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[cfg(feature = "proto-ipv6")]
+ mod mock {
+ use super::super::*;
+ pub const ADDR_1A: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
+ pub const ADDR_1B: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
+ pub const ADDR_1C: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
+ pub fn cidr_1() -> Ipv6Cidr {
+ Ipv6Cidr::new(
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]),
+ 64,
+ )
+ }
+
+ pub const ADDR_2A: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
+ pub const ADDR_2B: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
+ pub fn cidr_2() -> Ipv6Cidr {
+ Ipv6Cidr::new(
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]),
+ 64,
+ )
+ }
+ }
+
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ mod mock {
+ use super::super::*;
+ pub const ADDR_1A: Ipv4Address = Ipv4Address([192, 0, 2, 1]);
+ pub const ADDR_1B: Ipv4Address = Ipv4Address([192, 0, 2, 13]);
+ pub const ADDR_1C: Ipv4Address = Ipv4Address([192, 0, 2, 42]);
+ pub fn cidr_1() -> Ipv4Cidr {
+ Ipv4Cidr::new(Ipv4Address([192, 0, 2, 0]), 24)
+ }
+
+ pub const ADDR_2A: Ipv4Address = Ipv4Address([198, 51, 100, 1]);
+ pub const ADDR_2B: Ipv4Address = Ipv4Address([198, 51, 100, 21]);
+ pub fn cidr_2() -> Ipv4Cidr {
+ Ipv4Cidr::new(Ipv4Address([198, 51, 100, 0]), 24)
+ }
+ }
+
+ use self::mock::*;
+
+ #[test]
+ fn test_fill() {
+ let mut routes = Routes::new();
+
+ assert_eq!(
+ routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
+ None
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
+ None
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
+ None
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
+ None
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
+ None
+ );
+
+ let route = Route {
+ cidr: cidr_1().into(),
+ via_router: ADDR_1A.into(),
+ preferred_until: None,
+ expires_at: None,
+ };
+ routes.update(|storage| {
+ storage.push(route).unwrap();
+ });
+
+ assert_eq!(
+ routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
+ None
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
+ None
+ );
+
+ let route2 = Route {
+ cidr: cidr_2().into(),
+ via_router: ADDR_2A.into(),
+ preferred_until: Some(Instant::from_millis(10)),
+ expires_at: Some(Instant::from_millis(10)),
+ };
+ routes.update(|storage| {
+ storage.push(route2).unwrap();
+ });
+
+ assert_eq!(
+ routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
+ Some(ADDR_2A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
+ Some(ADDR_2A.into())
+ );
+
+ assert_eq!(
+ routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)),
+ Some(ADDR_1A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)),
+ Some(ADDR_2A.into())
+ );
+ assert_eq!(
+ routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)),
+ Some(ADDR_2A.into())
+ );
+ }
+}
diff --git a/src/iface/rpl/consts.rs b/src/iface/rpl/consts.rs
new file mode 100644
index 0000000..70a6613
--- /dev/null
+++ b/src/iface/rpl/consts.rs
@@ -0,0 +1,8 @@
+pub const SEQUENCE_WINDOW: u8 = 16;
+
+pub const DEFAULT_MIN_HOP_RANK_INCREASE: u16 = 256;
+
+pub const DEFAULT_DIO_INTERVAL_MIN: u32 = 12;
+pub const DEFAULT_DIO_REDUNDANCY_CONSTANT: usize = 10;
+/// This is 20 in the standard, but in Contiki they use:
+pub const DEFAULT_DIO_INTERVAL_DOUBLINGS: u32 = 8;
diff --git a/src/iface/rpl/lollipop.rs b/src/iface/rpl/lollipop.rs
new file mode 100644
index 0000000..4785c77
--- /dev/null
+++ b/src/iface/rpl/lollipop.rs
@@ -0,0 +1,189 @@
+//! Implementation of sequence counters defined in [RFC 6550 § 7.2]. Values from 128 and greater
+//! are used as a linear sequence to indicate a restart and bootstrap the counter. Values less than
+//! or equal to 127 are used as a circular sequence number space of size 128. When operating in the
+//! circular region, if sequence numbers are detected to be too far apart, then they are not
+//! comparable.
+//!
+//! [RFC 6550 § 7.2]: https://datatracker.ietf.org/doc/html/rfc6550#section-7.2
+
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SequenceCounter(u8);
+
+impl Default for SequenceCounter {
+ fn default() -> Self {
+ // RFC6550 7.2 recommends 240 (256 - SEQUENCE_WINDOW) as the initialization value of the
+ // counter.
+ Self(240)
+ }
+}
+
+impl SequenceCounter {
+ /// Create a new sequence counter.
+ ///
+ /// Use `Self::default()` when a new sequence counter needs to be created with a value that is
+ /// recommended in RFC6550 7.2, being 240.
+ pub fn new(value: u8) -> Self {
+ Self(value)
+ }
+
+ /// Return the value of the sequence counter.
+ pub fn value(&self) -> u8 {
+ self.0
+ }
+
+ /// Increment the sequence counter.
+ ///
+ /// When the sequence counter is greater than or equal to 128, the maximum value is 255.
+ /// When the sequence counter is less than 128, the maximum value is 127.
+ ///
+ /// When an increment of the sequence counter would cause the counter to increment beyond its
+ /// maximum value, the counter MUST wrap back to zero.
+ pub fn increment(&mut self) {
+ let max = if self.0 >= 128 { 255 } else { 127 };
+
+ self.0 = match self.0.checked_add(1) {
+ Some(val) if val <= max => val,
+ _ => 0,
+ };
+ }
+}
+
+impl PartialEq for SequenceCounter {
+ fn eq(&self, other: &Self) -> bool {
+ let a = self.value() as usize;
+ let b = other.value() as usize;
+
+ if ((128..=255).contains(&a) && (0..=127).contains(&b))
+ || ((128..=255).contains(&b) && (0..=127).contains(&a))
+ {
+ false
+ } else {
+ let result = if a > b { a - b } else { b - a };
+
+ if result <= super::consts::SEQUENCE_WINDOW as usize {
+ // RFC1982
+ a == b
+ } else {
+ // This case is actually not comparable.
+ false
+ }
+ }
+ }
+}
+
+impl PartialOrd for SequenceCounter {
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ use super::consts::SEQUENCE_WINDOW;
+ use core::cmp::Ordering;
+
+ let a = self.value() as usize;
+ let b = other.value() as usize;
+
+ if (128..256).contains(&a) && (0..128).contains(&b) {
+ if 256 + b - a <= SEQUENCE_WINDOW as usize {
+ Some(Ordering::Less)
+ } else {
+ Some(Ordering::Greater)
+ }
+ } else if (128..256).contains(&b) && (0..128).contains(&a) {
+ if 256 + a - b <= SEQUENCE_WINDOW as usize {
+ Some(Ordering::Greater)
+ } else {
+ Some(Ordering::Less)
+ }
+ } else if ((0..128).contains(&a) && (0..128).contains(&b))
+ || ((128..256).contains(&a) && (128..256).contains(&b))
+ {
+ let result = if a > b { a - b } else { b - a };
+
+ if result <= SEQUENCE_WINDOW as usize {
+ // RFC1982
+ a.partial_cmp(&b)
+ } else {
+ // This case is not comparable.
+ None
+ }
+ } else {
+ unreachable!();
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn sequence_counter_increment() {
+ let mut seq = SequenceCounter::new(253);
+ seq.increment();
+ assert_eq!(seq.value(), 254);
+ seq.increment();
+ assert_eq!(seq.value(), 255);
+ seq.increment();
+ assert_eq!(seq.value(), 0);
+
+ let mut seq = SequenceCounter::new(126);
+ seq.increment();
+ assert_eq!(seq.value(), 127);
+ seq.increment();
+ assert_eq!(seq.value(), 0);
+ }
+
+ #[test]
+ fn sequence_counter_comparison() {
+ use core::cmp::Ordering;
+
+ assert!(SequenceCounter::new(240) != SequenceCounter::new(1));
+ assert!(SequenceCounter::new(1) != SequenceCounter::new(240));
+ assert!(SequenceCounter::new(1) != SequenceCounter::new(240));
+ assert!(SequenceCounter::new(240) == SequenceCounter::new(240));
+ assert!(SequenceCounter::new(240 - 17) != SequenceCounter::new(240));
+
+ assert_eq!(
+ SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(5)),
+ Some(Ordering::Greater)
+ );
+ assert_eq!(
+ SequenceCounter::new(250).partial_cmp(&SequenceCounter::new(5)),
+ Some(Ordering::Less)
+ );
+ assert_eq!(
+ SequenceCounter::new(5).partial_cmp(&SequenceCounter::new(250)),
+ Some(Ordering::Greater)
+ );
+ assert_eq!(
+ SequenceCounter::new(127).partial_cmp(&SequenceCounter::new(129)),
+ Some(Ordering::Less)
+ );
+ assert_eq!(
+ SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(121)),
+ Some(Ordering::Less)
+ );
+ assert_eq!(
+ SequenceCounter::new(121).partial_cmp(&SequenceCounter::new(120)),
+ Some(Ordering::Greater)
+ );
+ assert_eq!(
+ SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(241)),
+ Some(Ordering::Less)
+ );
+ assert_eq!(
+ SequenceCounter::new(241).partial_cmp(&SequenceCounter::new(240)),
+ Some(Ordering::Greater)
+ );
+ assert_eq!(
+ SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(120)),
+ Some(Ordering::Equal)
+ );
+ assert_eq!(
+ SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(240)),
+ Some(Ordering::Equal)
+ );
+ assert_eq!(
+ SequenceCounter::new(130).partial_cmp(&SequenceCounter::new(241)),
+ None
+ );
+ }
+}
diff --git a/src/iface/rpl/mod.rs b/src/iface/rpl/mod.rs
new file mode 100644
index 0000000..69aa9ae
--- /dev/null
+++ b/src/iface/rpl/mod.rs
@@ -0,0 +1,9 @@
+#![allow(unused)]
+
+mod consts;
+mod lollipop;
+mod of0;
+mod parents;
+mod rank;
+mod relations;
+mod trickle;
diff --git a/src/iface/rpl/of0.rs b/src/iface/rpl/of0.rs
new file mode 100644
index 0000000..99e4d1f
--- /dev/null
+++ b/src/iface/rpl/of0.rs
@@ -0,0 +1,129 @@
+use super::parents::*;
+use super::rank::Rank;
+
+pub struct ObjectiveFunction0;
+
+pub(crate) trait ObjectiveFunction {
+ const OCP: u16;
+
+ /// Return the new calculated Rank, based on information from the parent.
+ fn rank(current_rank: Rank, parent_rank: Rank) -> Rank;
+
+ /// Return the preferred parent from a given parent set.
+ fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent>;
+}
+
+impl ObjectiveFunction0 {
+ const OCP: u16 = 0;
+
+ const RANK_STRETCH: u16 = 0;
+ const RANK_FACTOR: u16 = 1;
+ const RANK_STEP: u16 = 3;
+
+ fn rank_increase(parent_rank: Rank) -> u16 {
+ (Self::RANK_FACTOR * Self::RANK_STEP + Self::RANK_STRETCH)
+ * parent_rank.min_hop_rank_increase
+ }
+}
+
+impl ObjectiveFunction for ObjectiveFunction0 {
+ const OCP: u16 = 0;
+
+ fn rank(_: Rank, parent_rank: Rank) -> Rank {
+ assert_ne!(parent_rank, Rank::INFINITE);
+
+ Rank::new(
+ parent_rank.value + Self::rank_increase(parent_rank),
+ parent_rank.min_hop_rank_increase,
+ )
+ }
+
+ fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent> {
+ let mut pref_parent: Option<&Parent> = None;
+
+ for (_, parent) in parent_set.parents() {
+ if pref_parent.is_none() || parent.rank() < pref_parent.unwrap().rank() {
+ pref_parent = Some(parent);
+ }
+ }
+
+ pref_parent
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::iface::rpl::consts::DEFAULT_MIN_HOP_RANK_INCREASE;
+
+ use super::*;
+
+ #[test]
+ fn rank_increase() {
+ // 256 (root) + 3 * 256
+ assert_eq!(
+ ObjectiveFunction0::rank(Rank::INFINITE, Rank::ROOT),
+ Rank::new(256 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE)
+ );
+
+ // 1024 + 3 * 256
+ assert_eq!(
+ ObjectiveFunction0::rank(
+ Rank::INFINITE,
+ Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE)
+ ),
+ Rank::new(1024 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE)
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn rank_increase_infinite() {
+ assert_eq!(
+ ObjectiveFunction0::rank(Rank::INFINITE, Rank::INFINITE),
+ Rank::INFINITE
+ );
+ }
+
+ #[test]
+ fn empty_set() {
+ assert_eq!(
+ ObjectiveFunction0::preferred_parent(&ParentSet::default()),
+ None
+ );
+ }
+
+ #[test]
+ fn non_empty_set() {
+ use crate::wire::Ipv6Address;
+
+ let mut parents = ParentSet::default();
+
+ parents.add(
+ Ipv6Address::default(),
+ Parent::new(0, Rank::ROOT, Default::default(), Ipv6Address::default()),
+ );
+
+ let mut address = Ipv6Address::default();
+ address.0[15] = 1;
+
+ parents.add(
+ address,
+ Parent::new(
+ 0,
+ Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ Ipv6Address::default(),
+ ),
+ );
+
+ assert_eq!(
+ ObjectiveFunction0::preferred_parent(&parents),
+ Some(&Parent::new(
+ 0,
+ Rank::ROOT,
+ Default::default(),
+ Ipv6Address::default(),
+ ))
+ );
+ }
+}
diff --git a/src/iface/rpl/parents.rs b/src/iface/rpl/parents.rs
new file mode 100644
index 0000000..70d5a5e
--- /dev/null
+++ b/src/iface/rpl/parents.rs
@@ -0,0 +1,176 @@
+use crate::wire::Ipv6Address;
+
+use super::{lollipop::SequenceCounter, rank::Rank};
+use crate::config::RPL_PARENTS_BUFFER_COUNT;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub(crate) struct Parent {
+ rank: Rank,
+ preference: u8,
+ version_number: SequenceCounter,
+ dodag_id: Ipv6Address,
+}
+
+impl Parent {
+ /// Create a new parent.
+ pub(crate) fn new(
+ preference: u8,
+ rank: Rank,
+ version_number: SequenceCounter,
+ dodag_id: Ipv6Address,
+ ) -> Self {
+ Self {
+ rank,
+ preference,
+ version_number,
+ dodag_id,
+ }
+ }
+
+ /// Return the Rank of the parent.
+ pub(crate) fn rank(&self) -> &Rank {
+ &self.rank
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct ParentSet {
+ parents: heapless::LinearMap<Ipv6Address, Parent, { RPL_PARENTS_BUFFER_COUNT }>,
+}
+
+impl ParentSet {
+ /// Add a new parent to the parent set. The Rank of the new parent should be lower than the
+ /// Rank of the node that holds this parent set.
+ pub(crate) fn add(&mut self, address: Ipv6Address, parent: Parent) {
+ if let Some(p) = self.parents.get_mut(&address) {
+ *p = parent;
+ } else if let Err(p) = self.parents.insert(address, parent) {
+ if let Some((w_a, w_p)) = self.worst_parent() {
+ if w_p.rank.dag_rank() > parent.rank.dag_rank() {
+ self.parents.remove(&w_a.clone()).unwrap();
+ self.parents.insert(address, parent).unwrap();
+ } else {
+ net_debug!("could not add {} to parent set, buffer is full", address);
+ }
+ } else {
+ unreachable!()
+ }
+ }
+ }
+
+ /// Find a parent based on its address.
+ pub(crate) fn find(&self, address: &Ipv6Address) -> Option<&Parent> {
+ self.parents.get(address)
+ }
+
+ /// Find a mutable parent based on its address.
+ pub(crate) fn find_mut(&mut self, address: &Ipv6Address) -> Option<&mut Parent> {
+ self.parents.get_mut(address)
+ }
+
+ /// Return a slice to the parent set.
+ pub(crate) fn parents(&self) -> impl Iterator<Item = (&Ipv6Address, &Parent)> {
+ self.parents.iter()
+ }
+
+ /// Find the worst parent that is currently in the parent set.
+ fn worst_parent(&self) -> Option<(&Ipv6Address, &Parent)> {
+ self.parents.iter().max_by_key(|(k, v)| v.rank.dag_rank())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn add_parent() {
+ let mut set = ParentSet::default();
+ set.add(
+ Default::default(),
+ Parent::new(0, Rank::ROOT, Default::default(), Default::default()),
+ );
+
+ assert_eq!(
+ set.find(&Default::default()),
+ Some(&Parent::new(
+ 0,
+ Rank::ROOT,
+ Default::default(),
+ Default::default()
+ ))
+ );
+ }
+
+ #[test]
+ fn add_more_parents() {
+ use super::super::consts::DEFAULT_MIN_HOP_RANK_INCREASE;
+ let mut set = ParentSet::default();
+
+ let mut last_address = Default::default();
+ for i in 0..RPL_PARENTS_BUFFER_COUNT {
+ let i = i as u16;
+ let mut address = Ipv6Address::default();
+ address.0[15] = i as u8;
+ last_address = address;
+
+ set.add(
+ address,
+ Parent::new(
+ 0,
+ Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ address,
+ ),
+ );
+
+ assert_eq!(
+ set.find(&address),
+ Some(&Parent::new(
+ 0,
+ Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ address,
+ ))
+ );
+ }
+
+ // This one is not added to the set, because its Rank is worse than any other parent in the
+ // set.
+ let mut address = Ipv6Address::default();
+ address.0[15] = 8;
+ set.add(
+ address,
+ Parent::new(
+ 0,
+ Rank::new(256 * 8, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ address,
+ ),
+ );
+ assert_eq!(set.find(&address), None);
+
+ /// This Parent has a better rank than the last one in the set.
+ let mut address = Ipv6Address::default();
+ address.0[15] = 9;
+ set.add(
+ address,
+ Parent::new(
+ 0,
+ Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ address,
+ ),
+ );
+ assert_eq!(
+ set.find(&address),
+ Some(&Parent::new(
+ 0,
+ Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE),
+ Default::default(),
+ address
+ ))
+ );
+ assert_eq!(set.find(&last_address), None);
+ }
+}
diff --git a/src/iface/rpl/rank.rs b/src/iface/rpl/rank.rs
new file mode 100644
index 0000000..02a5ecf
--- /dev/null
+++ b/src/iface/rpl/rank.rs
@@ -0,0 +1,104 @@
+//! Implementation of the Rank comparison in RPL.
+//!
+//! A Rank can be thought of as a fixed-point number, where the position of the radix point between
+//! the integer part and the fractional part is determined by `MinHopRankIncrease`.
+//! `MinHopRankIncrease` is the minimum increase in Rank between a node and any of its DODAG
+//! parents.
+//! This value is provisined by the DODAG root.
+//!
+//! When Rank is compared, the integer portion of the Rank is to be used.
+//!
+//! Meaning of the comparison:
+//! - **Rank M is less than Rank N**: the position of M is closer to the DODAG root than the position
+//! of N. Node M may safely be a DODAG parent for node N.
+//! - **Ranks are equal**: the positions of both nodes within the DODAG and with respect to the DODAG
+//! are similar or identical. Routing through a node with equal Rank may cause a routing loop.
+//! - **Rank M is greater than Rank N**: the position of node M is farther from the DODAG root
+//! than the position of N. Node M may in fact be in the sub-DODAG of node N. If node N selects
+//! node M as a DODAG parent, there is a risk of creating a loop.
+
+use super::consts::DEFAULT_MIN_HOP_RANK_INCREASE;
+
+/// The Rank is the expression of the relative position within a DODAG Version with regard to
+/// neighbors, and it is not necessarily a good indication or a proper expression of a distance or
+/// a path cost to the root.
+#[derive(Debug, Clone, Copy, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Rank {
+ pub(super) value: u16,
+ pub(super) min_hop_rank_increase: u16,
+}
+
+impl core::fmt::Display for Rank {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "Rank({})", self.dag_rank())
+ }
+}
+
+impl Rank {
+ pub const INFINITE: Self = Rank::new(0xffff, DEFAULT_MIN_HOP_RANK_INCREASE);
+
+ /// The ROOT_RANK is the smallest rank possible.
+ /// DAG_RANK(ROOT_RANK) should be 1. See RFC6550 § 17.
+ pub const ROOT: Self = Rank::new(DEFAULT_MIN_HOP_RANK_INCREASE, DEFAULT_MIN_HOP_RANK_INCREASE);
+
+ /// Create a new Rank from some value and a `MinHopRankIncrease`.
+ /// The `MinHopRankIncrease` is used for calculating the integer part for comparing to other
+ /// Ranks.
+ pub const fn new(value: u16, min_hop_rank_increase: u16) -> Self {
+ assert!(min_hop_rank_increase > 0);
+
+ Self {
+ value,
+ min_hop_rank_increase,
+ }
+ }
+
+ /// Return the integer part of the Rank.
+ pub fn dag_rank(&self) -> u16 {
+ self.value / self.min_hop_rank_increase
+ }
+
+ /// Return the raw Rank value.
+ pub fn raw_value(&self) -> u16 {
+ self.value
+ }
+}
+
+impl PartialEq for Rank {
+ fn eq(&self, other: &Self) -> bool {
+ self.dag_rank() == other.dag_rank()
+ }
+}
+
+impl PartialOrd for Rank {
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ self.dag_rank().partial_cmp(&other.dag_rank())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn calculate_rank() {
+ let r = Rank::new(27, 16);
+ assert_eq!(r.dag_rank(), 1)
+ }
+
+ #[test]
+ fn comparison() {
+ let r1 = Rank::ROOT;
+ let r2 = Rank::new(16, 16);
+ assert!(r1 == r2);
+
+ let r1 = Rank::new(16, 16);
+ let r2 = Rank::new(32, 16);
+ assert!(r1 < r2);
+
+ let r1 = Rank::ROOT;
+ let r2 = Rank::INFINITE;
+ assert!(r1 < r2);
+ }
+}
diff --git a/src/iface/rpl/relations.rs b/src/iface/rpl/relations.rs
new file mode 100644
index 0000000..da02a3c
--- /dev/null
+++ b/src/iface/rpl/relations.rs
@@ -0,0 +1,162 @@
+use crate::time::Instant;
+use crate::wire::Ipv6Address;
+
+use crate::config::RPL_RELATIONS_BUFFER_COUNT;
+
+#[derive(Debug)]
+pub struct Relation {
+ destination: Ipv6Address,
+ next_hop: Ipv6Address,
+ expiration: Instant,
+}
+
+#[derive(Default, Debug)]
+pub struct Relations {
+ relations: heapless::Vec<Relation, { RPL_RELATIONS_BUFFER_COUNT }>,
+}
+
+impl Relations {
+ /// Add a new relation to the buffer. If there was already a relation in the buffer, then
+ /// update it.
+ pub fn add_relation(
+ &mut self,
+ destination: Ipv6Address,
+ next_hop: Ipv6Address,
+ expiration: Instant,
+ ) {
+ if let Some(r) = self
+ .relations
+ .iter_mut()
+ .find(|r| r.destination == destination)
+ {
+ r.next_hop = next_hop;
+ r.expiration = expiration;
+ } else {
+ let relation = Relation {
+ destination,
+ next_hop,
+ expiration,
+ };
+
+ if let Err(e) = self.relations.push(relation) {
+ net_debug!("Unable to add relation, buffer is full");
+ }
+ }
+ }
+
+ /// Remove all relation entries for a specific destination.
+ pub fn remove_relation(&mut self, destination: Ipv6Address) {
+ self.relations.retain(|r| r.destination != destination)
+ }
+
+ /// Return the next hop for a specific IPv6 address, if there is one.
+ pub fn find_next_hop(&mut self, destination: Ipv6Address) -> Option<Ipv6Address> {
+ self.relations.iter().find_map(|r| {
+ if r.destination == destination {
+ Some(r.next_hop)
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Purge expired relations.
+ pub fn purge(&mut self, now: Instant) {
+ self.relations.retain(|r| r.expiration > now)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::time::Duration;
+
+ fn addresses(count: usize) -> Vec<Ipv6Address> {
+ (0..count)
+ .map(|i| {
+ let mut ip = Ipv6Address::default();
+ ip.0[0] = i as u8;
+ ip
+ })
+ .collect()
+ }
+
+ #[test]
+ fn add_relation() {
+ let addrs = addresses(2);
+
+ let mut relations = Relations::default();
+ relations.add_relation(addrs[0], addrs[1], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+ }
+
+ #[test]
+ fn add_relations_full_buffer() {
+ let addrs = addresses(crate::config::RPL_RELATIONS_BUFFER_COUNT + 1);
+
+ // Try to add RPL_RELATIONS_BUFFER_COUNT + 1 to the buffer.
+ // The size of the buffer should still be RPL_RELATIONS_BUFFER_COUNT.
+ let mut relations = Relations::default();
+ for a in addrs {
+ relations.add_relation(a, a, Instant::now());
+ }
+
+ assert_eq!(relations.relations.len(), RPL_RELATIONS_BUFFER_COUNT);
+ }
+
+ #[test]
+ fn update_relation() {
+ let addrs = addresses(3);
+
+ let mut relations = Relations::default();
+ relations.add_relation(addrs[0], addrs[1], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+
+ relations.add_relation(addrs[0], addrs[2], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+
+ assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2]));
+ }
+
+ #[test]
+ fn find_next_hop() {
+ let addrs = addresses(3);
+
+ let mut relations = Relations::default();
+ relations.add_relation(addrs[0], addrs[1], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+ assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[1]));
+
+ relations.add_relation(addrs[0], addrs[2], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+ assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2]));
+
+ // Find the next hop of a destination not in the buffer.
+ assert_eq!(relations.find_next_hop(addrs[1]), None);
+ }
+
+ #[test]
+ fn remove_relation() {
+ let addrs = addresses(2);
+
+ let mut relations = Relations::default();
+ relations.add_relation(addrs[0], addrs[1], Instant::now());
+ assert_eq!(relations.relations.len(), 1);
+
+ relations.remove_relation(addrs[0]);
+ assert!(relations.relations.is_empty());
+ }
+
+ #[test]
+ fn purge_relation() {
+ let addrs = addresses(2);
+
+ let mut relations = Relations::default();
+ relations.add_relation(addrs[0], addrs[1], Instant::now() - Duration::from_secs(1));
+
+ assert_eq!(relations.relations.len(), 1);
+
+ relations.purge(Instant::now());
+ assert!(relations.relations.is_empty());
+ }
+}
diff --git a/src/iface/rpl/trickle.rs b/src/iface/rpl/trickle.rs
new file mode 100644
index 0000000..a5b3b97
--- /dev/null
+++ b/src/iface/rpl/trickle.rs
@@ -0,0 +1,266 @@
+//! Implementation of the Trickle timer defined in [RFC 6206]. The algorithm allows node in a lossy
+//! shared medium to exchange information in a highly robust, energy efficient, simple, and
+//! scalable manner. Dynamically adjusting transmission windows allows Trickle to spread new
+//! information fast while sending only a few messages per hour when information does not change.
+//!
+//! **NOTE**: the constants used for the default Trickle timer are the ones from the [Enhanced
+//! Trickle].
+//!
+//! [RFC 6206]: https://datatracker.ietf.org/doc/html/rfc6206
+//! [Enhanced Trickle]: https://d1wqtxts1xzle7.cloudfront.net/71402623/E-Trickle_Enhanced_Trickle_Algorithm_for20211005-2078-1ckh34a.pdf?1633439582=&response-content-disposition=inline%3B+filename%3DE_Trickle_Enhanced_Trickle_Algorithm_for.pdf&Expires=1681472005&Signature=cC7l-Pyr5r64XBNCDeSJ2ha6oqWUtO6A-KlDOyC0UVaHxDV3h3FuVHRtcNp3O9BUfRK8jeuWCYGBkCZgQT4Zgb6XwgVB-3z4TF9o3qBRMteRyYO5vjVkpPBeN7mz4Tl746SsSCHDm2NMtr7UVtLYamriU3D0rryoqLqJXmnkNoJpn~~wJe2H5PmPgIwixTwSvDkfFLSVoESaYS9ZWHZwbW-7G7OxIw8oSYhx9xMBnzkpdmT7sJNmvDzTUhoOjYrHTRM23cLVS9~oOSpT7hKtKD4h5CSmrNK4st07KnT9~tUqEcvGO3aXdd4quRZeKUcCkCbTLvhOEYg9~QqgD8xwhA__&Key-Pair-Id=APKAJLOHF5GGSLRBV4ZA
+
+use crate::{
+ rand::Rand,
+ time::{Duration, Instant},
+};
+
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) struct TrickleTimer {
+ i_min: u32,
+ i_max: u32,
+ k: usize,
+
+ i: Duration,
+ t: Duration,
+ t_exp: Instant,
+ i_exp: Instant,
+ counter: usize,
+}
+
+impl TrickleTimer {
+ /// Creat a new Trickle timer using the default values.
+ ///
+ /// **NOTE**: the standard defines I as a random value between [Imin, Imax]. However, this
+ /// could result in a t value that is very close to Imax. Therefore, sending DIO messages will
+ /// be sporadic, which is not ideal when a network is started. It might take a long time before
+ /// the network is actually stable. Therefore, we don't draw a random numberm but just use Imin
+ /// for I. This only affects the start of the RPL tree and speeds up building it. Also, we
+ /// don't use the default values from the standard, but the values from the _Enhanced Trickle
+ /// Algorithm for Low-Power and Lossy Networks_ from Baraq Ghaleb et al. This is also what the
+ /// Contiki Trickle timer does.
+ pub(crate) fn default(now: Instant, rand: &mut Rand) -> Self {
+ use super::consts::{
+ DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_INTERVAL_MIN,
+ DEFAULT_DIO_REDUNDANCY_CONSTANT,
+ };
+
+ Self::new(
+ DEFAULT_DIO_INTERVAL_MIN,
+ DEFAULT_DIO_INTERVAL_MIN + DEFAULT_DIO_INTERVAL_DOUBLINGS,
+ DEFAULT_DIO_REDUNDANCY_CONSTANT,
+ now,
+ rand,
+ )
+ }
+
+ /// Create a new Trickle timer.
+ pub(crate) fn new(i_min: u32, i_max: u32, k: usize, now: Instant, rand: &mut Rand) -> Self {
+ let mut timer = Self {
+ i_min,
+ i_max,
+ k,
+ i: Duration::ZERO,
+ t: Duration::ZERO,
+ t_exp: Instant::ZERO,
+ i_exp: Instant::ZERO,
+ counter: 0,
+ };
+
+ timer.i = Duration::from_millis(2u32.pow(timer.i_min) as u64);
+ timer.i_exp = now + timer.i;
+ timer.counter = 0;
+
+ timer.set_t(now, rand);
+
+ timer
+ }
+
+ /// Poll the Trickle timer. Returns `true` when the Trickle timer signals that a message can be
+ /// transmitted. This happens when the Trickle timer expires.
+ pub(crate) fn poll(&mut self, now: Instant, rand: &mut Rand) -> bool {
+ let can_transmit = self.can_transmit() && self.t_expired(now);
+
+ if can_transmit {
+ self.set_t(now, rand);
+ }
+
+ if self.i_expired(now) {
+ self.expire(now, rand);
+ }
+
+ can_transmit
+ }
+
+ /// Returns the Instant at which the Trickle timer should be polled again. Polling the Trickle
+ /// timer before this Instant is not harmfull, however, polling after it is not correct.
+ pub(crate) fn poll_at(&self) -> Instant {
+ self.t_exp.min(self.i_exp)
+ }
+
+ /// Signal the Trickle timer that a consistency has been heard, and thus increasing it's
+ /// counter.
+ pub(crate) fn hear_consistent(&mut self) {
+ self.counter += 1;
+ }
+
+ /// Signal the Trickle timer that an inconsistency has been heard. This resets the Trickle
+ /// timer when the current interval is not the smallest possible.
+ pub(crate) fn hear_inconsistency(&mut self, now: Instant, rand: &mut Rand) {
+ let i = Duration::from_millis(2u32.pow(self.i_min) as u64);
+ if self.i > i {
+ self.reset(i, now, rand);
+ }
+ }
+
+ /// Check if the Trickle timer can transmit or not. Returns `false` when the consistency
+ /// counter is bigger or equal to the default consistency constant.
+ pub(crate) fn can_transmit(&self) -> bool {
+ self.k != 0 && self.counter < self.k
+ }
+
+ /// Reset the Trickle timer when the interval has expired.
+ fn expire(&mut self, now: Instant, rand: &mut Rand) {
+ let max_interval = Duration::from_millis(2u32.pow(self.i_max) as u64);
+ let i = if self.i >= max_interval {
+ max_interval
+ } else {
+ self.i + self.i
+ };
+
+ self.reset(i, now, rand);
+ }
+
+ pub(crate) fn reset(&mut self, i: Duration, now: Instant, rand: &mut Rand) {
+ self.i = i;
+ self.i_exp = now + self.i;
+ self.counter = 0;
+ self.set_t(now, rand);
+ }
+
+ pub(crate) const fn max_expiration(&self) -> Duration {
+ Duration::from_millis(2u32.pow(self.i_max) as u64)
+ }
+
+ pub(crate) const fn min_expiration(&self) -> Duration {
+ Duration::from_millis(2u32.pow(self.i_min) as u64)
+ }
+
+ fn set_t(&mut self, now: Instant, rand: &mut Rand) {
+ let t = Duration::from_micros(
+ self.i.total_micros() / 2
+ + (rand.rand_u32() as u64
+ % (self.i.total_micros() - self.i.total_micros() / 2 + 1)),
+ );
+
+ self.t = t;
+ self.t_exp = now + t;
+ }
+
+ fn t_expired(&self, now: Instant) -> bool {
+ now >= self.t_exp
+ }
+
+ fn i_expired(&self, now: Instant) -> bool {
+ now >= self.i_exp
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn trickle_timer_intervals() {
+ let mut rand = Rand::new(1234);
+ let mut now = Instant::ZERO;
+ let mut trickle = TrickleTimer::default(now, &mut rand);
+
+ let mut previous_i = trickle.i;
+
+ while now <= Instant::from_secs(100_000) {
+ trickle.poll(now, &mut rand);
+
+ if now < Instant::ZERO + trickle.max_expiration() {
+ // t should always be inbetween I/2 and I.
+ assert!(trickle.i / 2 < trickle.t);
+ assert!(trickle.i > trickle.t);
+ }
+
+ if previous_i != trickle.i {
+ // When a new Interval is selected, this should be double the previous one.
+ assert_eq!(previous_i * 2, trickle.i);
+ assert_eq!(trickle.counter, 0);
+ previous_i = trickle.i;
+ }
+
+ now += Duration::from_millis(100);
+ }
+ }
+
+ #[test]
+ fn trickle_timer_hear_inconsistency() {
+ let mut rand = Rand::new(1234);
+ let mut now = Instant::ZERO;
+ let mut trickle = TrickleTimer::default(now, &mut rand);
+
+ trickle.counter = 1;
+
+ while now <= Instant::from_secs(10_000) {
+ trickle.poll(now, &mut rand);
+
+ if now < trickle.i_exp && now < Instant::ZERO + trickle.min_expiration() {
+ assert_eq!(trickle.counter, 1);
+ } else {
+ // The first interval expired, so the counter is reset.
+ assert_eq!(trickle.counter, 0);
+ }
+
+ if now == Instant::from_secs(10) {
+ // We set the counter to 1 such that we can test the `hear_inconsistency`.
+ trickle.counter = 1;
+
+ assert_eq!(trickle.counter, 1);
+
+ trickle.hear_inconsistency(now, &mut rand);
+
+ assert_eq!(trickle.counter, 0);
+ assert_eq!(trickle.i, trickle.min_expiration());
+ }
+
+ now += Duration::from_millis(100);
+ }
+ }
+
+ #[test]
+ fn trickle_timer_hear_consistency() {
+ let mut rand = Rand::new(1234);
+ let mut now = Instant::ZERO;
+ let mut trickle = TrickleTimer::default(now, &mut rand);
+
+ trickle.counter = 1;
+
+ let mut transmit_counter = 0;
+
+ while now <= Instant::from_secs(10_000) {
+ trickle.hear_consistent();
+
+ if trickle.poll(now, &mut rand) {
+ transmit_counter += 1;
+ }
+
+ if now == Instant::from_secs(10_000) {
+ use super::super::consts::{
+ DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_REDUNDANCY_CONSTANT,
+ };
+ assert!(!trickle.poll(now, &mut rand));
+ assert!(trickle.counter > DEFAULT_DIO_REDUNDANCY_CONSTANT);
+ // We should never have transmitted since the counter was higher than the default
+ // redundancy constant.
+ assert_eq!(transmit_counter, 0);
+ }
+
+ now += Duration::from_millis(100);
+ }
+ }
+}
diff --git a/src/iface/socket_meta.rs b/src/iface/socket_meta.rs
new file mode 100644
index 0000000..82c9908
--- /dev/null
+++ b/src/iface/socket_meta.rs
@@ -0,0 +1,103 @@
+use super::SocketHandle;
+use crate::{
+ socket::PollAt,
+ time::{Duration, Instant},
+ wire::IpAddress,
+};
+
+/// Neighbor dependency.
+///
+/// This enum tracks whether the socket should be polled based on the neighbor
+/// it is going to send packets to.
+#[derive(Debug, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+enum NeighborState {
+ /// Socket can be polled immediately.
+ #[default]
+ Active,
+ /// Socket should not be polled until either `silent_until` passes or
+ /// `neighbor` appears in the neighbor cache.
+ Waiting {
+ neighbor: IpAddress,
+ silent_until: Instant,
+ },
+}
+
+/// Network socket metadata.
+///
+/// This includes things that only external (to the socket, that is) code
+/// is interested in, but which are more conveniently stored inside the socket
+/// itself.
+#[derive(Debug, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) struct Meta {
+ /// Handle of this socket within its enclosing `SocketSet`.
+ /// Mainly useful for debug output.
+ pub(crate) handle: SocketHandle,
+ /// See [NeighborState](struct.NeighborState.html).
+ neighbor_state: NeighborState,
+}
+
+impl Meta {
+ /// Minimum delay between neighbor discovery requests for this particular
+ /// socket, in milliseconds.
+ ///
+ /// See also `iface::NeighborCache::SILENT_TIME`.
+ pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(1_000);
+
+ pub(crate) fn poll_at<F>(&self, socket_poll_at: PollAt, has_neighbor: F) -> PollAt
+ where
+ F: Fn(IpAddress) -> bool,
+ {
+ match self.neighbor_state {
+ NeighborState::Active => socket_poll_at,
+ NeighborState::Waiting { neighbor, .. } if has_neighbor(neighbor) => socket_poll_at,
+ NeighborState::Waiting { silent_until, .. } => PollAt::Time(silent_until),
+ }
+ }
+
+ pub(crate) fn egress_permitted<F>(&mut self, timestamp: Instant, has_neighbor: F) -> bool
+ where
+ F: Fn(IpAddress) -> bool,
+ {
+ match self.neighbor_state {
+ NeighborState::Active => true,
+ NeighborState::Waiting {
+ neighbor,
+ silent_until,
+ } => {
+ if has_neighbor(neighbor) {
+ net_trace!(
+ "{}: neighbor {} discovered, unsilencing",
+ self.handle,
+ neighbor
+ );
+ self.neighbor_state = NeighborState::Active;
+ true
+ } else if timestamp >= silent_until {
+ net_trace!(
+ "{}: neighbor {} silence timer expired, rediscovering",
+ self.handle,
+ neighbor
+ );
+ true
+ } else {
+ false
+ }
+ }
+ }
+ }
+
+ pub(crate) fn neighbor_missing(&mut self, timestamp: Instant, neighbor: IpAddress) {
+ net_trace!(
+ "{}: neighbor {} missing, silencing until t+{}",
+ self.handle,
+ neighbor,
+ Self::DISCOVERY_SILENT_TIME
+ );
+ self.neighbor_state = NeighborState::Waiting {
+ neighbor,
+ silent_until: timestamp + Self::DISCOVERY_SILENT_TIME,
+ };
+ }
+}
diff --git a/src/iface/socket_set.rs b/src/iface/socket_set.rs
new file mode 100644
index 0000000..be55fef
--- /dev/null
+++ b/src/iface/socket_set.rs
@@ -0,0 +1,151 @@
+use core::fmt;
+use managed::ManagedSlice;
+
+use super::socket_meta::Meta;
+use crate::socket::{AnySocket, Socket};
+
+/// Opaque struct with space for storing one socket.
+///
+/// This is public so you can use it to allocate space for storing
+/// sockets when creating an Interface.
+#[derive(Debug, Default)]
+pub struct SocketStorage<'a> {
+ inner: Option<Item<'a>>,
+}
+
+impl<'a> SocketStorage<'a> {
+ pub const EMPTY: Self = Self { inner: None };
+}
+
+/// An item of a socket set.
+#[derive(Debug)]
+pub(crate) struct Item<'a> {
+ pub(crate) meta: Meta,
+ pub(crate) socket: Socket<'a>,
+}
+
+/// A handle, identifying a socket in an Interface.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SocketHandle(usize);
+
+impl fmt::Display for SocketHandle {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "#{}", self.0)
+ }
+}
+
+/// An extensible set of sockets.
+///
+/// The lifetime `'a` is used when storing a `Socket<'a>`. If you're using
+/// owned buffers for your sockets (passed in as `Vec`s) you can use
+/// `SocketSet<'static>`.
+#[derive(Debug)]
+pub struct SocketSet<'a> {
+ sockets: ManagedSlice<'a, SocketStorage<'a>>,
+}
+
+impl<'a> SocketSet<'a> {
+ /// Create a socket set using the provided storage.
+ pub fn new<SocketsT>(sockets: SocketsT) -> SocketSet<'a>
+ where
+ SocketsT: Into<ManagedSlice<'a, SocketStorage<'a>>>,
+ {
+ let sockets = sockets.into();
+ SocketSet { sockets }
+ }
+
+ /// Add a socket to the set, and return its handle.
+ ///
+ /// # Panics
+ /// This function panics if the storage is fixed-size (not a `Vec`) and is full.
+ pub fn add<T: AnySocket<'a>>(&mut self, socket: T) -> SocketHandle {
+ fn put<'a>(index: usize, slot: &mut SocketStorage<'a>, socket: Socket<'a>) -> SocketHandle {
+ net_trace!("[{}]: adding", index);
+ let handle = SocketHandle(index);
+ let mut meta = Meta::default();
+ meta.handle = handle;
+ *slot = SocketStorage {
+ inner: Some(Item { meta, socket }),
+ };
+ handle
+ }
+
+ let socket = socket.upcast();
+
+ for (index, slot) in self.sockets.iter_mut().enumerate() {
+ if slot.inner.is_none() {
+ return put(index, slot, socket);
+ }
+ }
+
+ match &mut self.sockets {
+ ManagedSlice::Borrowed(_) => panic!("adding a socket to a full SocketSet"),
+ #[cfg(feature = "alloc")]
+ ManagedSlice::Owned(sockets) => {
+ sockets.push(SocketStorage { inner: None });
+ let index = sockets.len() - 1;
+ put(index, &mut sockets[index], socket)
+ }
+ }
+ }
+
+ /// Get a socket from the set by its handle, as mutable.
+ ///
+ /// # Panics
+ /// This function may panic if the handle does not belong to this socket set
+ /// or the socket has the wrong type.
+ pub fn get<T: AnySocket<'a>>(&self, handle: SocketHandle) -> &T {
+ match self.sockets[handle.0].inner.as_ref() {
+ Some(item) => {
+ T::downcast(&item.socket).expect("handle refers to a socket of a wrong type")
+ }
+ None => panic!("handle does not refer to a valid socket"),
+ }
+ }
+
+ /// Get a mutable socket from the set by its handle, as mutable.
+ ///
+ /// # Panics
+ /// This function may panic if the handle does not belong to this socket set
+ /// or the socket has the wrong type.
+ pub fn get_mut<T: AnySocket<'a>>(&mut self, handle: SocketHandle) -> &mut T {
+ match self.sockets[handle.0].inner.as_mut() {
+ Some(item) => T::downcast_mut(&mut item.socket)
+ .expect("handle refers to a socket of a wrong type"),
+ None => panic!("handle does not refer to a valid socket"),
+ }
+ }
+
+ /// Remove a socket from the set, without changing its state.
+ ///
+ /// # Panics
+ /// This function may panic if the handle does not belong to this socket set.
+ pub fn remove(&mut self, handle: SocketHandle) -> Socket<'a> {
+ net_trace!("[{}]: removing", handle.0);
+ match self.sockets[handle.0].inner.take() {
+ Some(item) => item.socket,
+ None => panic!("handle does not refer to a valid socket"),
+ }
+ }
+
+ /// Get an iterator to the inner sockets.
+ pub fn iter(&self) -> impl Iterator<Item = (SocketHandle, &Socket<'a>)> {
+ self.items().map(|i| (i.meta.handle, &i.socket))
+ }
+
+ /// Get a mutable iterator to the inner sockets.
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = (SocketHandle, &mut Socket<'a>)> {
+ self.items_mut().map(|i| (i.meta.handle, &mut i.socket))
+ }
+
+ /// Iterate every socket in this set.
+ pub(crate) fn items(&self) -> impl Iterator<Item = &Item<'a>> + '_ {
+ self.sockets.iter().filter_map(|x| x.inner.as_ref())
+ }
+
+ /// Iterate every socket in this set.
+ pub(crate) fn items_mut(&mut self) -> impl Iterator<Item = &mut Item<'a>> + '_ {
+ self.sockets.iter_mut().filter_map(|x| x.inner.as_mut())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..040ff57
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,180 @@
+#![cfg_attr(not(any(test, feature = "std")), no_std)]
+#![deny(unsafe_code)]
+
+//! The _smoltcp_ library is built in a layered structure, with the layers corresponding
+//! to the levels of API abstraction. Only the highest layers would be used by a typical
+//! application; however, the goal of _smoltcp_ is not just to provide a simple interface
+//! for writing applications but also to be a toolbox of networking primitives, so
+//! every layer is fully exposed and documented.
+//!
+//! When discussing networking stacks and layering, often the [OSI model][osi] is invoked.
+//! _smoltcp_ makes no effort to conform to the OSI model as it is not applicable to TCP/IP.
+//!
+//! # The socket layer
+//! The socket layer APIs are provided in the module [socket](socket/index.html); currently,
+//! raw, ICMP, TCP, and UDP sockets are provided. The socket API provides the usual primitives,
+//! but necessarily differs in many from the [Berkeley socket API][berk], as the latter was
+//! not designed to be used without heap allocation.
+//!
+//! The socket layer provides the buffering, packet construction and validation, and (for
+//! stateful sockets) the state machines, but it is interface-agnostic. An application must
+//! use sockets together with a network interface.
+//!
+//! # The interface layer
+//! The interface layer APIs are provided in the module [iface](iface/index.html); currently,
+//! Ethernet interface is provided.
+//!
+//! The interface layer handles the control messages, physical addressing and neighbor discovery.
+//! It routes packets to and from sockets.
+//!
+//! # The physical layer
+//! The physical layer APIs are provided in the module [phy](phy/index.html); currently,
+//! raw socket and TAP interface are provided. In addition, two _middleware_ interfaces
+//! are provided: the _tracer device_, which prints a human-readable representation of packets,
+//! and the _fault injector device_, which randomly introduces errors into the transmitted
+//! and received packet sequences.
+//!
+//! The physical layer handles interaction with a platform-specific network device.
+//!
+//! # The wire layers
+//! Unlike the higher layers, the wire layer APIs will not be used by a typical application.
+//! They however are the bedrock of _smoltcp_, and everything else is built on top of them.
+//!
+//! The wire layer APIs are designed by the principle "make illegal states ir-representable".
+//! If a wire layer object can be constructed, then it can also be parsed from or emitted to
+//! a lower level.
+//!
+//! The wire layer APIs also provide _tcpdump_-like pretty printing.
+//!
+//! ## The representation layer
+//! The representation layer APIs are provided in the module [wire].
+//!
+//! The representation layer exists to reduce the state space of raw packets. Raw packets
+//! may be nonsensical in a multitude of ways: invalid checksums, impossible combinations of flags,
+//! pointers to fields out of bounds, meaningless options... Representations shed all that,
+//! as well as any features not supported by _smoltcp_.
+//!
+//! ## The packet layer
+//! The packet layer APIs are also provided in the module [wire].
+//!
+//! The packet layer exists to provide a more structured way to work with packets than
+//! treating them as sequences of octets. It makes no judgement as to content of the packets,
+//! except where necessary to provide safe access to fields, and strives to implement every
+//! feature ever defined, to ensure that, when the representation layer is unable to make sense
+//! of a packet, it is still logged correctly and in full.
+//!
+//! # Minimum Supported Rust Version (MSRV)
+//!
+//! This crate is guaranteed to compile on stable Rust 1.65 and up with any valid set of features.
+//! It *might* compile on older versions but that may change in any new patch release.
+//!
+//! The exception is when using the `defmt` feature, in which case `defmt`'s MSRV applies, which
+//! is higher.
+//!
+//! [wire]: wire/index.html
+//! [osi]: https://en.wikipedia.org/wiki/OSI_model
+//! [berk]: https://en.wikipedia.org/wiki/Berkeley_sockets
+
+/* XXX compiler bug
+#![cfg(not(any(feature = "socket-raw",
+ feature = "socket-udp",
+ feature = "socket-tcp")))]
+compile_error!("at least one socket needs to be enabled"); */
+
+#![allow(clippy::match_like_matches_macro)]
+#![allow(clippy::redundant_field_names)]
+#![allow(clippy::identity_op)]
+#![allow(clippy::option_map_unit_fn)]
+#![allow(clippy::unit_arg)]
+#![allow(clippy::new_without_default)]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[cfg(not(any(
+ feature = "proto-ipv4",
+ feature = "proto-ipv6",
+ feature = "proto-sixlowpan"
+)))]
+compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6, proto-sixlowpan");
+
+#[cfg(all(
+ feature = "socket",
+ not(any(
+ feature = "socket-raw",
+ feature = "socket-udp",
+ feature = "socket-tcp",
+ feature = "socket-icmp",
+ feature = "socket-dhcpv4",
+ feature = "socket-dns",
+ ))
+))]
+compile_error!("If you enable the socket feature, you must enable at least one of the following features: socket-raw, socket-udp, socket-tcp, socket-icmp, socket-dhcpv4, socket-dns");
+
+#[cfg(all(
+ feature = "socket",
+ not(any(
+ feature = "medium-ethernet",
+ feature = "medium-ip",
+ feature = "medium-ieee802154",
+ ))
+))]
+compile_error!("If you enable the socket feature, you must enable at least one of the following features: medium-ip, medium-ethernet, medium-ieee802154");
+
+#[cfg(all(feature = "defmt", feature = "log"))]
+compile_error!("You must enable at most one of the following features: defmt, log");
+
+#[macro_use]
+mod macros;
+mod parsers;
+mod rand;
+
+#[cfg(test)]
+pub mod config {
+ #![allow(unused)]
+ pub const ASSEMBLER_MAX_SEGMENT_COUNT: usize = 4;
+ pub const DNS_MAX_NAME_SIZE: usize = 255;
+ pub const DNS_MAX_RESULT_COUNT: usize = 1;
+ pub const DNS_MAX_SERVER_COUNT: usize = 1;
+ pub const FRAGMENTATION_BUFFER_SIZE: usize = 1500;
+ pub const IFACE_MAX_ADDR_COUNT: usize = 8;
+ pub const IFACE_MAX_MULTICAST_GROUP_COUNT: usize = 4;
+ pub const IFACE_MAX_ROUTE_COUNT: usize = 4;
+ pub const IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT: usize = 4;
+ pub const IFACE_NEIGHBOR_CACHE_COUNT: usize = 3;
+ pub const REASSEMBLY_BUFFER_COUNT: usize = 4;
+ pub const REASSEMBLY_BUFFER_SIZE: usize = 1500;
+ pub const RPL_RELATIONS_BUFFER_COUNT: usize = 16;
+ pub const RPL_PARENTS_BUFFER_COUNT: usize = 8;
+ pub const IPV6_HBH_MAX_OPTIONS: usize = 2;
+}
+
+#[cfg(not(test))]
+pub mod config {
+ #![allow(unused)]
+ include!(concat!(env!("OUT_DIR"), "/config.rs"));
+}
+
+#[cfg(any(
+ feature = "medium-ethernet",
+ feature = "medium-ip",
+ feature = "medium-ieee802154"
+))]
+pub mod iface;
+
+pub mod phy;
+#[cfg(feature = "socket")]
+pub mod socket;
+pub mod storage;
+pub mod time;
+pub mod wire;
+
+#[cfg(all(
+ test,
+ any(
+ feature = "medium-ethernet",
+ feature = "medium-ip",
+ feature = "medium-ieee802154"
+ )
+))]
+mod tests;
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644
index 0000000..e899d24
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,169 @@
+#[cfg(not(test))]
+#[cfg(feature = "log")]
+macro_rules! net_log {
+ (trace, $($arg:expr),*) => { log::trace!($($arg),*) };
+ (debug, $($arg:expr),*) => { log::debug!($($arg),*) };
+}
+
+#[cfg(test)]
+#[cfg(feature = "log")]
+macro_rules! net_log {
+ (trace, $($arg:expr),*) => { println!($($arg),*) };
+ (debug, $($arg:expr),*) => { println!($($arg),*) };
+}
+
+#[cfg(feature = "defmt")]
+macro_rules! net_log {
+ (trace, $($arg:expr),*) => { defmt::trace!($($arg),*) };
+ (debug, $($arg:expr),*) => { defmt::debug!($($arg),*) };
+}
+
+#[cfg(not(any(feature = "log", feature = "defmt")))]
+macro_rules! net_log {
+ ($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }}
+}
+
+macro_rules! net_trace {
+ ($($arg:expr),*) => (net_log!(trace, $($arg),*));
+}
+
+macro_rules! net_debug {
+ ($($arg:expr),*) => (net_log!(debug, $($arg),*));
+}
+
+macro_rules! enum_with_unknown {
+ (
+ $( #[$enum_attr:meta] )*
+ pub enum $name:ident($ty:ty) {
+ $(
+ $( #[$variant_attr:meta] )*
+ $variant:ident = $value:expr
+ ),+ $(,)?
+ }
+ ) => {
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ $( #[$enum_attr] )*
+ pub enum $name {
+ $(
+ $( #[$variant_attr] )*
+ $variant
+ ),*,
+ Unknown($ty)
+ }
+
+ impl ::core::convert::From<$ty> for $name {
+ fn from(value: $ty) -> Self {
+ match value {
+ $( $value => $name::$variant ),*,
+ other => $name::Unknown(other)
+ }
+ }
+ }
+
+ impl ::core::convert::From<$name> for $ty {
+ fn from(value: $name) -> Self {
+ match value {
+ $( $name::$variant => $value ),*,
+ $name::Unknown(other) => other
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "proto-rpl")]
+macro_rules! get {
+ ($buffer:expr, into: $into:ty, fun: $fun:ident, field: $field:expr $(,)?) => {
+ {
+ <$into>::$fun(&$buffer.as_ref()[$field])
+ }
+ };
+
+ ($buffer:expr, into: $into:ty, field: $field:expr $(,)?) => {
+ get!($buffer, into: $into, field: $field, shift: 0, mask: 0b1111_1111)
+ };
+
+ ($buffer:expr, into: $into:ty, field: $field:expr, mask: $bit_mask:expr $(,)?) => {
+ get!($buffer, into: $into, field: $field, shift: 0, mask: $bit_mask)
+ };
+
+ ($buffer:expr, into: $into:ty, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {
+ {
+ <$into>::from((&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask)
+ }
+ };
+
+ ($buffer:expr, field: $field:expr $(,)?) => {
+ get!($buffer, field: $field, shift: 0, mask: 0b1111_1111)
+ };
+
+ ($buffer:expr, field: $field:expr, mask: $bit_mask:expr $(,)?) => {
+ get!($buffer, field: $field, shift: 0, mask: $bit_mask)
+ };
+
+ ($buffer:expr, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?)
+ =>
+ {
+ {
+ (&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask
+ }
+ };
+
+ ($buffer:expr, u16, field: $field:expr $(,)?) => {
+ {
+ NetworkEndian::read_u16(&$buffer.as_ref()[$field])
+ }
+ };
+
+ ($buffer:expr, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {
+ {
+ (($buffer.as_ref()[$field] >> $bit_shift) & $bit_mask) == 0b1
+ }
+ };
+
+ ($buffer:expr, u32, field: $field:expr $(,)?) => {
+ {
+ NetworkEndian::read_u32(&$buffer.as_ref()[$field])
+ }
+ };
+}
+
+#[cfg(feature = "proto-rpl")]
+macro_rules! set {
+ ($buffer:expr, address: $address:ident, field: $field:expr $(,)?) => {{
+ $buffer.as_mut()[$field].copy_from_slice($address.as_bytes());
+ }};
+
+ ($buffer:expr, $value:ident, field: $field:expr $(,)?) => {
+ set!($buffer, $value, field: $field, shift: 0, mask: 0b1111_1111)
+ };
+
+ ($buffer:expr, $value:ident, field: $field:expr, mask: $bit_mask:expr $(,)?) => {
+ set!($buffer, $value, field: $field, shift: 0, mask: $bit_mask)
+ };
+
+ ($buffer:expr, $value:ident, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{
+ let raw =
+ ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift)) | ($value << $bit_shift);
+ $buffer.as_mut()[$field] = raw;
+ }};
+
+ ($buffer:expr, $value:ident, bool, field: $field:expr, mask: $bit_mask:expr $(,)?) => {
+ set!($buffer, $value, bool, field: $field, shift: 0, mask: $bit_mask);
+ };
+
+ ($buffer:expr, $value:ident, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{
+ let raw = ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift))
+ | (if $value { 0b1 } else { 0b0 } << $bit_shift);
+ $buffer.as_mut()[$field] = raw;
+ }};
+
+ ($buffer:expr, $value:ident, u16, field: $field:expr $(,)?) => {{
+ NetworkEndian::write_u16(&mut $buffer.as_mut()[$field], $value);
+ }};
+
+ ($buffer:expr, $value:ident, u32, field: $field:expr $(,)?) => {{
+ NetworkEndian::write_u32(&mut $buffer.as_mut()[$field], $value);
+ }};
+}
diff --git a/src/parsers.rs b/src/parsers.rs
new file mode 100644
index 0000000..16419ab
--- /dev/null
+++ b/src/parsers.rs
@@ -0,0 +1,765 @@
+#![cfg_attr(
+ not(all(feature = "proto-ipv6", feature = "proto-ipv4")),
+ allow(dead_code)
+)]
+
+use core::result;
+use core::str::FromStr;
+
+#[cfg(feature = "medium-ethernet")]
+use crate::wire::EthernetAddress;
+use crate::wire::{IpAddress, IpCidr, IpEndpoint};
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::{Ipv4Address, Ipv4Cidr};
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::{Ipv6Address, Ipv6Cidr};
+
+type Result<T> = result::Result<T, ()>;
+
+struct Parser<'a> {
+ data: &'a [u8],
+ pos: usize,
+}
+
+impl<'a> Parser<'a> {
+ fn new(data: &'a str) -> Parser<'a> {
+ Parser {
+ data: data.as_bytes(),
+ pos: 0,
+ }
+ }
+
+ fn lookahead_char(&self, ch: u8) -> bool {
+ if self.pos < self.data.len() {
+ self.data[self.pos] == ch
+ } else {
+ false
+ }
+ }
+
+ fn advance(&mut self) -> Result<u8> {
+ match self.data.get(self.pos) {
+ Some(&chr) => {
+ self.pos += 1;
+ Ok(chr)
+ }
+ None => Err(()),
+ }
+ }
+
+ fn try_do<F, T>(&mut self, f: F) -> Option<T>
+ where
+ F: FnOnce(&mut Parser<'a>) -> Result<T>,
+ {
+ let pos = self.pos;
+ match f(self) {
+ Ok(res) => Some(res),
+ Err(()) => {
+ self.pos = pos;
+ None
+ }
+ }
+ }
+
+ fn accept_eof(&mut self) -> Result<()> {
+ if self.data.len() == self.pos {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ fn until_eof<F, T>(&mut self, f: F) -> Result<T>
+ where
+ F: FnOnce(&mut Parser<'a>) -> Result<T>,
+ {
+ let res = f(self)?;
+ self.accept_eof()?;
+ Ok(res)
+ }
+
+ fn accept_char(&mut self, chr: u8) -> Result<()> {
+ if self.advance()? == chr {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ fn accept_str(&mut self, string: &[u8]) -> Result<()> {
+ for byte in string.iter() {
+ self.accept_char(*byte)?;
+ }
+ Ok(())
+ }
+
+ fn accept_digit(&mut self, hex: bool) -> Result<u8> {
+ let digit = self.advance()?;
+ if digit.is_ascii_digit() {
+ Ok(digit - b'0')
+ } else if hex && (b'a'..=b'f').contains(&digit) {
+ Ok(digit - b'a' + 10)
+ } else if hex && (b'A'..=b'F').contains(&digit) {
+ Ok(digit - b'A' + 10)
+ } else {
+ Err(())
+ }
+ }
+
+ fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result<u32> {
+ let mut value = self.accept_digit(hex)? as u32;
+ for _ in 1..max_digits {
+ match self.try_do(|p| p.accept_digit(hex)) {
+ Some(digit) => {
+ value *= if hex { 16 } else { 10 };
+ value += digit as u32;
+ }
+ None => break,
+ }
+ }
+ if value < max_value {
+ Ok(value)
+ } else {
+ Err(())
+ }
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ fn accept_mac_joined_with(&mut self, separator: u8) -> Result<EthernetAddress> {
+ let mut octets = [0u8; 6];
+ for (n, octet) in octets.iter_mut().enumerate() {
+ *octet = self.accept_number(2, 0x100, true)? as u8;
+ if n != 5 {
+ self.accept_char(separator)?;
+ }
+ }
+ Ok(EthernetAddress(octets))
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ fn accept_mac(&mut self) -> Result<EthernetAddress> {
+ if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) {
+ return Ok(mac);
+ }
+ if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) {
+ return Ok(mac);
+ }
+ Err(())
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> {
+ let octets = self.accept_ipv4_octets()?;
+
+ parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16);
+ *idx += 1;
+ parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16);
+ *idx += 1;
+
+ Ok(())
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ fn accept_ipv6_part(
+ &mut self,
+ (head, tail): (&mut [u16; 8], &mut [u16; 6]),
+ (head_idx, tail_idx): (&mut usize, &mut usize),
+ mut use_tail: bool,
+ ) -> Result<()> {
+ let double_colon = match self.try_do(|p| p.accept_str(b"::")) {
+ Some(_) if !use_tail && *head_idx < 7 => {
+ // Found a double colon. Start filling out the
+ // tail and set the double colon flag in case
+ // this is the last character we can parse.
+ use_tail = true;
+ true
+ }
+ Some(_) => {
+ // This is a bad address. Only one double colon is
+ // allowed and an address is only 128 bits.
+ return Err(());
+ }
+ None => {
+ if *head_idx != 0 || use_tail && *tail_idx != 0 {
+ // If this is not the first number or the position following
+ // a double colon, we expect there to be a single colon.
+ self.accept_char(b':')?;
+ }
+ false
+ }
+ };
+
+ match self.try_do(|p| p.accept_number(4, 0x10000, true)) {
+ Some(part) if !use_tail && *head_idx < 8 => {
+ // Valid u16 to be added to the address
+ head[*head_idx] = part as u16;
+ *head_idx += 1;
+
+ if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] {
+ self.try_do(|p| {
+ p.accept_char(b':')?;
+ p.accept_ipv4_mapped_ipv6_part(head, head_idx)
+ });
+ }
+ Ok(())
+ }
+ Some(part) if *tail_idx < 6 => {
+ // Valid u16 to be added to the address
+ tail[*tail_idx] = part as u16;
+ *tail_idx += 1;
+
+ if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
+ self.try_do(|p| {
+ p.accept_char(b':')?;
+ p.accept_ipv4_mapped_ipv6_part(tail, tail_idx)
+ });
+ }
+ Ok(())
+ }
+ Some(_) => {
+ // Tail or head section is too long
+ Err(())
+ }
+ None if double_colon => {
+ // The address ends with "::". E.g. 1234:: or ::
+ Ok(())
+ }
+ None => {
+ // Invalid address
+ Err(())
+ }
+ }?;
+
+ if *head_idx + *tail_idx > 8 {
+ // The head and tail indexes add up to a bad address length.
+ Err(())
+ } else if !self.lookahead_char(b':') {
+ if *head_idx < 8 && !use_tail {
+ // There was no double colon found, and the head is too short
+ return Err(());
+ }
+ Ok(())
+ } else {
+ // Continue recursing
+ self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail)
+ }
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ fn accept_ipv6(&mut self) -> Result<Ipv6Address> {
+ // IPv6 addresses may contain a "::" to indicate a series of
+ // 16 bit sections that evaluate to 0. E.g.
+ //
+ // fe80:0000:0000:0000:0000:0000:0000:0001
+ //
+ // May be written as
+ //
+ // fe80::1
+ //
+ // As a result, we need to find the first section of colon
+ // delimited u16's before a possible "::", then the
+ // possible second section after the "::", and finally
+ // combine the second optional section to the end of the
+ // final address.
+ //
+ // See https://tools.ietf.org/html/rfc4291#section-2.2
+ // for details.
+ let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]);
+ let (mut head_idx, mut tail_idx) = (0, 0);
+
+ self.accept_ipv6_part(
+ (&mut addr, &mut tail),
+ (&mut head_idx, &mut tail_idx),
+ false,
+ )?;
+
+ // We need to copy the tail portion (the portion following the "::") to the
+ // end of the address.
+ addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]);
+
+ Ok(Ipv6Address::from_parts(&addr))
+ }
+
+ fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> {
+ let mut octets = [0u8; 4];
+ for (n, octet) in octets.iter_mut().enumerate() {
+ *octet = self.accept_number(3, 0x100, false)? as u8;
+ if n != 3 {
+ self.accept_char(b'.')?;
+ }
+ }
+ Ok(octets)
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ fn accept_ipv4(&mut self) -> Result<Ipv4Address> {
+ let octets = self.accept_ipv4_octets()?;
+ Ok(Ipv4Address(octets))
+ }
+
+ fn accept_ip(&mut self) -> Result<IpAddress> {
+ #[cfg(feature = "proto-ipv4")]
+ #[allow(clippy::single_match)]
+ match self.try_do(|p| p.accept_ipv4()) {
+ Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)),
+ None => (),
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ #[allow(clippy::single_match)]
+ match self.try_do(|p| p.accept_ipv6()) {
+ Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)),
+ None => (),
+ }
+
+ Err(())
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ fn accept_ipv4_endpoint(&mut self) -> Result<IpEndpoint> {
+ let ip = self.accept_ipv4()?;
+
+ let port = if self.accept_eof().is_ok() {
+ 0
+ } else {
+ self.accept_char(b':')?;
+ self.accept_number(5, 65535, false)?
+ };
+
+ Ok(IpEndpoint {
+ addr: IpAddress::Ipv4(ip),
+ port: port as u16,
+ })
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ fn accept_ipv6_endpoint(&mut self) -> Result<IpEndpoint> {
+ if self.lookahead_char(b'[') {
+ self.accept_char(b'[')?;
+ let ip = self.accept_ipv6()?;
+ self.accept_char(b']')?;
+ self.accept_char(b':')?;
+ let port = self.accept_number(5, 65535, false)?;
+
+ Ok(IpEndpoint {
+ addr: IpAddress::Ipv6(ip),
+ port: port as u16,
+ })
+ } else {
+ let ip = self.accept_ipv6()?;
+ Ok(IpEndpoint {
+ addr: IpAddress::Ipv6(ip),
+ port: 0,
+ })
+ }
+ }
+
+ fn accept_ip_endpoint(&mut self) -> Result<IpEndpoint> {
+ #[cfg(feature = "proto-ipv4")]
+ #[allow(clippy::single_match)]
+ match self.try_do(|p| p.accept_ipv4_endpoint()) {
+ Some(ipv4) => return Ok(ipv4),
+ None => (),
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ #[allow(clippy::single_match)]
+ match self.try_do(|p| p.accept_ipv6_endpoint()) {
+ Some(ipv6) => return Ok(ipv6),
+ None => (),
+ }
+
+ Err(())
+ }
+}
+
+#[cfg(feature = "medium-ethernet")]
+impl FromStr for EthernetAddress {
+ type Err = ();
+
+ /// Parse a string representation of an Ethernet address.
+ fn from_str(s: &str) -> Result<EthernetAddress> {
+ Parser::new(s).until_eof(|p| p.accept_mac())
+ }
+}
+
+#[cfg(feature = "proto-ipv4")]
+impl FromStr for Ipv4Address {
+ type Err = ();
+
+ /// Parse a string representation of an IPv4 address.
+ fn from_str(s: &str) -> Result<Ipv4Address> {
+ Parser::new(s).until_eof(|p| p.accept_ipv4())
+ }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl FromStr for Ipv6Address {
+ type Err = ();
+
+ /// Parse a string representation of an IPv6 address.
+ fn from_str(s: &str) -> Result<Ipv6Address> {
+ Parser::new(s).until_eof(|p| p.accept_ipv6())
+ }
+}
+
+impl FromStr for IpAddress {
+ type Err = ();
+
+ /// Parse a string representation of an IP address.
+ fn from_str(s: &str) -> Result<IpAddress> {
+ Parser::new(s).until_eof(|p| p.accept_ip())
+ }
+}
+
+#[cfg(feature = "proto-ipv4")]
+impl FromStr for Ipv4Cidr {
+ type Err = ();
+
+ /// Parse a string representation of an IPv4 CIDR.
+ fn from_str(s: &str) -> Result<Ipv4Cidr> {
+ Parser::new(s).until_eof(|p| {
+ let ip = p.accept_ipv4()?;
+ p.accept_char(b'/')?;
+ let prefix_len = p.accept_number(2, 33, false)? as u8;
+ Ok(Ipv4Cidr::new(ip, prefix_len))
+ })
+ }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl FromStr for Ipv6Cidr {
+ type Err = ();
+
+ /// Parse a string representation of an IPv6 CIDR.
+ fn from_str(s: &str) -> Result<Ipv6Cidr> {
+ // https://tools.ietf.org/html/rfc4291#section-2.3
+ Parser::new(s).until_eof(|p| {
+ let ip = p.accept_ipv6()?;
+ p.accept_char(b'/')?;
+ let prefix_len = p.accept_number(3, 129, false)? as u8;
+ Ok(Ipv6Cidr::new(ip, prefix_len))
+ })
+ }
+}
+
+impl FromStr for IpCidr {
+ type Err = ();
+
+ /// Parse a string representation of an IP CIDR.
+ fn from_str(s: &str) -> Result<IpCidr> {
+ #[cfg(feature = "proto-ipv4")]
+ #[allow(clippy::single_match)]
+ match Ipv4Cidr::from_str(s) {
+ Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)),
+ Err(_) => (),
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ #[allow(clippy::single_match)]
+ match Ipv6Cidr::from_str(s) {
+ Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)),
+ Err(_) => (),
+ }
+
+ Err(())
+ }
+}
+
+impl FromStr for IpEndpoint {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<IpEndpoint> {
+ Parser::new(s).until_eof(|p| p.accept_ip_endpoint())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ macro_rules! check_cidr_test_array {
+ ($tests:expr, $from_str:path, $variant:path) => {
+ for &(s, cidr) in &$tests {
+ assert_eq!($from_str(s), cidr);
+ assert_eq!(IpCidr::from_str(s), cidr.map($variant));
+
+ if let Ok(cidr) = cidr {
+ assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr));
+ assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr)));
+ }
+ }
+ };
+ }
+
+ #[test]
+ #[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
+ fn test_mac() {
+ assert_eq!(EthernetAddress::from_str(""), Err(()));
+ assert_eq!(
+ EthernetAddress::from_str("02:00:00:00:00:00"),
+ Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00]))
+ );
+ assert_eq!(
+ EthernetAddress::from_str("01:23:45:67:89:ab"),
+ Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]))
+ );
+ assert_eq!(
+ EthernetAddress::from_str("cd:ef:10:00:00:00"),
+ Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00]))
+ );
+ assert_eq!(
+ EthernetAddress::from_str("00:00:00:ab:cd:ef"),
+ Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
+ );
+ assert_eq!(
+ EthernetAddress::from_str("00-00-00-ab-cd-ef"),
+ Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
+ );
+ assert_eq!(
+ EthernetAddress::from_str("AB-CD-EF-00-00-00"),
+ Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00]))
+ );
+ assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(()));
+ assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(()));
+ assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(()));
+ assert_eq!(EthernetAddress::from_str("02:00:00:00:00:0x"), Err(()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_ipv4() {
+ assert_eq!(Ipv4Address::from_str(""), Err(()));
+ assert_eq!(
+ Ipv4Address::from_str("1.2.3.4"),
+ Ok(Ipv4Address([1, 2, 3, 4]))
+ );
+ assert_eq!(
+ Ipv4Address::from_str("001.2.3.4"),
+ Ok(Ipv4Address([1, 2, 3, 4]))
+ );
+ assert_eq!(Ipv4Address::from_str("0001.2.3.4"), Err(()));
+ assert_eq!(Ipv4Address::from_str("999.2.3.4"), Err(()));
+ assert_eq!(Ipv4Address::from_str("1.2.3.4.5"), Err(()));
+ assert_eq!(Ipv4Address::from_str("1.2.3"), Err(()));
+ assert_eq!(Ipv4Address::from_str("1.2.3."), Err(()));
+ assert_eq!(Ipv4Address::from_str("1.2.3.4."), Err(()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn test_ipv6() {
+ // Obviously not valid
+ assert_eq!(Ipv6Address::from_str(""), Err(()));
+ assert_eq!(
+ Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"),
+ Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
+ );
+ assert_eq!(Ipv6Address::from_str("::1"), Ok(Ipv6Address::LOOPBACK));
+ assert_eq!(Ipv6Address::from_str("::"), Ok(Ipv6Address::UNSPECIFIED));
+ assert_eq!(
+ Ipv6Address::from_str("fe80::1"),
+ Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("1234:5678::"),
+ Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("1234:5678::8765:4321"),
+ Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321))
+ );
+ // Two double colons in address
+ assert_eq!(Ipv6Address::from_str("1234:5678::1::1"), Err(()));
+ assert_eq!(
+ Ipv6Address::from_str("4444:333:22:1::4"),
+ Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("1:1:1:1:1:1::"),
+ Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("::1:1:1:1:1:1"),
+ Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1))
+ );
+ assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
+ // Double colon appears too late indicating an address that is too long
+ assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"), Err(()));
+ // Section after double colon is too long for a valid address
+ assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
+ // Obviously too long
+ assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"), Err(()));
+ // Address is too short
+ assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"), Err(()));
+ // Long number
+ assert_eq!(Ipv6Address::from_str("::000001"), Err(()));
+ // IPv4-Mapped address
+ assert_eq!(
+ Ipv6Address::from_str("::ffff:192.168.1.1"),
+ Ok(Ipv6Address([
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
+ ]))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"),
+ Ok(Ipv6Address([
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
+ ]))
+ );
+ assert_eq!(
+ Ipv6Address::from_str("0::ffff:192.168.1.1"),
+ Ok(Ipv6Address([
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
+ ]))
+ );
+ // Only ffff is allowed in position 6 when IPv4 mapped
+ assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"), Err(()));
+ // Positions 1-5 must be 0 when IPv4 mapped
+ assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"), Err(()));
+ assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"), Err(()));
+ // Out of range ipv4 octet
+ assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"), Err(()));
+ // Invalid hex in ipv4 octet
+ assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"), Err(()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_ip_ipv4() {
+ assert_eq!(IpAddress::from_str(""), Err(()));
+ assert_eq!(
+ IpAddress::from_str("1.2.3.4"),
+ Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4])))
+ );
+ assert_eq!(IpAddress::from_str("x"), Err(()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn test_ip_ipv6() {
+ assert_eq!(IpAddress::from_str(""), Err(()));
+ assert_eq!(
+ IpAddress::from_str("fe80::1"),
+ Ok(IpAddress::Ipv6(Ipv6Address::new(
+ 0xfe80, 0, 0, 0, 0, 0, 0, 1
+ )))
+ );
+ assert_eq!(IpAddress::from_str("x"), Err(()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_cidr_ipv4() {
+ let tests = [
+ (
+ "127.0.0.1/8",
+ Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8)),
+ ),
+ (
+ "192.168.1.1/24",
+ Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8)),
+ ),
+ (
+ "8.8.8.8/32",
+ Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8)),
+ ),
+ (
+ "8.8.8.8/0",
+ Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8)),
+ ),
+ ("", Err(())),
+ ("1", Err(())),
+ ("127.0.0.1", Err(())),
+ ("127.0.0.1/", Err(())),
+ ("127.0.0.1/33", Err(())),
+ ("127.0.0.1/111", Err(())),
+ ("/32", Err(())),
+ ];
+
+ check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn test_cidr_ipv6() {
+ let tests = [
+ (
+ "fe80::1/64",
+ Ok(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ 64u8,
+ )),
+ ),
+ (
+ "fe80::/64",
+ Ok(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
+ 64u8,
+ )),
+ ),
+ ("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))),
+ ("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))),
+ (
+ "fe80:0:0:0:0:0:0:1/64",
+ Ok(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ 64u8,
+ )),
+ ),
+ ("fe80:0:0:0:0:0:0:1|64", Err(())),
+ ("fe80::|64", Err(())),
+ ("fe80::1::/64", Err(())),
+ ];
+ check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_endpoint_ipv4() {
+ assert_eq!(IpEndpoint::from_str(""), Err(()));
+ assert_eq!(IpEndpoint::from_str("x"), Err(()));
+ assert_eq!(
+ IpEndpoint::from_str("127.0.0.1"),
+ Ok(IpEndpoint {
+ addr: IpAddress::v4(127, 0, 0, 1),
+ port: 0
+ })
+ );
+ assert_eq!(
+ IpEndpoint::from_str("127.0.0.1:12345"),
+ Ok(IpEndpoint {
+ addr: IpAddress::v4(127, 0, 0, 1),
+ port: 12345
+ })
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn test_endpoint_ipv6() {
+ assert_eq!(IpEndpoint::from_str(""), Err(()));
+ assert_eq!(IpEndpoint::from_str("x"), Err(()));
+ assert_eq!(
+ IpEndpoint::from_str("fe80::1"),
+ Ok(IpEndpoint {
+ addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ port: 0
+ })
+ );
+ assert_eq!(
+ IpEndpoint::from_str("[fe80::1]:12345"),
+ Ok(IpEndpoint {
+ addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ port: 12345
+ })
+ );
+ assert_eq!(
+ IpEndpoint::from_str("[::]:12345"),
+ Ok(IpEndpoint {
+ addr: IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0),
+ port: 12345
+ })
+ );
+ }
+}
diff --git a/src/phy/fault_injector.rs b/src/phy/fault_injector.rs
new file mode 100644
index 0000000..fffe11a
--- /dev/null
+++ b/src/phy/fault_injector.rs
@@ -0,0 +1,330 @@
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::{Duration, Instant};
+
+use super::PacketMeta;
+
+// We use our own RNG to stay compatible with #![no_std].
+// The use of the RNG below has a slight bias, but it doesn't matter.
+fn xorshift32(state: &mut u32) -> u32 {
+ let mut x = *state;
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ *state = x;
+ x
+}
+
+// This could be fixed once associated consts are stable.
+const MTU: usize = 1536;
+
+#[derive(Debug, Default, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct Config {
+ corrupt_pct: u8,
+ drop_pct: u8,
+ max_size: usize,
+ max_tx_rate: u64,
+ max_rx_rate: u64,
+ interval: Duration,
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct State {
+ rng_seed: u32,
+ refilled_at: Instant,
+ tx_bucket: u64,
+ rx_bucket: u64,
+}
+
+impl State {
+ fn maybe(&mut self, pct: u8) -> bool {
+ xorshift32(&mut self.rng_seed) % 100 < pct as u32
+ }
+
+ fn corrupt<T: AsMut<[u8]>>(&mut self, mut buffer: T) {
+ let buffer = buffer.as_mut();
+ // We introduce a single bitflip, as the most likely, and the hardest to detect, error.
+ let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len();
+ let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
+ buffer[index] ^= bit;
+ }
+
+ fn refill(&mut self, config: &Config, timestamp: Instant) {
+ if timestamp - self.refilled_at > config.interval {
+ self.tx_bucket = config.max_tx_rate;
+ self.rx_bucket = config.max_rx_rate;
+ self.refilled_at = timestamp;
+ }
+ }
+
+ fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool {
+ if config.max_tx_rate == 0 {
+ return true;
+ }
+
+ self.refill(config, timestamp);
+ if self.tx_bucket > 0 {
+ self.tx_bucket -= 1;
+ true
+ } else {
+ false
+ }
+ }
+
+ fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool {
+ if config.max_rx_rate == 0 {
+ return true;
+ }
+
+ self.refill(config, timestamp);
+ if self.rx_bucket > 0 {
+ self.rx_bucket -= 1;
+ true
+ } else {
+ false
+ }
+ }
+}
+
+/// A fault injector device.
+///
+/// A fault injector is a device that alters packets traversing through it to simulate
+/// adverse network conditions (such as random packet loss or corruption), or software
+/// or hardware limitations (such as a limited number or size of usable network buffers).
+#[derive(Debug)]
+pub struct FaultInjector<D: Device> {
+ inner: D,
+ state: State,
+ config: Config,
+ rx_buf: [u8; MTU],
+}
+
+impl<D: Device> FaultInjector<D> {
+ /// Create a fault injector device, using the given random number generator seed.
+ pub fn new(inner: D, seed: u32) -> FaultInjector<D> {
+ FaultInjector {
+ inner,
+ state: State {
+ rng_seed: seed,
+ refilled_at: Instant::from_millis(0),
+ tx_bucket: 0,
+ rx_bucket: 0,
+ },
+ config: Config::default(),
+ rx_buf: [0u8; MTU],
+ }
+ }
+
+ /// Return the underlying device, consuming the fault injector.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+
+ /// Return the probability of corrupting a packet, in percents.
+ pub fn corrupt_chance(&self) -> u8 {
+ self.config.corrupt_pct
+ }
+
+ /// Return the probability of dropping a packet, in percents.
+ pub fn drop_chance(&self) -> u8 {
+ self.config.drop_pct
+ }
+
+ /// Return the maximum packet size, in octets.
+ pub fn max_packet_size(&self) -> usize {
+ self.config.max_size
+ }
+
+ /// Return the maximum packet transmission rate, in packets per second.
+ pub fn max_tx_rate(&self) -> u64 {
+ self.config.max_tx_rate
+ }
+
+ /// Return the maximum packet reception rate, in packets per second.
+ pub fn max_rx_rate(&self) -> u64 {
+ self.config.max_rx_rate
+ }
+
+ /// Return the interval for packet rate limiting, in milliseconds.
+ pub fn bucket_interval(&self) -> Duration {
+ self.config.interval
+ }
+
+ /// Set the probability of corrupting a packet, in percents.
+ ///
+ /// # Panics
+ /// This function panics if the probability is not between 0% and 100%.
+ pub fn set_corrupt_chance(&mut self, pct: u8) {
+ if pct > 100 {
+ panic!("percentage out of range")
+ }
+ self.config.corrupt_pct = pct
+ }
+
+ /// Set the probability of dropping a packet, in percents.
+ ///
+ /// # Panics
+ /// This function panics if the probability is not between 0% and 100%.
+ pub fn set_drop_chance(&mut self, pct: u8) {
+ if pct > 100 {
+ panic!("percentage out of range")
+ }
+ self.config.drop_pct = pct
+ }
+
+ /// Set the maximum packet size, in octets.
+ pub fn set_max_packet_size(&mut self, size: usize) {
+ self.config.max_size = size
+ }
+
+ /// Set the maximum packet transmission rate, in packets per interval.
+ pub fn set_max_tx_rate(&mut self, rate: u64) {
+ self.config.max_tx_rate = rate
+ }
+
+ /// Set the maximum packet reception rate, in packets per interval.
+ pub fn set_max_rx_rate(&mut self, rate: u64) {
+ self.config.max_rx_rate = rate
+ }
+
+ /// Set the interval for packet rate limiting, in milliseconds.
+ pub fn set_bucket_interval(&mut self, interval: Duration) {
+ self.state.refilled_at = Instant::from_millis(0);
+ self.config.interval = interval
+ }
+}
+
+impl<D: Device> Device for FaultInjector<D> {
+ type RxToken<'a> = RxToken<'a>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = self.inner.capabilities();
+ if caps.max_transmission_unit > MTU {
+ caps.max_transmission_unit = MTU;
+ }
+ caps
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let (rx_token, tx_token) = self.inner.receive(timestamp)?;
+ let rx_meta = <D::RxToken<'_> as phy::RxToken>::meta(&rx_token);
+
+ let len = super::RxToken::consume(rx_token, |buffer| {
+ if (self.config.max_size > 0 && buffer.len() > self.config.max_size)
+ || buffer.len() > self.rx_buf.len()
+ {
+ net_trace!("rx: dropping a packet that is too large");
+ return None;
+ }
+ self.rx_buf[..buffer.len()].copy_from_slice(buffer);
+ Some(buffer.len())
+ })?;
+
+ let buf = &mut self.rx_buf[..len];
+
+ if self.state.maybe(self.config.drop_pct) {
+ net_trace!("rx: randomly dropping a packet");
+ return None;
+ }
+
+ if !self.state.maybe_receive(&self.config, timestamp) {
+ net_trace!("rx: dropping a packet because of rate limiting");
+ return None;
+ }
+
+ if self.state.maybe(self.config.corrupt_pct) {
+ net_trace!("rx: randomly corrupting a packet");
+ self.state.corrupt(&mut buf[..]);
+ }
+
+ let rx = RxToken { buf, meta: rx_meta };
+ let tx = TxToken {
+ state: &mut self.state,
+ config: self.config,
+ token: tx_token,
+ junk: [0; MTU],
+ timestamp,
+ };
+ Some((rx, tx))
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ self.inner.transmit(timestamp).map(|token| TxToken {
+ state: &mut self.state,
+ config: self.config,
+ token,
+ junk: [0; MTU],
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a> {
+ buf: &'a mut [u8],
+ meta: PacketMeta,
+}
+
+impl<'a> phy::RxToken for RxToken<'a> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(self.buf)
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.meta
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken> {
+ state: &'a mut State,
+ config: Config,
+ token: Tx,
+ junk: [u8; MTU],
+ timestamp: Instant,
+}
+
+impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
+ fn consume<R, F>(mut self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let drop = if self.state.maybe(self.config.drop_pct) {
+ net_trace!("tx: randomly dropping a packet");
+ true
+ } else if self.config.max_size > 0 && len > self.config.max_size {
+ net_trace!("tx: dropping a packet that is too large");
+ true
+ } else if !self.state.maybe_transmit(&self.config, self.timestamp) {
+ net_trace!("tx: dropping a packet because of rate limiting");
+ true
+ } else {
+ false
+ };
+
+ if drop {
+ return f(&mut self.junk[..len]);
+ }
+
+ self.token.consume(len, |mut buf| {
+ if self.state.maybe(self.config.corrupt_pct) {
+ net_trace!("tx: corrupting a packet");
+ self.state.corrupt(&mut buf)
+ }
+ f(buf)
+ })
+ }
+
+ fn set_meta(&mut self, meta: PacketMeta) {
+ self.token.set_meta(meta);
+ }
+}
diff --git a/src/phy/fuzz_injector.rs b/src/phy/fuzz_injector.rs
new file mode 100644
index 0000000..6769d8e
--- /dev/null
+++ b/src/phy/fuzz_injector.rs
@@ -0,0 +1,129 @@
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::Instant;
+
+// This could be fixed once associated consts are stable.
+const MTU: usize = 1536;
+
+/// Represents a fuzzer. It is expected to replace bytes in the packet with fuzzed data.
+pub trait Fuzzer {
+ /// Modify a single packet with fuzzed data.
+ fn fuzz_packet(&self, packet_data: &mut [u8]);
+}
+
+/// A fuzz injector device.
+///
+/// A fuzz injector is a device that alters packets traversing through it according to the
+/// directions of a guided fuzzer. It is designed to support fuzzing internal state machines inside
+/// smoltcp, and is not for production use.
+#[allow(unused)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct FuzzInjector<D: Device, FTx: Fuzzer, FRx: Fuzzer> {
+ inner: D,
+ fuzz_tx: FTx,
+ fuzz_rx: FRx,
+}
+
+#[allow(unused)]
+impl<D: Device, FTx: Fuzzer, FRx: Fuzzer> FuzzInjector<D, FTx, FRx> {
+ /// Create a fuzz injector device.
+ pub fn new(inner: D, fuzz_tx: FTx, fuzz_rx: FRx) -> FuzzInjector<D, FTx, FRx> {
+ FuzzInjector {
+ inner,
+ fuzz_tx,
+ fuzz_rx,
+ }
+ }
+
+ /// Return the underlying device, consuming the fuzz injector.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+}
+
+impl<D: Device, FTx, FRx> Device for FuzzInjector<D, FTx, FRx>
+where
+ FTx: Fuzzer,
+ FRx: Fuzzer,
+{
+ type RxToken<'a> = RxToken<'a, D::RxToken<'a>, FRx>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>, FTx>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = self.inner.capabilities();
+ if caps.max_transmission_unit > MTU {
+ caps.max_transmission_unit = MTU;
+ }
+ caps
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
+ let rx = RxToken {
+ fuzzer: &mut self.fuzz_rx,
+ token: rx_token,
+ };
+ let tx = TxToken {
+ fuzzer: &mut self.fuzz_tx,
+ token: tx_token,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ self.inner.transmit(timestamp).map(|token| TxToken {
+ fuzzer: &mut self.fuzz_tx,
+ token: token,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a> {
+ fuzzer: &'a F,
+ token: Rx,
+}
+
+impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(|buffer| {
+ self.fuzzer.fuzz_packet(buffer);
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken, F: Fuzzer + 'a> {
+ fuzzer: &'a F,
+ token: Tx,
+}
+
+impl<'a, Tx: phy::TxToken, FTx: Fuzzer> phy::TxToken for TxToken<'a, Tx, FTx> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buf| {
+ let result = f(buf);
+ self.fuzzer.fuzz_packet(buf);
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
diff --git a/src/phy/loopback.rs b/src/phy/loopback.rs
new file mode 100644
index 0000000..1f57c0c
--- /dev/null
+++ b/src/phy/loopback.rs
@@ -0,0 +1,88 @@
+use alloc::collections::VecDeque;
+use alloc::vec::Vec;
+
+use crate::phy::{self, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A loopback device.
+#[derive(Debug)]
+pub struct Loopback {
+ pub(crate) queue: VecDeque<Vec<u8>>,
+ medium: Medium,
+}
+
+#[allow(clippy::new_without_default)]
+impl Loopback {
+ /// Creates a loopback device.
+ ///
+ /// Every packet transmitted through this device will be received through it
+ /// in FIFO order.
+ pub fn new(medium: Medium) -> Loopback {
+ Loopback {
+ queue: VecDeque::new(),
+ medium,
+ }
+ }
+}
+
+impl Device for Loopback {
+ type RxToken<'a> = RxToken;
+ type TxToken<'a> = TxToken<'a>;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: 65535,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ self.queue.pop_front().map(move |buffer| {
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ queue: &mut self.queue,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ queue: &mut self.queue,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer)
+ }
+}
+
+#[doc(hidden)]
+#[derive(Debug)]
+pub struct TxToken<'a> {
+ queue: &'a mut VecDeque<Vec<u8>>,
+}
+
+impl<'a> phy::TxToken for TxToken<'a> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut buffer = Vec::new();
+ buffer.resize(len, 0);
+ let result = f(&mut buffer);
+ self.queue.push_back(buffer);
+ result
+ }
+}
diff --git a/src/phy/mod.rs b/src/phy/mod.rs
new file mode 100644
index 0000000..c3845d9
--- /dev/null
+++ b/src/phy/mod.rs
@@ -0,0 +1,398 @@
+/*! Access to networking hardware.
+
+The `phy` module deals with the *network devices*. It provides a trait
+for transmitting and receiving frames, [Device](trait.Device.html)
+and implementations of it:
+
+ * the [_loopback_](struct.Loopback.html), for zero dependency testing;
+ * _middleware_ [Tracer](struct.Tracer.html) and
+ [FaultInjector](struct.FaultInjector.html), to facilitate debugging;
+ * _adapters_ [RawSocket](struct.RawSocket.html) and
+ [TunTapInterface](struct.TunTapInterface.html), to transmit and receive frames
+ on the host OS.
+*/
+#![cfg_attr(
+ feature = "medium-ethernet",
+ doc = r##"
+# Examples
+
+An implementation of the [Device](trait.Device.html) trait for a simple hardware
+Ethernet controller could look as follows:
+
+```rust
+use smoltcp::phy::{self, DeviceCapabilities, Device, Medium};
+use smoltcp::time::Instant;
+
+struct StmPhy {
+ rx_buffer: [u8; 1536],
+ tx_buffer: [u8; 1536],
+}
+
+impl<'a> StmPhy {
+ fn new() -> StmPhy {
+ StmPhy {
+ rx_buffer: [0; 1536],
+ tx_buffer: [0; 1536],
+ }
+ }
+}
+
+impl phy::Device for StmPhy {
+ type RxToken<'a> = StmPhyRxToken<'a> where Self: 'a;
+ type TxToken<'a> = StmPhyTxToken<'a> where Self: 'a;
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ Some((StmPhyRxToken(&mut self.rx_buffer[..]),
+ StmPhyTxToken(&mut self.tx_buffer[..])))
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(StmPhyTxToken(&mut self.tx_buffer[..]))
+ }
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = DeviceCapabilities::default();
+ caps.max_transmission_unit = 1536;
+ caps.max_burst_size = Some(1);
+ caps.medium = Medium::Ethernet;
+ caps
+ }
+}
+
+struct StmPhyRxToken<'a>(&'a mut [u8]);
+
+impl<'a> phy::RxToken for StmPhyRxToken<'a> {
+ fn consume<R, F>(mut self, f: F) -> R
+ where F: FnOnce(&mut [u8]) -> R
+ {
+ // TODO: receive packet into buffer
+ let result = f(&mut self.0);
+ println!("rx called");
+ result
+ }
+}
+
+struct StmPhyTxToken<'a>(&'a mut [u8]);
+
+impl<'a> phy::TxToken for StmPhyTxToken<'a> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where F: FnOnce(&mut [u8]) -> R
+ {
+ let result = f(&mut self.0[..len]);
+ println!("tx called {}", len);
+ // TODO: send packet out
+ result
+ }
+}
+```
+"##
+)]
+
+use crate::time::Instant;
+
+#[cfg(all(
+ any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
+ unix
+))]
+mod sys;
+
+mod fault_injector;
+mod fuzz_injector;
+#[cfg(feature = "alloc")]
+mod loopback;
+mod pcap_writer;
+#[cfg(all(feature = "phy-raw_socket", unix))]
+mod raw_socket;
+mod tracer;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+mod tuntap_interface;
+
+#[cfg(all(
+ any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
+ unix
+))]
+pub use self::sys::wait;
+
+pub use self::fault_injector::FaultInjector;
+pub use self::fuzz_injector::{FuzzInjector, Fuzzer};
+#[cfg(feature = "alloc")]
+pub use self::loopback::Loopback;
+pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter};
+#[cfg(all(feature = "phy-raw_socket", unix))]
+pub use self::raw_socket::RawSocket;
+pub use self::tracer::Tracer;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::tuntap_interface::TunTapInterface;
+
+/// Metadata associated to a packet.
+///
+/// The packet metadata is a set of attributes associated to network packets
+/// as they travel up or down the stack. The metadata is get/set by the
+/// [`Device`] implementations or by the user when sending/receiving packets from a
+/// socket.
+///
+/// Metadata fields are enabled via Cargo features. If no field is enabled, this
+/// struct becomes zero-sized, which allows the compiler to optimize it out as if
+/// the packet metadata mechanism didn't exist at all.
+///
+/// Currently only UDP sockets allow setting/retrieving packet metadata. The metadata
+/// for packets emitted with other sockets will be all default values.
+///
+/// This struct is marked as `#[non_exhaustive]`. This means it is not possible to
+/// create it directly by specifying all fields. You have to instead create it with
+/// default values and then set the fields you want. This makes adding metadata
+/// fields a non-breaking change.
+///
+/// ```rust
+/// let mut meta = smoltcp::phy::PacketMeta::default();
+/// #[cfg(feature = "packetmeta-id")]
+/// {
+/// meta.id = 15;
+/// }
+/// ```
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
+#[non_exhaustive]
+pub struct PacketMeta {
+ #[cfg(feature = "packetmeta-id")]
+ pub id: u32,
+}
+
+/// A description of checksum behavior for a particular protocol.
+#[derive(Debug, Clone, Copy, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Checksum {
+ /// Verify checksum when receiving and compute checksum when sending.
+ #[default]
+ Both,
+ /// Verify checksum when receiving.
+ Rx,
+ /// Compute checksum before sending.
+ Tx,
+ /// Ignore checksum completely.
+ None,
+}
+
+impl Checksum {
+ /// Returns whether checksum should be verified when receiving.
+ pub fn rx(&self) -> bool {
+ match *self {
+ Checksum::Both | Checksum::Rx => true,
+ _ => false,
+ }
+ }
+
+ /// Returns whether checksum should be verified when sending.
+ pub fn tx(&self) -> bool {
+ match *self {
+ Checksum::Both | Checksum::Tx => true,
+ _ => false,
+ }
+ }
+}
+
+/// A description of checksum behavior for every supported protocol.
+#[derive(Debug, Clone, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub struct ChecksumCapabilities {
+ pub ipv4: Checksum,
+ pub udp: Checksum,
+ pub tcp: Checksum,
+ #[cfg(feature = "proto-ipv4")]
+ pub icmpv4: Checksum,
+ #[cfg(feature = "proto-ipv6")]
+ pub icmpv6: Checksum,
+}
+
+impl ChecksumCapabilities {
+ /// Checksum behavior that results in not computing or verifying checksums
+ /// for any of the supported protocols.
+ pub fn ignored() -> Self {
+ ChecksumCapabilities {
+ ipv4: Checksum::None,
+ udp: Checksum::None,
+ tcp: Checksum::None,
+ #[cfg(feature = "proto-ipv4")]
+ icmpv4: Checksum::None,
+ #[cfg(feature = "proto-ipv6")]
+ icmpv6: Checksum::None,
+ }
+ }
+}
+
+/// A description of device capabilities.
+///
+/// Higher-level protocols may achieve higher throughput or lower latency if they consider
+/// the bandwidth or packet size limitations.
+#[derive(Debug, Clone, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub struct DeviceCapabilities {
+ /// Medium of the device.
+ ///
+ /// This indicates what kind of packet the sent/received bytes are, and determines
+ /// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
+ /// for Ethernet mediums.
+ pub medium: Medium,
+
+ /// Maximum transmission unit.
+ ///
+ /// The network device is unable to send or receive frames larger than the value returned
+ /// by this function.
+ ///
+ /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but
+ /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14.
+ ///
+ /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet
+ /// devices. This is a common source of confusion.
+ ///
+ /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets.
+ pub max_transmission_unit: usize,
+
+ /// Maximum burst size, in terms of MTU.
+ ///
+ /// The network device is unable to send or receive bursts large than the value returned
+ /// by this function.
+ ///
+ /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are
+ /// dynamically allocated.
+ pub max_burst_size: Option<usize>,
+
+ /// Checksum behavior.
+ ///
+ /// If the network device is capable of verifying or computing checksums for some protocols,
+ /// it can request that the stack not do so in software to improve performance.
+ pub checksum: ChecksumCapabilities,
+}
+
+impl DeviceCapabilities {
+ pub fn ip_mtu(&self) -> usize {
+ match self.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ self.max_transmission_unit - crate::wire::EthernetFrame::<&[u8]>::header_len()
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => self.max_transmission_unit,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802
+ }
+ }
+}
+
+/// Type of medium of a device.
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Medium {
+ /// Ethernet medium. Devices of this type send and receive Ethernet frames,
+ /// and interfaces using it must do neighbor discovery via ARP or NDISC.
+ ///
+ /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
+ #[cfg(feature = "medium-ethernet")]
+ Ethernet,
+
+ /// IP medium. Devices of this type send and receive IP frames, without an
+ /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
+ ///
+ /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
+ #[cfg(feature = "medium-ip")]
+ Ip,
+
+ #[cfg(feature = "medium-ieee802154")]
+ Ieee802154,
+}
+
+impl Default for Medium {
+ fn default() -> Medium {
+ #[cfg(feature = "medium-ethernet")]
+ return Medium::Ethernet;
+ #[cfg(all(feature = "medium-ip", not(feature = "medium-ethernet")))]
+ return Medium::Ip;
+ #[cfg(all(
+ feature = "medium-ieee802154",
+ not(feature = "medium-ip"),
+ not(feature = "medium-ethernet")
+ ))]
+ return Medium::Ieee802154;
+ #[cfg(all(
+ not(feature = "medium-ip"),
+ not(feature = "medium-ethernet"),
+ not(feature = "medium-ieee802154")
+ ))]
+ return panic!("No medium enabled");
+ }
+}
+
+/// An interface for sending and receiving raw network frames.
+///
+/// The interface is based on _tokens_, which are types that allow to receive/transmit a
+/// single packet. The `receive` and `transmit` functions only construct such tokens, the
+/// real sending/receiving operation are performed when the tokens are consumed.
+pub trait Device {
+ type RxToken<'a>: RxToken
+ where
+ Self: 'a;
+ type TxToken<'a>: TxToken
+ where
+ Self: 'a;
+
+ /// Construct a token pair consisting of one receive token and one transmit token.
+ ///
+ /// The additional transmit token makes it possible to generate a reply packet based
+ /// on the contents of the received packet. For example, this makes it possible to
+ /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes
+ /// need to be sent back, without heap allocation.
+ ///
+ /// The timestamp must be a number of milliseconds, monotonically increasing since an
+ /// arbitrary moment in time, such as system startup.
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
+
+ /// Construct a transmit token.
+ ///
+ /// The timestamp must be a number of milliseconds, monotonically increasing since an
+ /// arbitrary moment in time, such as system startup.
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>>;
+
+ /// Get a description of device capabilities.
+ fn capabilities(&self) -> DeviceCapabilities;
+}
+
+/// A token to receive a single network packet.
+pub trait RxToken {
+ /// Consumes the token to receive a single network packet.
+ ///
+ /// This method receives a packet and then calls the given closure `f` with the raw
+ /// packet bytes as argument.
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R;
+
+ /// The Packet ID associated with the frame received by this [`RxToken`]
+ fn meta(&self) -> PacketMeta {
+ PacketMeta::default()
+ }
+}
+
+/// A token to transmit a single network packet.
+pub trait TxToken {
+ /// Consumes the token to send a single network packet.
+ ///
+ /// This method constructs a transmit buffer of size `len` and calls the passed
+ /// closure `f` with a mutable reference to that buffer. The closure should construct
+ /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure
+ /// returns, the transmit buffer is sent out.
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R;
+
+ /// The Packet ID to be associated with the frame to be transmitted by this [`TxToken`].
+ #[allow(unused_variables)]
+ fn set_meta(&mut self, meta: PacketMeta) {}
+}
diff --git a/src/phy/pcap_writer.rs b/src/phy/pcap_writer.rs
new file mode 100644
index 0000000..aadf2a2
--- /dev/null
+++ b/src/phy/pcap_writer.rs
@@ -0,0 +1,268 @@
+use byteorder::{ByteOrder, NativeEndian};
+use core::cell::RefCell;
+use phy::Medium;
+#[cfg(feature = "std")]
+use std::io::Write;
+
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::Instant;
+
+enum_with_unknown! {
+ /// Captured packet header type.
+ pub enum PcapLinkType(u32) {
+ /// Ethernet frames
+ Ethernet = 1,
+ /// IPv4 or IPv6 packets (depending on the version field)
+ Ip = 101,
+ /// IEEE 802.15.4 packets without FCS.
+ Ieee802154WithoutFcs = 230,
+ }
+}
+
+/// Packet capture mode.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum PcapMode {
+ /// Capture both received and transmitted packets.
+ Both,
+ /// Capture only received packets.
+ RxOnly,
+ /// Capture only transmitted packets.
+ TxOnly,
+}
+
+/// A packet capture sink.
+pub trait PcapSink {
+ /// Write data into the sink.
+ fn write(&mut self, data: &[u8]);
+
+ /// Flush data written into the sync.
+ fn flush(&mut self) {}
+
+ /// Write an `u16` into the sink, in native byte order.
+ fn write_u16(&mut self, value: u16) {
+ let mut bytes = [0u8; 2];
+ NativeEndian::write_u16(&mut bytes, value);
+ self.write(&bytes[..])
+ }
+
+ /// Write an `u32` into the sink, in native byte order.
+ fn write_u32(&mut self, value: u32) {
+ let mut bytes = [0u8; 4];
+ NativeEndian::write_u32(&mut bytes, value);
+ self.write(&bytes[..])
+ }
+
+ /// Write the libpcap global header into the sink.
+ ///
+ /// This method may be overridden e.g. if special synchronization is necessary.
+ fn global_header(&mut self, link_type: PcapLinkType) {
+ self.write_u32(0xa1b2c3d4); // magic number
+ self.write_u16(2); // major version
+ self.write_u16(4); // minor version
+ self.write_u32(0); // timezone (= UTC)
+ self.write_u32(0); // accuracy (not used)
+ self.write_u32(65535); // maximum packet length
+ self.write_u32(link_type.into()); // link-layer header type
+ }
+
+ /// Write the libpcap packet header into the sink.
+ ///
+ /// See also the note for [global_header](#method.global_header).
+ ///
+ /// # Panics
+ /// This function panics if `length` is greater than 65535.
+ fn packet_header(&mut self, timestamp: Instant, length: usize) {
+ assert!(length <= 65535);
+
+ self.write_u32(timestamp.secs() as u32); // timestamp seconds
+ self.write_u32(timestamp.micros() as u32); // timestamp microseconds
+ self.write_u32(length as u32); // captured length
+ self.write_u32(length as u32); // original length
+ }
+
+ /// Write the libpcap packet header followed by packet data into the sink.
+ ///
+ /// See also the note for [global_header](#method.global_header).
+ fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
+ self.packet_header(timestamp, packet.len());
+ self.write(packet);
+ self.flush();
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T: Write> PcapSink for T {
+ fn write(&mut self, data: &[u8]) {
+ T::write_all(self, data).expect("cannot write")
+ }
+
+ fn flush(&mut self) {
+ T::flush(self).expect("cannot flush")
+ }
+}
+
+/// A packet capture writer device.
+///
+/// Every packet transmitted or received through this device is timestamped
+/// and written (in the [libpcap] format) using the provided [sink].
+/// Note that writes are fine-grained, and buffering is recommended.
+///
+/// The packet sink should be cheaply cloneable, as it is cloned on every
+/// transmitted packet. For example, `&'a mut Vec<u8>` is cheaply cloneable
+/// but `&std::io::File`
+///
+/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
+/// [sink]: trait.PcapSink.html
+#[derive(Debug)]
+pub struct PcapWriter<D, S>
+where
+ D: Device,
+ S: PcapSink,
+{
+ lower: D,
+ sink: RefCell<S>,
+ mode: PcapMode,
+}
+
+impl<D: Device, S: PcapSink> PcapWriter<D, S> {
+ /// Creates a packet capture writer.
+ pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
+ let medium = lower.capabilities().medium;
+ let link_type = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => PcapLinkType::Ip,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => PcapLinkType::Ethernet,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => PcapLinkType::Ieee802154WithoutFcs,
+ };
+ sink.global_header(link_type);
+ PcapWriter {
+ lower,
+ sink: RefCell::new(sink),
+ mode,
+ }
+ }
+
+ /// Get a reference to the underlying device.
+ ///
+ /// Even if the device offers reading through a standard reference, it is inadvisable to
+ /// directly read from the device as doing so will circumvent the packet capture.
+ pub fn get_ref(&self) -> &D {
+ &self.lower
+ }
+
+ /// Get a mutable reference to the underlying device.
+ ///
+ /// It is inadvisable to directly read from the device as doing so will circumvent the packet capture.
+ pub fn get_mut(&mut self) -> &mut D {
+ &mut self.lower
+ }
+}
+
+impl<D: Device, S> Device for PcapWriter<D, S>
+where
+ S: PcapSink,
+{
+ type RxToken<'a> = RxToken<'a, D::RxToken<'a>, S>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>, S>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ self.lower.capabilities()
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let sink = &self.sink;
+ let mode = self.mode;
+ self.lower
+ .receive(timestamp)
+ .map(move |(rx_token, tx_token)| {
+ let rx = RxToken {
+ token: rx_token,
+ sink,
+ mode,
+ timestamp,
+ };
+ let tx = TxToken {
+ token: tx_token,
+ sink,
+ mode,
+ timestamp,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ let sink = &self.sink;
+ let mode = self.mode;
+ self.lower.transmit(timestamp).map(move |token| TxToken {
+ token,
+ sink,
+ mode,
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
+ token: Rx,
+ sink: &'a RefCell<S>,
+ mode: PcapMode,
+ timestamp: Instant,
+}
+
+impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
+ fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, f: F) -> R {
+ self.token.consume(|buffer| {
+ match self.mode {
+ PcapMode::Both | PcapMode::RxOnly => self
+ .sink
+ .borrow_mut()
+ .packet(self.timestamp, buffer.as_ref()),
+ PcapMode::TxOnly => (),
+ }
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
+ token: Tx,
+ sink: &'a RefCell<S>,
+ mode: PcapMode,
+ timestamp: Instant,
+}
+
+impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buffer| {
+ let result = f(buffer);
+ match self.mode {
+ PcapMode::Both | PcapMode::TxOnly => {
+ self.sink.borrow_mut().packet(self.timestamp, buffer)
+ }
+ PcapMode::RxOnly => (),
+ };
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
diff --git a/src/phy/raw_socket.rs b/src/phy/raw_socket.rs
new file mode 100644
index 0000000..19c5b98
--- /dev/null
+++ b/src/phy/raw_socket.rs
@@ -0,0 +1,137 @@
+use std::cell::RefCell;
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::rc::Rc;
+use std::vec::Vec;
+
+use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A socket that captures or transmits the complete frame.
+#[derive(Debug)]
+pub struct RawSocket {
+ medium: Medium,
+ lower: Rc<RefCell<sys::RawSocketDesc>>,
+ mtu: usize,
+}
+
+impl AsRawFd for RawSocket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower.borrow().as_raw_fd()
+ }
+}
+
+impl RawSocket {
+ /// Creates a raw socket, bound to the interface called `name`.
+ ///
+ /// This requires superuser privileges or a corresponding capability bit
+ /// set on the executable.
+ pub fn new(name: &str, medium: Medium) -> io::Result<RawSocket> {
+ let mut lower = sys::RawSocketDesc::new(name, medium)?;
+ lower.bind_interface()?;
+
+ let mut mtu = lower.interface_mtu()?;
+
+ #[cfg(feature = "medium-ieee802154")]
+ if medium == Medium::Ieee802154 {
+ // SIOCGIFMTU returns 127 - (ACK_PSDU - FCS - 1) - FCS.
+ // 127 - (5 - 2 - 1) - 2 = 123
+ // For IEEE802154, we want to add (ACK_PSDU - FCS - 1), since that is what SIOCGIFMTU
+ // uses as the size of the link layer header.
+ //
+ // https://github.com/torvalds/linux/blob/7475e51b87969e01a6812eac713a1c8310372e8a/net/mac802154/iface.c#L541
+ mtu += 2;
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ if medium == Medium::Ethernet {
+ // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
+ // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
+ mtu += crate::wire::EthernetFrame::<&[u8]>::header_len()
+ }
+
+ Ok(RawSocket {
+ medium,
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ })
+ }
+}
+
+impl Device for RawSocket {
+ type RxToken<'a> = RxToken
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: self.mtu,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; self.mtu];
+ match lower.recv(&mut buffer[..]) {
+ Ok(size) => {
+ buffer.resize(size, 0);
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ lower: self.lower.clone(),
+ };
+ Some((rx, tx))
+ }
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => None,
+ Err(err) => panic!("{}", err),
+ }
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ lower: self.lower.clone(),
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer[..])
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken {
+ lower: Rc<RefCell<sys::RawSocketDesc>>,
+}
+
+impl phy::TxToken for TxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; len];
+ let result = f(&mut buffer);
+ match lower.send(&buffer[..]) {
+ Ok(_) => {}
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
+ net_debug!("phy: tx failed due to WouldBlock")
+ }
+ Err(err) => panic!("{}", err),
+ }
+ result
+ }
+}
diff --git a/src/phy/sys/bpf.rs b/src/phy/sys/bpf.rs
new file mode 100644
index 0000000..7e65b98
--- /dev/null
+++ b/src/phy/sys/bpf.rs
@@ -0,0 +1,180 @@
+use std::io;
+use std::mem;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+use libc;
+
+use super::{ifreq, ifreq_for};
+use crate::phy::Medium;
+use crate::wire::ETHERNET_HEADER_LEN;
+
+/// set interface
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCSETIF: libc::c_ulong = 0x8020426c;
+/// get buffer length
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCGBLEN: libc::c_ulong = 0x40044266;
+/// set immediate/nonblocking read
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
+/// set bpf_hdr struct size
+#[cfg(target_os = "macos")]
+const SIZEOF_BPF_HDR: usize = 18;
+/// set bpf_hdr struct size
+#[cfg(target_os = "openbsd")]
+const SIZEOF_BPF_HDR: usize = 24;
+/// The actual header length may be larger than the bpf_hdr struct due to aligning
+/// see https://github.com/openbsd/src/blob/37ecb4d066e5566411cc16b362d3960c93b1d0be/sys/net/bpf.c#L1649
+/// and https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/net/bpf.c#L3580
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::<u32>() - 1)
+ & !(mem::align_of::<u32>() - 1))
+ - ETHERNET_HEADER_LEN;
+
+macro_rules! try_ioctl {
+ ($fd:expr,$cmd:expr,$req:expr) => {
+ unsafe {
+ if libc::ioctl($fd, $cmd, $req) == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ };
+}
+
+#[derive(Debug)]
+pub struct BpfDevice {
+ fd: libc::c_int,
+ ifreq: ifreq,
+}
+
+impl AsRawFd for BpfDevice {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+fn open_device() -> io::Result<libc::c_int> {
+ unsafe {
+ for i in 0..256 {
+ let dev = format!("/dev/bpf{}\0", i);
+ match libc::open(
+ dev.as_ptr() as *const libc::c_char,
+ libc::O_RDWR | libc::O_NONBLOCK,
+ ) {
+ -1 => continue,
+ fd => return Ok(fd),
+ };
+ }
+ }
+ // at this point, all 256 BPF devices were busy and we weren't able to open any
+ Err(io::Error::last_os_error())
+}
+
+impl BpfDevice {
+ pub fn new(name: &str, _medium: Medium) -> io::Result<BpfDevice> {
+ Ok(BpfDevice {
+ fd: open_device()?,
+ ifreq: ifreq_for(name),
+ })
+ }
+
+ pub fn bind_interface(&mut self) -> io::Result<()> {
+ try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq);
+
+ Ok(())
+ }
+
+ /// This in fact does not return the interface's mtu,
+ /// but it returns the size of the buffer that the app needs to allocate
+ /// for the BPF device
+ ///
+ /// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround
+ /// to get the actual interface mtu, but this should work better
+ ///
+ /// To get the interface MTU, you would need to create a raw socket first,
+ /// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to.
+ /// This MTU that you would get would not include the length of `struct bpf_hdr`
+ /// which gets prepended to every packet by BPF,
+ /// and your packet will be truncated if it has the length of the MTU.
+ ///
+ /// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes.
+ /// You could do something like `mtu += BPF_HDRLEN`,
+ /// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly,
+ /// and you must set it before setting the interface with the `BIOCSETIF` ioctl.
+ ///
+ /// The reason I said this should work better is because you might see some unexpected behavior,
+ /// truncated/unaligned packets, I/O errors on read()
+ /// if you change the buffer size to the actual MTU of the interface.
+ pub fn interface_mtu(&mut self) -> io::Result<usize> {
+ let mut bufsize: libc::c_int = 1;
+ try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
+ try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int);
+
+ Ok(bufsize as usize)
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::read(
+ self.fd,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ );
+
+ if len == -1 || len < BPF_HDRLEN as isize {
+ return Err(io::Error::last_os_error());
+ }
+
+ let len = len as usize;
+
+ libc::memmove(
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ &buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void,
+ len - BPF_HDRLEN,
+ );
+
+ Ok(len)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::write(
+ self.fd,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ );
+
+ if len == -1 {
+ Err(io::Error::last_os_error()).unwrap()
+ }
+
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for BpfDevice {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.fd);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ #[cfg(target_os = "macos")]
+ fn test_aligned_bpf_hdr_len() {
+ assert_eq!(18, BPF_HDRLEN);
+ }
+
+ #[test]
+ #[cfg(target_os = "openbsd")]
+ fn test_aligned_bpf_hdr_len() {
+ assert_eq!(26, BPF_HDRLEN);
+ }
+}
diff --git a/src/phy/sys/linux.rs b/src/phy/sys/linux.rs
new file mode 100644
index 0000000..c73eb4f
--- /dev/null
+++ b/src/phy/sys/linux.rs
@@ -0,0 +1,26 @@
+#![allow(unused)]
+
+pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
+pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
+pub const ETH_P_ALL: libc::c_short = 0x0003;
+pub const ETH_P_IEEE802154: libc::c_short = 0x00F6;
+
+// Constant definition as per
+// https://github.com/golang/sys/blob/master/unix/zerrors_linux_<arch>.go
+pub const TUNSETIFF: libc::c_ulong = if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "mips64el",
+ target_arch = "mipsel",
+ target_arch = "powerpc",
+ target_arch = "powerpc64",
+ target_arch = "powerpc64le",
+ target_arch = "sparc64"
+)) {
+ 0x800454CA
+} else {
+ 0x400454CA
+};
+pub const IFF_TUN: libc::c_int = 0x0001;
+pub const IFF_TAP: libc::c_int = 0x0002;
+pub const IFF_NO_PI: libc::c_int = 0x1000;
diff --git a/src/phy/sys/mod.rs b/src/phy/sys/mod.rs
new file mode 100644
index 0000000..3f42301
--- /dev/null
+++ b/src/phy/sys/mod.rs
@@ -0,0 +1,136 @@
+#![allow(unsafe_code)]
+
+use crate::time::Duration;
+use std::os::unix::io::RawFd;
+use std::{io, mem, ptr};
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[path = "linux.rs"]
+mod imp;
+
+#[cfg(all(
+ feature = "phy-raw_socket",
+ not(any(target_os = "linux", target_os = "android")),
+ unix
+))]
+pub mod bpf;
+#[cfg(all(
+ feature = "phy-raw_socket",
+ any(target_os = "linux", target_os = "android")
+))]
+pub mod raw_socket;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub mod tuntap_interface;
+
+#[cfg(all(
+ feature = "phy-raw_socket",
+ not(any(target_os = "linux", target_os = "android")),
+ unix
+))]
+pub use self::bpf::BpfDevice as RawSocketDesc;
+#[cfg(all(
+ feature = "phy-raw_socket",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::raw_socket::RawSocketDesc;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::tuntap_interface::TunTapInterfaceDesc;
+
+/// Wait until given file descriptor becomes readable, but no longer than given timeout.
+pub fn wait(fd: RawFd, duration: Option<Duration>) -> io::Result<()> {
+ unsafe {
+ let mut readfds = {
+ let mut readfds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(readfds.as_mut_ptr());
+ libc::FD_SET(fd, readfds.as_mut_ptr());
+ readfds.assume_init()
+ };
+
+ let mut writefds = {
+ let mut writefds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(writefds.as_mut_ptr());
+ writefds.assume_init()
+ };
+
+ let mut exceptfds = {
+ let mut exceptfds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(exceptfds.as_mut_ptr());
+ exceptfds.assume_init()
+ };
+
+ let mut timeout = libc::timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ };
+ let timeout_ptr = if let Some(duration) = duration {
+ timeout.tv_sec = duration.secs() as libc::time_t;
+ timeout.tv_usec = (duration.millis() * 1_000) as libc::suseconds_t;
+ &mut timeout as *mut _
+ } else {
+ ptr::null_mut()
+ };
+
+ let res = libc::select(
+ fd + 1,
+ &mut readfds,
+ &mut writefds,
+ &mut exceptfds,
+ timeout_ptr,
+ );
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+ }
+}
+
+#[cfg(all(
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
+ unix
+))]
+#[repr(C)]
+#[derive(Debug)]
+struct ifreq {
+ ifr_name: [libc::c_char; libc::IF_NAMESIZE],
+ ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
+}
+
+#[cfg(all(
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
+ unix
+))]
+fn ifreq_for(name: &str) -> ifreq {
+ let mut ifreq = ifreq {
+ ifr_name: [0; libc::IF_NAMESIZE],
+ ifr_data: 0,
+ };
+ for (i, byte) in name.as_bytes().iter().enumerate() {
+ ifreq.ifr_name[i] = *byte as libc::c_char
+ }
+ ifreq
+}
+
+#[cfg(all(
+ any(target_os = "linux", target_os = "android"),
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket")
+))]
+fn ifreq_ioctl(
+ lower: libc::c_int,
+ ifreq: &mut ifreq,
+ cmd: libc::c_ulong,
+) -> io::Result<libc::c_int> {
+ unsafe {
+ let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(ifreq.ifr_data)
+}
diff --git a/src/phy/sys/raw_socket.rs b/src/phy/sys/raw_socket.rs
new file mode 100644
index 0000000..f37fe96
--- /dev/null
+++ b/src/phy/sys/raw_socket.rs
@@ -0,0 +1,115 @@
+use super::*;
+use crate::phy::Medium;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::{io, mem};
+
+#[derive(Debug)]
+pub struct RawSocketDesc {
+ protocol: libc::c_short,
+ lower: libc::c_int,
+ ifreq: ifreq,
+}
+
+impl AsRawFd for RawSocketDesc {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower
+ }
+}
+
+impl RawSocketDesc {
+ pub fn new(name: &str, medium: Medium) -> io::Result<RawSocketDesc> {
+ let protocol = match medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => imp::ETH_P_ALL,
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => imp::ETH_P_ALL,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => imp::ETH_P_IEEE802154,
+ };
+
+ let lower = unsafe {
+ let lower = libc::socket(
+ libc::AF_PACKET,
+ libc::SOCK_RAW | libc::SOCK_NONBLOCK,
+ protocol.to_be() as i32,
+ );
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ Ok(RawSocketDesc {
+ protocol,
+ lower,
+ ifreq: ifreq_for(name),
+ })
+ }
+
+ pub fn interface_mtu(&mut self) -> io::Result<usize> {
+ ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize)
+ }
+
+ pub fn bind_interface(&mut self) -> io::Result<()> {
+ let sockaddr = libc::sockaddr_ll {
+ sll_family: libc::AF_PACKET as u16,
+ sll_protocol: self.protocol.to_be() as u16,
+ sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?,
+ sll_hatype: 1,
+ sll_pkttype: 0,
+ sll_halen: 6,
+ sll_addr: [0; 8],
+ };
+
+ unsafe {
+ let res = libc::bind(
+ self.lower,
+ &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
+ mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
+ );
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::recv(
+ self.lower,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ 0,
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::send(
+ self.lower,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ 0,
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for RawSocketDesc {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.lower);
+ }
+ }
+}
diff --git a/src/phy/sys/tuntap_interface.rs b/src/phy/sys/tuntap_interface.rs
new file mode 100644
index 0000000..3019cad
--- /dev/null
+++ b/src/phy/sys/tuntap_interface.rs
@@ -0,0 +1,130 @@
+use super::*;
+use crate::{phy::Medium, wire::EthernetFrame};
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+#[derive(Debug)]
+pub struct TunTapInterfaceDesc {
+ lower: libc::c_int,
+ mtu: usize,
+}
+
+impl AsRawFd for TunTapInterfaceDesc {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower
+ }
+}
+
+impl TunTapInterfaceDesc {
+ pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterfaceDesc> {
+ let lower = unsafe {
+ let lower = libc::open(
+ "/dev/net/tun\0".as_ptr() as *const libc::c_char,
+ libc::O_RDWR | libc::O_NONBLOCK,
+ );
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ let mut ifreq = ifreq_for(name);
+ Self::attach_interface_ifreq(lower, medium, &mut ifreq)?;
+ let mtu = Self::mtu_ifreq(medium, &mut ifreq)?;
+
+ Ok(TunTapInterfaceDesc { lower, mtu })
+ }
+
+ pub fn from_fd(fd: RawFd, mtu: usize) -> io::Result<TunTapInterfaceDesc> {
+ Ok(TunTapInterfaceDesc { lower: fd, mtu })
+ }
+
+ fn attach_interface_ifreq(
+ lower: libc::c_int,
+ medium: Medium,
+ ifr: &mut ifreq,
+ ) -> io::Result<()> {
+ let mode = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => imp::IFF_TUN,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => imp::IFF_TAP,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+ ifr.ifr_data = mode | imp::IFF_NO_PI;
+ ifreq_ioctl(lower, ifr, imp::TUNSETIFF).map(|_| ())
+ }
+
+ fn mtu_ifreq(medium: Medium, ifr: &mut ifreq) -> io::Result<usize> {
+ let lower = unsafe {
+ let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ let ip_mtu = ifreq_ioctl(lower, ifr, imp::SIOCGIFMTU).map(|mtu| mtu as usize);
+
+ unsafe {
+ libc::close(lower);
+ }
+
+ // Propagate error after close, to ensure we always close.
+ let ip_mtu = ip_mtu?;
+
+ // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
+ // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
+ let mtu = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => ip_mtu,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => ip_mtu + EthernetFrame::<&[u8]>::header_len(),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+
+ Ok(mtu)
+ }
+
+ pub fn interface_mtu(&self) -> io::Result<usize> {
+ Ok(self.mtu)
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::read(
+ self.lower,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::write(
+ self.lower,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for TunTapInterfaceDesc {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.lower);
+ }
+ }
+}
diff --git a/src/phy/tracer.rs b/src/phy/tracer.rs
new file mode 100644
index 0000000..48e60ec
--- /dev/null
+++ b/src/phy/tracer.rs
@@ -0,0 +1,189 @@
+use core::fmt;
+
+use crate::phy::{self, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+/// A tracer device.
+///
+/// A tracer is a device that pretty prints all packets traversing it
+/// using the provided writer function, and then passes them to another
+/// device.
+pub struct Tracer<D: Device> {
+ inner: D,
+ writer: fn(Instant, Packet),
+}
+
+impl<D: Device> Tracer<D> {
+ /// Create a tracer device.
+ pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer<D> {
+ Tracer { inner, writer }
+ }
+
+ /// Get a reference to the underlying device.
+ ///
+ /// Even if the device offers reading through a standard reference, it is inadvisable to
+ /// directly read from the device as doing so will circumvent the tracing.
+ pub fn get_ref(&self) -> &D {
+ &self.inner
+ }
+
+ /// Get a mutable reference to the underlying device.
+ ///
+ /// It is inadvisable to directly read from the device as doing so will circumvent the tracing.
+ pub fn get_mut(&mut self) -> &mut D {
+ &mut self.inner
+ }
+
+ /// Return the underlying device, consuming the tracer.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+}
+
+impl<D: Device> Device for Tracer<D> {
+ type RxToken<'a> = RxToken<D::RxToken<'a>>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<D::TxToken<'a>>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ self.inner.capabilities()
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let medium = self.inner.capabilities().medium;
+ self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
+ let rx = RxToken {
+ token: rx_token,
+ writer: self.writer,
+ medium,
+ timestamp,
+ };
+ let tx = TxToken {
+ token: tx_token,
+ writer: self.writer,
+ medium,
+ timestamp,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ let medium = self.inner.capabilities().medium;
+ self.inner.transmit(timestamp).map(|tx_token| TxToken {
+ token: tx_token,
+ medium,
+ writer: self.writer,
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<Rx: phy::RxToken> {
+ token: Rx,
+ writer: fn(Instant, Packet),
+ medium: Medium,
+ timestamp: Instant,
+}
+
+impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(|buffer| {
+ (self.writer)(
+ self.timestamp,
+ Packet {
+ buffer,
+ medium: self.medium,
+ prefix: "<- ",
+ },
+ );
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<Tx: phy::TxToken> {
+ token: Tx,
+ writer: fn(Instant, Packet),
+ medium: Medium,
+ timestamp: Instant,
+}
+
+impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buffer| {
+ let result = f(buffer);
+ (self.writer)(
+ self.timestamp,
+ Packet {
+ buffer,
+ medium: self.medium,
+ prefix: "-> ",
+ },
+ );
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
+
+pub struct Packet<'a> {
+ buffer: &'a [u8],
+ medium: Medium,
+ prefix: &'static str,
+}
+
+impl<'a> fmt::Display for Packet<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut indent = PrettyIndent::new(self.prefix);
+ match self.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ ),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) {
+ #[cfg(feature = "proto-ipv4")]
+ Ok(crate::wire::IpVersion::Ipv4) => {
+ crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ )
+ }
+ #[cfg(feature = "proto-ipv6")]
+ Ok(crate::wire::IpVersion::Ipv6) => {
+ crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ )
+ }
+ _ => f.write_str("unrecognized IP version"),
+ },
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => Ok(()), // XXX
+ }
+ }
+}
diff --git a/src/phy/tuntap_interface.rs b/src/phy/tuntap_interface.rs
new file mode 100644
index 0000000..32a28db
--- /dev/null
+++ b/src/phy/tuntap_interface.rs
@@ -0,0 +1,126 @@
+use std::cell::RefCell;
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::rc::Rc;
+use std::vec::Vec;
+
+use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A virtual TUN (IP) or TAP (Ethernet) interface.
+#[derive(Debug)]
+pub struct TunTapInterface {
+ lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
+ mtu: usize,
+ medium: Medium,
+}
+
+impl AsRawFd for TunTapInterface {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower.borrow().as_raw_fd()
+ }
+}
+
+impl TunTapInterface {
+ /// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist.
+ ///
+ /// If `name` is a persistent interface configured with UID of the current user,
+ /// no special privileges are needed. Otherwise, this requires superuser privileges
+ /// or a corresponding capability set on the executable.
+ pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterface> {
+ let lower = sys::TunTapInterfaceDesc::new(name, medium)?;
+ let mtu = lower.interface_mtu()?;
+ Ok(TunTapInterface {
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ medium,
+ })
+ }
+
+ /// Attaches to a TUN/TAP interface specified by file descriptor `fd`.
+ ///
+ /// On platforms like Android, a file descriptor to a tun interface is exposed.
+ /// On these platforms, a TunTapInterface cannot be instantiated with a name.
+ pub fn from_fd(fd: RawFd, medium: Medium, mtu: usize) -> io::Result<TunTapInterface> {
+ let lower = sys::TunTapInterfaceDesc::from_fd(fd, mtu)?;
+ Ok(TunTapInterface {
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ medium,
+ })
+ }
+}
+
+impl Device for TunTapInterface {
+ type RxToken<'a> = RxToken;
+ type TxToken<'a> = TxToken;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: self.mtu,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; self.mtu];
+ match lower.recv(&mut buffer[..]) {
+ Ok(size) => {
+ buffer.resize(size, 0);
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ lower: self.lower.clone(),
+ };
+ Some((rx, tx))
+ }
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => None,
+ Err(err) => panic!("{}", err),
+ }
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ lower: self.lower.clone(),
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer[..])
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken {
+ lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
+}
+
+impl phy::TxToken for TxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; len];
+ let result = f(&mut buffer);
+ match lower.send(&buffer[..]) {
+ Ok(_) => {}
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
+ net_debug!("phy: tx failed due to WouldBlock")
+ }
+ Err(err) => panic!("{}", err),
+ }
+ result
+ }
+}
diff --git a/src/rand.rs b/src/rand.rs
new file mode 100644
index 0000000..15d88f7
--- /dev/null
+++ b/src/rand.rs
@@ -0,0 +1,40 @@
+#![allow(unsafe_code)]
+#![allow(unused)]
+
+#[derive(Debug)]
+pub(crate) struct Rand {
+ state: u64,
+}
+
+impl Rand {
+ pub(crate) const fn new(seed: u64) -> Self {
+ Self { state: seed }
+ }
+
+ pub(crate) fn rand_u32(&mut self) -> u32 {
+ // sPCG32 from https://www.pcg-random.org/paper.html
+ // see also https://nullprogram.com/blog/2017/09/21/
+ const M: u64 = 0xbb2efcec3c39611d;
+ const A: u64 = 0x7590ef39;
+
+ let s = self.state.wrapping_mul(M).wrapping_add(A);
+ self.state = s;
+
+ let shift = 29 - (s >> 61);
+ (s >> shift) as u32
+ }
+
+ pub(crate) fn rand_u16(&mut self) -> u16 {
+ let n = self.rand_u32();
+ (n ^ (n >> 16)) as u16
+ }
+
+ pub(crate) fn rand_source_port(&mut self) -> u16 {
+ loop {
+ let res = self.rand_u16();
+ if res > 1024 {
+ return res;
+ }
+ }
+ }
+}
diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs
new file mode 100644
index 0000000..13ecbd3
--- /dev/null
+++ b/src/socket/dhcpv4.rs
@@ -0,0 +1,1417 @@
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+use crate::iface::Context;
+use crate::time::{Duration, Instant};
+use crate::wire::dhcpv4::field as dhcpv4_field;
+use crate::wire::{
+ DhcpMessageType, DhcpPacket, DhcpRepr, IpAddress, IpProtocol, Ipv4Address, Ipv4Cidr, Ipv4Repr,
+ UdpRepr, DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_SERVER_PORT, UDP_HEADER_LEN,
+};
+use crate::wire::{DhcpOption, HardwareAddress};
+use heapless::Vec;
+
+#[cfg(feature = "async")]
+use super::WakerRegistration;
+
+use super::PollAt;
+
+const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120);
+
+const DEFAULT_PARAMETER_REQUEST_LIST: &[u8] = &[
+ dhcpv4_field::OPT_SUBNET_MASK,
+ dhcpv4_field::OPT_ROUTER,
+ dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
+];
+
+/// IPv4 configuration data provided by the DHCP server.
+#[derive(Debug, Eq, PartialEq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Config<'a> {
+ /// Information on how to reach the DHCP server that responded with DHCP
+ /// configuration.
+ pub server: ServerInfo,
+ /// IP address
+ pub address: Ipv4Cidr,
+ /// Router address, also known as default gateway. Does not necessarily
+ /// match the DHCP server's address.
+ pub router: Option<Ipv4Address>,
+ /// DNS servers
+ pub dns_servers: Vec<Ipv4Address, DHCP_MAX_DNS_SERVER_COUNT>,
+ /// Received DHCP packet
+ pub packet: Option<DhcpPacket<&'a [u8]>>,
+}
+
+/// Information on how to reach a DHCP server.
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ServerInfo {
+ /// IP address to use as destination in outgoing packets
+ pub address: Ipv4Address,
+ /// Server identifier to use in outgoing packets. Usually equal to server_address,
+ /// but may differ in some situations (eg DHCP relays)
+ pub identifier: Ipv4Address,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct DiscoverState {
+ /// When to send next request
+ retry_at: Instant,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct RequestState {
+ /// When to send next request
+ retry_at: Instant,
+ /// How many retries have been done
+ retry: u16,
+ /// Server we're trying to request from
+ server: ServerInfo,
+ /// IP address that we're trying to request.
+ requested_ip: Ipv4Address,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct RenewState {
+ /// Active network config
+ config: Config<'static>,
+
+ /// Renew timer. When reached, we will start attempting
+ /// to renew this lease with the DHCP server.
+ ///
+ /// Must be less or equal than `rebind_at`.
+ renew_at: Instant,
+
+ /// Rebind timer. When reached, we will start broadcasting to renew
+ /// this lease with any DHCP server.
+ ///
+ /// Must be greater than or equal to `renew_at`, and less than or
+ /// equal to `expires_at`.
+ rebind_at: Instant,
+
+ /// Whether the T2 time has elapsed
+ rebinding: bool,
+
+ /// Expiration timer. When reached, this lease is no longer valid, so it must be
+ /// thrown away and the ethernet interface deconfigured.
+ expires_at: Instant,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+enum ClientState {
+ /// Discovering the DHCP server
+ Discovering(DiscoverState),
+ /// Requesting an address
+ Requesting(RequestState),
+ /// Having an address, refresh it periodically.
+ Renewing(RenewState),
+}
+
+/// Timeout and retry configuration.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub struct RetryConfig {
+ pub discover_timeout: Duration,
+ /// The REQUEST timeout doubles every 2 tries.
+ pub initial_request_timeout: Duration,
+ pub request_retries: u16,
+ pub min_renew_timeout: Duration,
+ /// An upper bound on how long to wait between retrying a renew or rebind.
+ ///
+ /// Set this to [`Duration::MAX`] if you don't want to impose an upper bound.
+ pub max_renew_timeout: Duration,
+}
+
+impl Default for RetryConfig {
+ fn default() -> Self {
+ Self {
+ discover_timeout: Duration::from_secs(10),
+ initial_request_timeout: Duration::from_secs(5),
+ request_retries: 5,
+ min_renew_timeout: Duration::from_secs(60),
+ max_renew_timeout: Duration::MAX,
+ }
+ }
+}
+
+/// Return value for the `Dhcpv4Socket::poll` function
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Event<'a> {
+ /// Configuration has been lost (for example, the lease has expired)
+ Deconfigured,
+ /// Configuration has been newly acquired, or modified.
+ Configured(Config<'a>),
+}
+
+#[derive(Debug)]
+pub struct Socket<'a> {
+ /// State of the DHCP client.
+ state: ClientState,
+ /// Set to true on config/state change, cleared back to false by the `config` function.
+ config_changed: bool,
+ /// xid of the last sent message.
+ transaction_id: u32,
+
+ /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration.
+ /// Useful to react faster to IP configuration changes and to test whether renews work correctly.
+ max_lease_duration: Option<Duration>,
+
+ retry_config: RetryConfig,
+
+ /// Ignore NAKs.
+ ignore_naks: bool,
+
+ /// Server port config
+ pub(crate) server_port: u16,
+
+ /// Client port config
+ pub(crate) client_port: u16,
+
+ /// A buffer contains options additional to be added to outgoing DHCP
+ /// packets.
+ outgoing_options: &'a [DhcpOption<'a>],
+ /// A buffer containing all requested parameters.
+ parameter_request_list: Option<&'a [u8]>,
+
+ /// Incoming DHCP packets are copied into this buffer, overwriting the previous.
+ receive_packet_buffer: Option<&'a mut [u8]>,
+
+ /// Waker registration
+ #[cfg(feature = "async")]
+ waker: WakerRegistration,
+}
+
+/// DHCP client socket.
+///
+/// The socket acquires an IP address configuration through DHCP autonomously.
+/// You must query the configuration with `.poll()` after every call to `Interface::poll()`,
+/// and apply the configuration to the `Interface`.
+impl<'a> Socket<'a> {
+ /// Create a DHCPv4 socket
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Socket {
+ state: ClientState::Discovering(DiscoverState {
+ retry_at: Instant::from_millis(0),
+ }),
+ config_changed: true,
+ transaction_id: 1,
+ max_lease_duration: None,
+ retry_config: RetryConfig::default(),
+ ignore_naks: false,
+ outgoing_options: &[],
+ parameter_request_list: None,
+ receive_packet_buffer: None,
+ #[cfg(feature = "async")]
+ waker: WakerRegistration::new(),
+ server_port: DHCP_SERVER_PORT,
+ client_port: DHCP_CLIENT_PORT,
+ }
+ }
+
+ /// Set the retry/timeouts configuration.
+ pub fn set_retry_config(&mut self, config: RetryConfig) {
+ self.retry_config = config;
+ }
+
+ /// Gets the current retry/timeouts configuration
+ pub fn get_retry_config(&self) -> RetryConfig {
+ self.retry_config
+ }
+
+ /// Set the outgoing options.
+ pub fn set_outgoing_options(&mut self, options: &'a [DhcpOption<'a>]) {
+ self.outgoing_options = options;
+ }
+
+ /// Set the buffer into which incoming DHCP packets are copied into.
+ pub fn set_receive_packet_buffer(&mut self, buffer: &'a mut [u8]) {
+ self.receive_packet_buffer = Some(buffer);
+ }
+
+ /// Set the parameter request list.
+ ///
+ /// This should contain at least `OPT_SUBNET_MASK` (`1`), `OPT_ROUTER`
+ /// (`3`), and `OPT_DOMAIN_NAME_SERVER` (`6`).
+ pub fn set_parameter_request_list(&mut self, parameter_request_list: &'a [u8]) {
+ self.parameter_request_list = Some(parameter_request_list);
+ }
+
+ /// Get the configured max lease duration.
+ ///
+ /// See also [`Self::set_max_lease_duration()`]
+ pub fn max_lease_duration(&self) -> Option<Duration> {
+ self.max_lease_duration
+ }
+
+ /// Set the max lease duration.
+ ///
+ /// When set, the lease duration will be capped at the configured duration if the
+ /// DHCP server gives us a longer lease. This is generally not recommended, but
+ /// can be useful for debugging or reacting faster to network configuration changes.
+ ///
+ /// If None, no max is applied (the lease duration from the DHCP server is used.)
+ pub fn set_max_lease_duration(&mut self, max_lease_duration: Option<Duration>) {
+ self.max_lease_duration = max_lease_duration;
+ }
+
+ /// Get whether to ignore NAKs.
+ ///
+ /// See also [`Self::set_ignore_naks()`]
+ pub fn ignore_naks(&self) -> bool {
+ self.ignore_naks
+ }
+
+ /// Set whether to ignore NAKs.
+ ///
+ /// This is not compliant with the DHCP RFCs, since theoretically
+ /// we must stop using the assigned IP when receiving a NAK. This
+ /// can increase reliability on broken networks with buggy routers
+ /// or rogue DHCP servers, however.
+ pub fn set_ignore_naks(&mut self, ignore_naks: bool) {
+ self.ignore_naks = ignore_naks;
+ }
+
+ /// Set the server/client port
+ ///
+ /// Allows you to specify the ports used by DHCP.
+ /// This is meant to support esoteric usecases allowed by the dhclient program.
+ pub fn set_ports(&mut self, server_port: u16, client_port: u16) {
+ self.server_port = server_port;
+ self.client_port = client_port;
+ }
+
+ pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
+ let t = match &self.state {
+ ClientState::Discovering(state) => state.retry_at,
+ ClientState::Requesting(state) => state.retry_at,
+ ClientState::Renewing(state) => if state.rebinding {
+ state.rebind_at
+ } else {
+ state.renew_at.min(state.rebind_at)
+ }
+ .min(state.expires_at),
+ };
+ PollAt::Time(t)
+ }
+
+ pub(crate) fn process(
+ &mut self,
+ cx: &mut Context,
+ ip_repr: &Ipv4Repr,
+ repr: &UdpRepr,
+ payload: &[u8],
+ ) {
+ let src_ip = ip_repr.src_addr;
+
+ // This is enforced in interface.rs.
+ assert!(repr.src_port == self.server_port && repr.dst_port == self.client_port);
+
+ let dhcp_packet = match DhcpPacket::new_checked(payload) {
+ Ok(dhcp_packet) => dhcp_packet,
+ Err(e) => {
+ net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
+ return;
+ }
+ };
+ let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
+ Ok(dhcp_repr) => dhcp_repr,
+ Err(e) => {
+ net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
+ return;
+ }
+ };
+
+ let HardwareAddress::Ethernet(ethernet_addr) = cx.hardware_addr() else {
+ panic!("using DHCPv4 socket with a non-ethernet hardware address.");
+ };
+
+ if dhcp_repr.client_hardware_address != ethernet_addr {
+ return;
+ }
+ if dhcp_repr.transaction_id != self.transaction_id {
+ return;
+ }
+ let server_identifier = match dhcp_repr.server_identifier {
+ Some(server_identifier) => server_identifier,
+ None => {
+ net_debug!(
+ "DHCP ignoring {:?} because missing server_identifier",
+ dhcp_repr.message_type
+ );
+ return;
+ }
+ };
+
+ net_debug!(
+ "DHCP recv {:?} from {}: {:?}",
+ dhcp_repr.message_type,
+ src_ip,
+ dhcp_repr
+ );
+
+ // Copy over the payload into the receive packet buffer.
+ if let Some(buffer) = self.receive_packet_buffer.as_mut() {
+ if let Some(buffer) = buffer.get_mut(..payload.len()) {
+ buffer.copy_from_slice(payload);
+ }
+ }
+
+ match (&mut self.state, dhcp_repr.message_type) {
+ (ClientState::Discovering(_state), DhcpMessageType::Offer) => {
+ if !dhcp_repr.your_ip.is_unicast() {
+ net_debug!("DHCP ignoring OFFER because your_ip is not unicast");
+ return;
+ }
+
+ self.state = ClientState::Requesting(RequestState {
+ retry_at: cx.now(),
+ retry: 0,
+ server: ServerInfo {
+ address: src_ip,
+ identifier: server_identifier,
+ },
+ requested_ip: dhcp_repr.your_ip, // use the offered ip
+ });
+ }
+ (ClientState::Requesting(state), DhcpMessageType::Ack) => {
+ if let Some((config, renew_at, rebind_at, expires_at)) =
+ Self::parse_ack(cx.now(), &dhcp_repr, self.max_lease_duration, state.server)
+ {
+ self.state = ClientState::Renewing(RenewState {
+ config,
+ renew_at,
+ rebind_at,
+ expires_at,
+ rebinding: false,
+ });
+ self.config_changed();
+ }
+ }
+ (ClientState::Requesting(_), DhcpMessageType::Nak) => {
+ if !self.ignore_naks {
+ self.reset();
+ }
+ }
+ (ClientState::Renewing(state), DhcpMessageType::Ack) => {
+ if let Some((config, renew_at, rebind_at, expires_at)) = Self::parse_ack(
+ cx.now(),
+ &dhcp_repr,
+ self.max_lease_duration,
+ state.config.server,
+ ) {
+ state.renew_at = renew_at;
+ state.rebind_at = rebind_at;
+ state.rebinding = false;
+ state.expires_at = expires_at;
+ // The `receive_packet_buffer` field isn't populated until
+ // the client asks for the state, but receiving any packet
+ // will change it, so we indicate that the config has
+ // changed every time if the receive packet buffer is set,
+ // but we only write changes to the rest of the config now.
+ let config_changed =
+ state.config != config || self.receive_packet_buffer.is_some();
+ if state.config != config {
+ state.config = config;
+ }
+ if config_changed {
+ self.config_changed();
+ }
+ }
+ }
+ (ClientState::Renewing(_), DhcpMessageType::Nak) => {
+ if !self.ignore_naks {
+ self.reset();
+ }
+ }
+ _ => {
+ net_debug!(
+ "DHCP ignoring {:?}: unexpected in current state",
+ dhcp_repr.message_type
+ );
+ }
+ }
+ }
+
+ fn parse_ack(
+ now: Instant,
+ dhcp_repr: &DhcpRepr,
+ max_lease_duration: Option<Duration>,
+ server: ServerInfo,
+ ) -> Option<(Config<'static>, Instant, Instant, Instant)> {
+ let subnet_mask = match dhcp_repr.subnet_mask {
+ Some(subnet_mask) => subnet_mask,
+ None => {
+ net_debug!("DHCP ignoring ACK because missing subnet_mask");
+ return None;
+ }
+ };
+
+ let prefix_len = match IpAddress::Ipv4(subnet_mask).prefix_len() {
+ Some(prefix_len) => prefix_len,
+ None => {
+ net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask");
+ return None;
+ }
+ };
+
+ if !dhcp_repr.your_ip.is_unicast() {
+ net_debug!("DHCP ignoring ACK because your_ip is not unicast");
+ return None;
+ }
+
+ let mut lease_duration = dhcp_repr
+ .lease_duration
+ .map(|d| Duration::from_secs(d as _))
+ .unwrap_or(DEFAULT_LEASE_DURATION);
+ if let Some(max_lease_duration) = max_lease_duration {
+ lease_duration = lease_duration.min(max_lease_duration);
+ }
+
+ // Cleanup the DNS servers list, keeping only unicasts/
+ // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :(
+ let mut dns_servers = Vec::new();
+
+ dhcp_repr
+ .dns_servers
+ .iter()
+ .flatten()
+ .filter(|s| s.is_unicast())
+ .for_each(|a| {
+ // This will never produce an error, as both the arrays and `dns_servers`
+ // have length DHCP_MAX_DNS_SERVER_COUNT
+ dns_servers.push(*a).ok();
+ });
+
+ let config = Config {
+ server,
+ address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len),
+ router: dhcp_repr.router,
+ dns_servers,
+ packet: None,
+ };
+
+ // Set renew and rebind times as per RFC 2131:
+ // Times T1 and T2 are configurable by the server through
+ // options. T1 defaults to (0.5 * duration_of_lease). T2
+ // defaults to (0.875 * duration_of_lease).
+ let (renew_duration, rebind_duration) = match (
+ dhcp_repr
+ .renew_duration
+ .map(|d| Duration::from_secs(d as u64)),
+ dhcp_repr
+ .rebind_duration
+ .map(|d| Duration::from_secs(d as u64)),
+ ) {
+ (Some(renew_duration), Some(rebind_duration)) => (renew_duration, rebind_duration),
+ (None, None) => (lease_duration / 2, lease_duration * 7 / 8),
+ // RFC 2131 does not say what to do if only one value is
+ // provided, so:
+
+ // If only T1 is provided, set T2 to be 0.75 through the gap
+ // between T1 and the duration of the lease. If T1 is set to
+ // the default (0.5 * duration_of_lease), then T2 will also
+ // be set to the default (0.875 * duration_of_lease).
+ (Some(renew_duration), None) => (
+ renew_duration,
+ renew_duration + (lease_duration - renew_duration) * 3 / 4,
+ ),
+
+ // If only T2 is provided, then T1 will be set to be
+ // whichever is smaller of the default (0.5 *
+ // duration_of_lease) or T2.
+ (None, Some(rebind_duration)) => {
+ ((lease_duration / 2).min(rebind_duration), rebind_duration)
+ }
+ };
+ let renew_at = now + renew_duration;
+ let rebind_at = now + rebind_duration;
+ let expires_at = now + lease_duration;
+
+ Some((config, renew_at, rebind_at, expires_at))
+ }
+
+ #[cfg(not(test))]
+ fn random_transaction_id(cx: &mut Context) -> u32 {
+ cx.rand().rand_u32()
+ }
+
+ #[cfg(test)]
+ fn random_transaction_id(_cx: &mut Context) -> u32 {
+ 0x12345678
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, (Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<(), E>,
+ {
+ // note: Dhcpv4Socket is only usable in ethernet mediums, so the
+ // unwrap can never fail.
+ let HardwareAddress::Ethernet(ethernet_addr) = cx.hardware_addr() else {
+ panic!("using DHCPv4 socket with a non-ethernet hardware address.");
+ };
+
+ // Worst case biggest IPv4 header length.
+ // 0x0f * 4 = 60 bytes.
+ const MAX_IPV4_HEADER_LEN: usize = 60;
+
+ // We don't directly modify self.transaction_id because sending the packet
+ // may fail. We only want to update state after successfully sending.
+ let next_transaction_id = Self::random_transaction_id(cx);
+
+ let mut dhcp_repr = DhcpRepr {
+ message_type: DhcpMessageType::Discover,
+ transaction_id: next_transaction_id,
+ secs: 0,
+ client_hardware_address: ethernet_addr,
+ client_ip: Ipv4Address::UNSPECIFIED,
+ your_ip: Ipv4Address::UNSPECIFIED,
+ server_ip: Ipv4Address::UNSPECIFIED,
+ router: None,
+ subnet_mask: None,
+ relay_agent_ip: Ipv4Address::UNSPECIFIED,
+ broadcast: false,
+ requested_ip: None,
+ client_identifier: Some(ethernet_addr),
+ server_identifier: None,
+ parameter_request_list: Some(
+ self.parameter_request_list
+ .unwrap_or(DEFAULT_PARAMETER_REQUEST_LIST),
+ ),
+ max_size: Some((cx.ip_mtu() - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16),
+ lease_duration: None,
+ renew_duration: None,
+ rebind_duration: None,
+ dns_servers: None,
+ additional_options: self.outgoing_options,
+ };
+
+ let udp_repr = UdpRepr {
+ src_port: self.client_port,
+ dst_port: self.server_port,
+ };
+
+ let mut ipv4_repr = Ipv4Repr {
+ src_addr: Ipv4Address::UNSPECIFIED,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: 0, // filled right before emit
+ hop_limit: 64,
+ };
+
+ match &mut self.state {
+ ClientState::Discovering(state) => {
+ if cx.now() < state.retry_at {
+ return Ok(());
+ }
+
+ // send packet
+ net_debug!(
+ "DHCP send DISCOVER to {}: {:?}",
+ ipv4_repr.dst_addr,
+ dhcp_repr
+ );
+ ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+ emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
+
+ // Update state AFTER the packet has been successfully sent.
+ state.retry_at = cx.now() + self.retry_config.discover_timeout;
+ self.transaction_id = next_transaction_id;
+ Ok(())
+ }
+ ClientState::Requesting(state) => {
+ if cx.now() < state.retry_at {
+ return Ok(());
+ }
+
+ if state.retry >= self.retry_config.request_retries {
+ net_debug!("DHCP request retries exceeded, restarting discovery");
+ self.reset();
+ return Ok(());
+ }
+
+ dhcp_repr.message_type = DhcpMessageType::Request;
+ dhcp_repr.requested_ip = Some(state.requested_ip);
+ dhcp_repr.server_identifier = Some(state.server.identifier);
+
+ net_debug!(
+ "DHCP send request to {}: {:?}",
+ ipv4_repr.dst_addr,
+ dhcp_repr
+ );
+ ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+ emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
+
+ // Exponential backoff: Double every 2 retries.
+ state.retry_at = cx.now()
+ + (self.retry_config.initial_request_timeout << (state.retry as u32 / 2));
+ state.retry += 1;
+
+ self.transaction_id = next_transaction_id;
+ Ok(())
+ }
+ ClientState::Renewing(state) => {
+ let now = cx.now();
+ if state.expires_at <= now {
+ net_debug!("DHCP lease expired");
+ self.reset();
+ // return Ok so we get polled again
+ return Ok(());
+ }
+
+ if now < state.renew_at || state.rebinding && now < state.rebind_at {
+ return Ok(());
+ }
+
+ state.rebinding |= now >= state.rebind_at;
+
+ ipv4_repr.src_addr = state.config.address.address();
+ // Renewing is unicast to the original server, rebinding is broadcast
+ if !state.rebinding {
+ ipv4_repr.dst_addr = state.config.server.address;
+ }
+ dhcp_repr.message_type = DhcpMessageType::Request;
+ dhcp_repr.client_ip = state.config.address.address();
+
+ net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
+ ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+ emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
+
+ // In both RENEWING and REBINDING states, if the client receives no
+ // response to its DHCPREQUEST message, the client SHOULD wait one-half
+ // of the remaining time until T2 (in RENEWING state) and one-half of
+ // the remaining lease time (in REBINDING state), down to a minimum of
+ // 60 seconds, before retransmitting the DHCPREQUEST message.
+ if state.rebinding {
+ state.rebind_at = now
+ + self
+ .retry_config
+ .min_renew_timeout
+ .max((state.expires_at - now) / 2)
+ .min(self.retry_config.max_renew_timeout);
+ } else {
+ state.renew_at = now
+ + self
+ .retry_config
+ .min_renew_timeout
+ .max((state.rebind_at - now) / 2)
+ .min(state.rebind_at - now)
+ .min(self.retry_config.max_renew_timeout);
+ }
+
+ self.transaction_id = next_transaction_id;
+ Ok(())
+ }
+ }
+ }
+
+ /// Reset state and restart discovery phase.
+ ///
+ /// Use this to speed up acquisition of an address in a new
+ /// network if a link was down and it is now back up.
+ pub fn reset(&mut self) {
+ net_trace!("DHCP reset");
+ if let ClientState::Renewing(_) = &self.state {
+ self.config_changed();
+ }
+ self.state = ClientState::Discovering(DiscoverState {
+ retry_at: Instant::from_millis(0),
+ });
+ }
+
+ /// Query the socket for configuration changes.
+ ///
+ /// The socket has an internal "configuration changed" flag. If
+ /// set, this function returns the configuration and resets the flag.
+ pub fn poll(&mut self) -> Option<Event> {
+ if !self.config_changed {
+ None
+ } else if let ClientState::Renewing(state) = &self.state {
+ self.config_changed = false;
+ Some(Event::Configured(Config {
+ server: state.config.server,
+ address: state.config.address,
+ router: state.config.router,
+ dns_servers: state.config.dns_servers.clone(),
+ packet: self
+ .receive_packet_buffer
+ .as_deref()
+ .map(DhcpPacket::new_unchecked),
+ }))
+ } else {
+ self.config_changed = false;
+ Some(Event::Deconfigured)
+ }
+ }
+
+ /// This function _must_ be called when the configuration provided to the
+ /// interface, by this DHCP socket, changes. It will update the `config_changed` field
+ /// so that a subsequent call to `poll` will yield an event, and wake a possible waker.
+ pub(crate) fn config_changed(&mut self) {
+ self.config_changed = true;
+ #[cfg(feature = "async")]
+ self.waker.wake();
+ }
+
+ /// Register a waker.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `poll` method calls, which indicates a new state in the DHCP configuration
+ /// provided by this DHCP socket.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ #[cfg(feature = "async")]
+ pub fn register_waker(&mut self, waker: &Waker) {
+ self.waker.register(waker)
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use std::ops::{Deref, DerefMut};
+
+ use super::*;
+ use crate::wire::EthernetAddress;
+
+ // =========================================================================================//
+ // Helper functions
+
+ struct TestSocket {
+ socket: Socket<'static>,
+ cx: Context,
+ }
+
+ impl Deref for TestSocket {
+ type Target = Socket<'static>;
+ fn deref(&self) -> &Self::Target {
+ &self.socket
+ }
+ }
+
+ impl DerefMut for TestSocket {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.socket
+ }
+ }
+
+ fn send(
+ s: &mut TestSocket,
+ timestamp: Instant,
+ (ip_repr, udp_repr, dhcp_repr): (Ipv4Repr, UdpRepr, DhcpRepr),
+ ) {
+ s.cx.set_now(timestamp);
+
+ net_trace!("send: {:?}", ip_repr);
+ net_trace!(" {:?}", udp_repr);
+ net_trace!(" {:?}", dhcp_repr);
+
+ let mut payload = vec![0; dhcp_repr.buffer_len()];
+ dhcp_repr
+ .emit(&mut DhcpPacket::new_unchecked(&mut payload))
+ .unwrap();
+
+ s.socket.process(&mut s.cx, &ip_repr, &udp_repr, &payload)
+ }
+
+ fn recv(s: &mut TestSocket, timestamp: Instant, reprs: &[(Ipv4Repr, UdpRepr, DhcpRepr)]) {
+ s.cx.set_now(timestamp);
+
+ let mut i = 0;
+
+ while s.socket.poll_at(&mut s.cx) <= PollAt::Time(timestamp) {
+ let _ = s
+ .socket
+ .dispatch(&mut s.cx, |_, (mut ip_repr, udp_repr, dhcp_repr)| {
+ assert_eq!(ip_repr.next_header, IpProtocol::Udp);
+ assert_eq!(
+ ip_repr.payload_len,
+ udp_repr.header_len() + dhcp_repr.buffer_len()
+ );
+
+ // We validated the payload len, change it to 0 to make equality testing easier
+ ip_repr.payload_len = 0;
+
+ net_trace!("recv: {:?}", ip_repr);
+ net_trace!(" {:?}", udp_repr);
+ net_trace!(" {:?}", dhcp_repr);
+
+ let got_repr = (ip_repr, udp_repr, dhcp_repr);
+ match reprs.get(i) {
+ Some(want_repr) => assert_eq!(want_repr, &got_repr),
+ None => panic!("Too many reprs emitted"),
+ }
+ i += 1;
+ Ok::<_, ()>(())
+ });
+ }
+
+ assert_eq!(i, reprs.len());
+ }
+
+ macro_rules! send {
+ ($socket:ident, $repr:expr) =>
+ (send!($socket, time 0, $repr));
+ ($socket:ident, time $time:expr, $repr:expr) =>
+ (send(&mut $socket, Instant::from_millis($time), $repr));
+ }
+
+ macro_rules! recv {
+ ($socket:ident, $reprs:expr) => ({
+ recv!($socket, time 0, $reprs);
+ });
+ ($socket:ident, time $time:expr, $reprs:expr) => ({
+ recv(&mut $socket, Instant::from_millis($time), &$reprs);
+ });
+ }
+
+ // =========================================================================================//
+ // Constants
+
+ const TXID: u32 = 0x12345678;
+
+ const MY_IP: Ipv4Address = Ipv4Address([192, 168, 1, 42]);
+ const SERVER_IP: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
+ const DNS_IP_1: Ipv4Address = Ipv4Address([1, 1, 1, 1]);
+ const DNS_IP_2: Ipv4Address = Ipv4Address([1, 1, 1, 2]);
+ const DNS_IP_3: Ipv4Address = Ipv4Address([1, 1, 1, 3]);
+ const DNS_IPS: &[Ipv4Address] = &[DNS_IP_1, DNS_IP_2, DNS_IP_3];
+
+ const MASK_24: Ipv4Address = Ipv4Address([255, 255, 255, 0]);
+
+ const MY_MAC: EthernetAddress = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+
+ const IP_BROADCAST: Ipv4Repr = Ipv4Repr {
+ src_addr: Ipv4Address::UNSPECIFIED,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: 0,
+ hop_limit: 64,
+ };
+
+ const IP_BROADCAST_ADDRESSED: Ipv4Repr = Ipv4Repr {
+ src_addr: MY_IP,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: 0,
+ hop_limit: 64,
+ };
+
+ const IP_SERVER_BROADCAST: Ipv4Repr = Ipv4Repr {
+ src_addr: SERVER_IP,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: 0,
+ hop_limit: 64,
+ };
+
+ const IP_RECV: Ipv4Repr = Ipv4Repr {
+ src_addr: SERVER_IP,
+ dst_addr: MY_IP,
+ next_header: IpProtocol::Udp,
+ payload_len: 0,
+ hop_limit: 64,
+ };
+
+ const IP_SEND: Ipv4Repr = Ipv4Repr {
+ src_addr: MY_IP,
+ dst_addr: SERVER_IP,
+ next_header: IpProtocol::Udp,
+ payload_len: 0,
+ hop_limit: 64,
+ };
+
+ const UDP_SEND: UdpRepr = UdpRepr {
+ src_port: DHCP_CLIENT_PORT,
+ dst_port: DHCP_SERVER_PORT,
+ };
+ const UDP_RECV: UdpRepr = UdpRepr {
+ src_port: DHCP_SERVER_PORT,
+ dst_port: DHCP_CLIENT_PORT,
+ };
+
+ const DIFFERENT_CLIENT_PORT: u16 = 6800;
+ const DIFFERENT_SERVER_PORT: u16 = 6700;
+
+ const UDP_SEND_DIFFERENT_PORT: UdpRepr = UdpRepr {
+ src_port: DIFFERENT_CLIENT_PORT,
+ dst_port: DIFFERENT_SERVER_PORT,
+ };
+ const UDP_RECV_DIFFERENT_PORT: UdpRepr = UdpRepr {
+ src_port: DIFFERENT_SERVER_PORT,
+ dst_port: DIFFERENT_CLIENT_PORT,
+ };
+
+ const DHCP_DEFAULT: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Unknown(99),
+ transaction_id: TXID,
+ secs: 0,
+ client_hardware_address: MY_MAC,
+ client_ip: Ipv4Address::UNSPECIFIED,
+ your_ip: Ipv4Address::UNSPECIFIED,
+ server_ip: Ipv4Address::UNSPECIFIED,
+ router: None,
+ subnet_mask: None,
+ relay_agent_ip: Ipv4Address::UNSPECIFIED,
+ broadcast: false,
+ requested_ip: None,
+ client_identifier: None,
+ server_identifier: None,
+ parameter_request_list: None,
+ dns_servers: None,
+ max_size: None,
+ renew_duration: None,
+ rebind_duration: None,
+ lease_duration: None,
+ additional_options: &[],
+ };
+
+ const DHCP_DISCOVER: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Discover,
+ client_identifier: Some(MY_MAC),
+ parameter_request_list: Some(&[1, 3, 6]),
+ max_size: Some(1432),
+ ..DHCP_DEFAULT
+ };
+
+ fn dhcp_offer() -> DhcpRepr<'static> {
+ DhcpRepr {
+ message_type: DhcpMessageType::Offer,
+ server_ip: SERVER_IP,
+ server_identifier: Some(SERVER_IP),
+
+ your_ip: MY_IP,
+ router: Some(SERVER_IP),
+ subnet_mask: Some(MASK_24),
+ dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()),
+ lease_duration: Some(1000),
+
+ ..DHCP_DEFAULT
+ }
+ }
+
+ const DHCP_REQUEST: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Request,
+ client_identifier: Some(MY_MAC),
+ server_identifier: Some(SERVER_IP),
+ max_size: Some(1432),
+
+ requested_ip: Some(MY_IP),
+ parameter_request_list: Some(&[1, 3, 6]),
+ ..DHCP_DEFAULT
+ };
+
+ fn dhcp_ack() -> DhcpRepr<'static> {
+ DhcpRepr {
+ message_type: DhcpMessageType::Ack,
+ server_ip: SERVER_IP,
+ server_identifier: Some(SERVER_IP),
+
+ your_ip: MY_IP,
+ router: Some(SERVER_IP),
+ subnet_mask: Some(MASK_24),
+ dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()),
+ lease_duration: Some(1000),
+
+ ..DHCP_DEFAULT
+ }
+ }
+
+ const DHCP_NAK: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Nak,
+ server_ip: SERVER_IP,
+ server_identifier: Some(SERVER_IP),
+ ..DHCP_DEFAULT
+ };
+
+ const DHCP_RENEW: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Request,
+ client_identifier: Some(MY_MAC),
+ // NO server_identifier in renew requests, only in first one!
+ client_ip: MY_IP,
+ max_size: Some(1432),
+
+ requested_ip: None,
+ parameter_request_list: Some(&[1, 3, 6]),
+ ..DHCP_DEFAULT
+ };
+
+ const DHCP_REBIND: DhcpRepr = DhcpRepr {
+ message_type: DhcpMessageType::Request,
+ client_identifier: Some(MY_MAC),
+ // NO server_identifier in renew requests, only in first one!
+ client_ip: MY_IP,
+ max_size: Some(1432),
+
+ requested_ip: None,
+ parameter_request_list: Some(&[1, 3, 6]),
+ ..DHCP_DEFAULT
+ };
+
+ // =========================================================================================//
+ // Tests
+
+ use crate::phy::Medium;
+ use crate::tests::setup;
+ use rstest::*;
+
+ fn socket(medium: Medium) -> TestSocket {
+ let (iface, _, _) = setup(medium);
+ let mut s = Socket::new();
+ assert_eq!(s.poll(), Some(Event::Deconfigured));
+ TestSocket {
+ socket: s,
+ cx: iface.inner,
+ }
+ }
+
+ fn socket_different_port(medium: Medium) -> TestSocket {
+ let (iface, _, _) = setup(medium);
+ let mut s = Socket::new();
+ s.set_ports(DIFFERENT_SERVER_PORT, DIFFERENT_CLIENT_PORT);
+
+ assert_eq!(s.poll(), Some(Event::Deconfigured));
+ TestSocket {
+ socket: s,
+ cx: iface.inner,
+ }
+ }
+
+ fn socket_bound(medium: Medium) -> TestSocket {
+ let mut s = socket(medium);
+ s.state = ClientState::Renewing(RenewState {
+ config: Config {
+ server: ServerInfo {
+ address: SERVER_IP,
+ identifier: SERVER_IP,
+ },
+ address: Ipv4Cidr::new(MY_IP, 24),
+ dns_servers: Vec::from_slice(DNS_IPS).unwrap(),
+ router: Some(SERVER_IP),
+ packet: None,
+ },
+ renew_at: Instant::from_secs(500),
+ rebind_at: Instant::from_secs(875),
+ rebinding: false,
+ expires_at: Instant::from_secs(1000),
+ });
+
+ s
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_bind(#[case] medium: Medium) {
+ let mut s = socket(medium);
+
+ recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ assert_eq!(s.poll(), None);
+ send!(s, (IP_RECV, UDP_RECV, dhcp_offer()));
+ assert_eq!(s.poll(), None);
+ recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ assert_eq!(s.poll(), None);
+ send!(s, (IP_RECV, UDP_RECV, dhcp_ack()));
+
+ assert_eq!(
+ s.poll(),
+ Some(Event::Configured(Config {
+ server: ServerInfo {
+ address: SERVER_IP,
+ identifier: SERVER_IP,
+ },
+ address: Ipv4Cidr::new(MY_IP, 24),
+ dns_servers: Vec::from_slice(DNS_IPS).unwrap(),
+ router: Some(SERVER_IP),
+ packet: None,
+ }))
+ );
+
+ match &s.state {
+ ClientState::Renewing(r) => {
+ assert_eq!(r.renew_at, Instant::from_secs(500));
+ assert_eq!(r.rebind_at, Instant::from_secs(875));
+ assert_eq!(r.expires_at, Instant::from_secs(1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_bind_different_ports(#[case] medium: Medium) {
+ let mut s = socket_different_port(medium);
+
+ recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_DISCOVER)]);
+ assert_eq!(s.poll(), None);
+ send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_offer()));
+ assert_eq!(s.poll(), None);
+ recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_REQUEST)]);
+ assert_eq!(s.poll(), None);
+ send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_ack()));
+
+ assert_eq!(
+ s.poll(),
+ Some(Event::Configured(Config {
+ server: ServerInfo {
+ address: SERVER_IP,
+ identifier: SERVER_IP,
+ },
+ address: Ipv4Cidr::new(MY_IP, 24),
+ dns_servers: Vec::from_slice(DNS_IPS).unwrap(),
+ router: Some(SERVER_IP),
+ packet: None,
+ }))
+ );
+
+ match &s.state {
+ ClientState::Renewing(r) => {
+ assert_eq!(r.renew_at, Instant::from_secs(500));
+ assert_eq!(r.rebind_at, Instant::from_secs(875));
+ assert_eq!(r.expires_at, Instant::from_secs(1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_discover_retransmit(#[case] medium: Medium) {
+ let mut s = socket(medium);
+
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ recv!(s, time 1_000, []);
+ recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ recv!(s, time 11_000, []);
+ recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+
+ // check after retransmits it still works
+ send!(s, time 20_000, (IP_RECV, UDP_RECV, dhcp_offer()));
+ recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_request_retransmit(#[case] medium: Medium) {
+ let mut s = socket(medium);
+
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer()));
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 1_000, []);
+ recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 6_000, []);
+ recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 15_000, []);
+ recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+
+ // check after retransmits it still works
+ send!(s, time 20_000, (IP_RECV, UDP_RECV, dhcp_ack()));
+
+ match &s.state {
+ ClientState::Renewing(r) => {
+ assert_eq!(r.renew_at, Instant::from_secs(20 + 500));
+ assert_eq!(r.expires_at, Instant::from_secs(20 + 1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_request_timeout(#[case] medium: Medium) {
+ let mut s = socket(medium);
+
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer()));
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ recv!(s, time 30_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+
+ // After 5 tries and 70 seconds, it gives up.
+ // 5 + 5 + 10 + 10 + 20 = 70
+ recv!(s, time 70_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+
+ // check it still works
+ send!(s, time 60_000, (IP_RECV, UDP_RECV, dhcp_offer()));
+ recv!(s, time 60_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_request_nak(#[case] medium: Medium) {
+ let mut s = socket(medium);
+
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer()));
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
+ send!(s, time 0, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK));
+ recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_renew(#[case] medium: Medium) {
+ let mut s = socket_bound(medium);
+
+ recv!(s, []);
+ assert_eq!(s.poll(), None);
+ recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ assert_eq!(s.poll(), None);
+
+ match &s.state {
+ ClientState::Renewing(r) => {
+ // the expiration still hasn't been bumped, because
+ // we haven't received the ACK yet
+ assert_eq!(r.expires_at, Instant::from_secs(1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+
+ send!(s, time 500_000, (IP_RECV, UDP_RECV, dhcp_ack()));
+ assert_eq!(s.poll(), None);
+
+ match &s.state {
+ ClientState::Renewing(r) => {
+ // NOW the expiration gets bumped
+ assert_eq!(r.renew_at, Instant::from_secs(500 + 500));
+ assert_eq!(r.expires_at, Instant::from_secs(500 + 1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_renew_rebind_retransmit(#[case] medium: Medium) {
+ let mut s = socket_bound(medium);
+
+ recv!(s, []);
+ // First renew attempt at T1
+ recv!(s, time 499_000, []);
+ recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt at half way to T2
+ recv!(s, time 687_000, []);
+ recv!(s, time 687_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt at half way again to T2
+ recv!(s, time 781_000, []);
+ recv!(s, time 781_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt 60s later (minimum interval)
+ recv!(s, time 841_000, []);
+ recv!(s, time 841_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // No more renews due to minimum interval
+ recv!(s, time 874_000, []);
+ // First rebind attempt
+ recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ // Next rebind attempt half way to expiry
+ recv!(s, time 937_000, []);
+ recv!(s, time 937_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ // Next rebind attempt 60s later (minimum interval)
+ recv!(s, time 997_000, []);
+ recv!(s, time 997_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+
+ // check it still works
+ send!(s, time 999_000, (IP_RECV, UDP_RECV, dhcp_ack()));
+ match &s.state {
+ ClientState::Renewing(r) => {
+ // NOW the expiration gets bumped
+ assert_eq!(r.renew_at, Instant::from_secs(999 + 500));
+ assert_eq!(r.expires_at, Instant::from_secs(999 + 1000));
+ }
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_renew_rebind_timeout(#[case] medium: Medium) {
+ let mut s = socket_bound(medium);
+
+ recv!(s, []);
+ // First renew attempt at T1
+ recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt at half way to T2
+ recv!(s, time 687_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt at half way again to T2
+ recv!(s, time 781_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt 60s later (minimum interval)
+ recv!(s, time 841_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // TODO uncomment below part of test
+ // // First rebind attempt
+ // recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ // // Next rebind attempt half way to expiry
+ // recv!(s, time 937_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ // // Next rebind attempt 60s later (minimum interval)
+ // recv!(s, time 997_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ // No more rebinds due to minimum interval
+ recv!(s, time 1_000_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ match &s.state {
+ ClientState::Discovering(_) => {}
+ _ => panic!("Invalid state"),
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_min_max_renew_timeout(#[case] medium: Medium) {
+ let mut s = socket_bound(medium);
+ // Set a minimum of 45s and a maximum of 120s
+ let config = RetryConfig {
+ max_renew_timeout: Duration::from_secs(120),
+ min_renew_timeout: Duration::from_secs(45),
+ ..s.get_retry_config()
+ };
+ s.set_retry_config(config);
+ recv!(s, []);
+ // First renew attempt at T1
+ recv!(s, time 499_999, []);
+ recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt 120s after T1 because we hit the max
+ recv!(s, time 619_999, []);
+ recv!(s, time 620_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt 120s after previous because we hit the max again
+ recv!(s, time 739_999, []);
+ recv!(s, time 740_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt half way to T2
+ recv!(s, time 807_499, []);
+ recv!(s, time 807_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next renew attempt 45s after previous because we hit the min
+ recv!(s, time 852_499, []);
+ recv!(s, time 852_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ // Next is a rebind, because the min puts us after T2
+ recv!(s, time 874_999, []);
+ recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]);
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_renew_nak(#[case] medium: Medium) {
+ let mut s = socket_bound(medium);
+
+ recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
+ send!(s, time 500_000, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK));
+ recv!(s, time 500_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
+ }
+}
diff --git a/src/socket/dns.rs b/src/socket/dns.rs
new file mode 100644
index 0000000..610d5c6
--- /dev/null
+++ b/src/socket/dns.rs
@@ -0,0 +1,699 @@
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+use heapless::Vec;
+use managed::ManagedSlice;
+
+use crate::config::{DNS_MAX_NAME_SIZE, DNS_MAX_RESULT_COUNT, DNS_MAX_SERVER_COUNT};
+use crate::socket::{Context, PollAt};
+use crate::time::{Duration, Instant};
+use crate::wire::dns::{Flags, Opcode, Packet, Question, Rcode, Record, RecordData, Repr, Type};
+use crate::wire::{self, IpAddress, IpProtocol, IpRepr, UdpRepr};
+
+#[cfg(feature = "async")]
+use super::WakerRegistration;
+
+const DNS_PORT: u16 = 53;
+const MDNS_DNS_PORT: u16 = 5353;
+const RETRANSMIT_DELAY: Duration = Duration::from_millis(1_000);
+const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10_000);
+const RETRANSMIT_TIMEOUT: Duration = Duration::from_millis(10_000); // Should generally be 2-10 secs
+
+#[cfg(feature = "proto-ipv6")]
+const MDNS_IPV6_ADDR: IpAddress = IpAddress::Ipv6(crate::wire::Ipv6Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb,
+]));
+
+#[cfg(feature = "proto-ipv4")]
+const MDNS_IPV4_ADDR: IpAddress = IpAddress::Ipv4(crate::wire::Ipv4Address([224, 0, 0, 251]));
+
+/// Error returned by [`Socket::start_query`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum StartQueryError {
+ NoFreeSlot,
+ InvalidName,
+ NameTooLong,
+}
+
+impl core::fmt::Display for StartQueryError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ StartQueryError::NoFreeSlot => write!(f, "No free slot"),
+ StartQueryError::InvalidName => write!(f, "Invalid name"),
+ StartQueryError::NameTooLong => write!(f, "Name too long"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for StartQueryError {}
+
+/// Error returned by [`Socket::get_query_result`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum GetQueryResultError {
+ /// Query is not done yet.
+ Pending,
+ /// Query failed.
+ Failed,
+}
+
+impl core::fmt::Display for GetQueryResultError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ GetQueryResultError::Pending => write!(f, "Query is not done yet"),
+ GetQueryResultError::Failed => write!(f, "Query failed"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for GetQueryResultError {}
+
+/// State for an in-progress DNS query.
+///
+/// The only reason this struct is public is to allow the socket state
+/// to be allocated externally.
+#[derive(Debug)]
+pub struct DnsQuery {
+ state: State,
+
+ #[cfg(feature = "async")]
+ waker: WakerRegistration,
+}
+
+impl DnsQuery {
+ fn set_state(&mut self, state: State) {
+ self.state = state;
+ #[cfg(feature = "async")]
+ self.waker.wake();
+ }
+}
+
+#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
+enum State {
+ Pending(PendingQuery),
+ Completed(CompletedQuery),
+ Failure,
+}
+
+#[derive(Debug)]
+struct PendingQuery {
+ name: Vec<u8, DNS_MAX_NAME_SIZE>,
+ type_: Type,
+
+ port: u16, // UDP port (src for request, dst for response)
+ txid: u16, // transaction ID
+
+ timeout_at: Option<Instant>,
+ retransmit_at: Instant,
+ delay: Duration,
+
+ server_idx: usize,
+ mdns: MulticastDns,
+}
+
+#[derive(Debug)]
+pub enum MulticastDns {
+ Disabled,
+ #[cfg(feature = "socket-mdns")]
+ Enabled,
+}
+
+#[derive(Debug)]
+struct CompletedQuery {
+ addresses: Vec<IpAddress, DNS_MAX_RESULT_COUNT>,
+}
+
+/// A handle to an in-progress DNS query.
+#[derive(Clone, Copy)]
+pub struct QueryHandle(usize);
+
+/// A Domain Name System socket.
+///
+/// A UDP socket is bound to a specific endpoint, and owns transmit and receive
+/// packet buffers.
+#[derive(Debug)]
+pub struct Socket<'a> {
+ servers: Vec<IpAddress, DNS_MAX_SERVER_COUNT>,
+ queries: ManagedSlice<'a, Option<DnsQuery>>,
+
+ /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ hop_limit: Option<u8>,
+}
+
+impl<'a> Socket<'a> {
+ /// Create a DNS socket.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `servers.len() > MAX_SERVER_COUNT`
+ pub fn new<Q>(servers: &[IpAddress], queries: Q) -> Socket<'a>
+ where
+ Q: Into<ManagedSlice<'a, Option<DnsQuery>>>,
+ {
+ Socket {
+ servers: Vec::from_slice(servers).unwrap(),
+ queries: queries.into(),
+ hop_limit: None,
+ }
+ }
+
+ /// Update the list of DNS servers, will replace all existing servers
+ ///
+ /// # Panics
+ ///
+ /// Panics if `servers.len() > MAX_SERVER_COUNT`
+ pub fn update_servers(&mut self, servers: &[IpAddress]) {
+ self.servers = Vec::from_slice(servers).unwrap();
+ }
+
+ /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// See also the [set_hop_limit](#method.set_hop_limit) method
+ pub fn hop_limit(&self) -> Option<u8> {
+ self.hop_limit
+ }
+
+ /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
+ /// value (64).
+ ///
+ /// # Panics
+ ///
+ /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
+ ///
+ /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
+ /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
+ pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
+ // A host MUST NOT send a datagram with a hop limit value of 0
+ if let Some(0) = hop_limit {
+ panic!("the time-to-live value of a packet must not be zero")
+ }
+
+ self.hop_limit = hop_limit
+ }
+
+ fn find_free_query(&mut self) -> Option<QueryHandle> {
+ for (i, q) in self.queries.iter().enumerate() {
+ if q.is_none() {
+ return Some(QueryHandle(i));
+ }
+ }
+
+ match &mut self.queries {
+ ManagedSlice::Borrowed(_) => None,
+ #[cfg(feature = "alloc")]
+ ManagedSlice::Owned(queries) => {
+ queries.push(None);
+ let index = queries.len() - 1;
+ Some(QueryHandle(index))
+ }
+ }
+ }
+
+ /// Start a query.
+ ///
+ /// `name` is specified in human-friendly format, such as `"rust-lang.org"`.
+ /// It accepts names both with and without trailing dot, and they're treated
+ /// the same (there's no support for DNS search path).
+ pub fn start_query(
+ &mut self,
+ cx: &mut Context,
+ name: &str,
+ query_type: Type,
+ ) -> Result<QueryHandle, StartQueryError> {
+ let mut name = name.as_bytes();
+
+ if name.is_empty() {
+ net_trace!("invalid name: zero length");
+ return Err(StartQueryError::InvalidName);
+ }
+
+ // Remove trailing dot, if any
+ if name[name.len() - 1] == b'.' {
+ name = &name[..name.len() - 1];
+ }
+
+ let mut raw_name: Vec<u8, DNS_MAX_NAME_SIZE> = Vec::new();
+
+ let mut mdns = MulticastDns::Disabled;
+ #[cfg(feature = "socket-mdns")]
+ if name.split(|&c| c == b'.').last().unwrap() == b"local" {
+ net_trace!("Starting a mDNS query");
+ mdns = MulticastDns::Enabled;
+ }
+
+ for s in name.split(|&c| c == b'.') {
+ if s.len() > 63 {
+ net_trace!("invalid name: too long label");
+ return Err(StartQueryError::InvalidName);
+ }
+ if s.is_empty() {
+ net_trace!("invalid name: zero length label");
+ return Err(StartQueryError::InvalidName);
+ }
+
+ // Push label
+ raw_name
+ .push(s.len() as u8)
+ .map_err(|_| StartQueryError::NameTooLong)?;
+ raw_name
+ .extend_from_slice(s)
+ .map_err(|_| StartQueryError::NameTooLong)?;
+ }
+
+ // Push terminator.
+ raw_name
+ .push(0x00)
+ .map_err(|_| StartQueryError::NameTooLong)?;
+
+ self.start_query_raw(cx, &raw_name, query_type, mdns)
+ }
+
+ /// Start a query with a raw (wire-format) DNS name.
+ /// `b"\x09rust-lang\x03org\x00"`
+ ///
+ /// You probably want to use [`start_query`] instead.
+ pub fn start_query_raw(
+ &mut self,
+ cx: &mut Context,
+ raw_name: &[u8],
+ query_type: Type,
+ mdns: MulticastDns,
+ ) -> Result<QueryHandle, StartQueryError> {
+ let handle = self.find_free_query().ok_or(StartQueryError::NoFreeSlot)?;
+
+ self.queries[handle.0] = Some(DnsQuery {
+ state: State::Pending(PendingQuery {
+ name: Vec::from_slice(raw_name).map_err(|_| StartQueryError::NameTooLong)?,
+ type_: query_type,
+ txid: cx.rand().rand_u16(),
+ port: cx.rand().rand_source_port(),
+ delay: RETRANSMIT_DELAY,
+ timeout_at: None,
+ retransmit_at: Instant::ZERO,
+ server_idx: 0,
+ mdns,
+ }),
+ #[cfg(feature = "async")]
+ waker: WakerRegistration::new(),
+ });
+ Ok(handle)
+ }
+
+ /// Get the result of a query.
+ ///
+ /// If the query is completed, the query slot is automatically freed.
+ ///
+ /// # Panics
+ /// Panics if the QueryHandle corresponds to a free slot.
+ pub fn get_query_result(
+ &mut self,
+ handle: QueryHandle,
+ ) -> Result<Vec<IpAddress, DNS_MAX_RESULT_COUNT>, GetQueryResultError> {
+ let slot = &mut self.queries[handle.0];
+ let q = slot.as_mut().unwrap();
+ match &mut q.state {
+ // Query is not done yet.
+ State::Pending(_) => Err(GetQueryResultError::Pending),
+ // Query is done
+ State::Completed(q) => {
+ let res = q.addresses.clone();
+ *slot = None; // Free up the slot for recycling.
+ Ok(res)
+ }
+ State::Failure => {
+ *slot = None; // Free up the slot for recycling.
+ Err(GetQueryResultError::Failed)
+ }
+ }
+ }
+
+ /// Cancels a query, freeing the slot.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the QueryHandle corresponds to an already free slot.
+ pub fn cancel_query(&mut self, handle: QueryHandle) {
+ let slot = &mut self.queries[handle.0];
+ if slot.is_none() {
+ panic!("Canceling query in a free slot.")
+ }
+ *slot = None; // Free up the slot for recycling.
+ }
+
+ /// Assign a waker to a query slot
+ ///
+ /// The waker will be woken when the query completes, either successfully or failed.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the QueryHandle corresponds to an already free slot.
+ #[cfg(feature = "async")]
+ pub fn register_query_waker(&mut self, handle: QueryHandle, waker: &Waker) {
+ self.queries[handle.0]
+ .as_mut()
+ .unwrap()
+ .waker
+ .register(waker);
+ }
+
+ pub(crate) fn accepts(&self, ip_repr: &IpRepr, udp_repr: &UdpRepr) -> bool {
+ (udp_repr.src_port == DNS_PORT
+ && self
+ .servers
+ .iter()
+ .any(|server| *server == ip_repr.src_addr()))
+ || (udp_repr.src_port == MDNS_DNS_PORT)
+ }
+
+ pub(crate) fn process(
+ &mut self,
+ _cx: &mut Context,
+ ip_repr: &IpRepr,
+ udp_repr: &UdpRepr,
+ payload: &[u8],
+ ) {
+ debug_assert!(self.accepts(ip_repr, udp_repr));
+
+ let size = payload.len();
+
+ net_trace!(
+ "receiving {} octets from {:?}:{}",
+ size,
+ ip_repr.src_addr(),
+ udp_repr.dst_port
+ );
+
+ let p = match Packet::new_checked(payload) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("dns packet malformed");
+ return;
+ }
+ };
+ if p.opcode() != Opcode::Query {
+ net_trace!("unwanted opcode {:?}", p.opcode());
+ return;
+ }
+
+ if !p.flags().contains(Flags::RESPONSE) {
+ net_trace!("packet doesn't have response bit set");
+ return;
+ }
+
+ if p.question_count() != 1 {
+ net_trace!("bad question count {:?}", p.question_count());
+ return;
+ }
+
+ // Find pending query
+ for q in self.queries.iter_mut().flatten() {
+ if let State::Pending(pq) = &mut q.state {
+ if udp_repr.dst_port != pq.port || p.transaction_id() != pq.txid {
+ continue;
+ }
+
+ if p.rcode() == Rcode::NXDomain {
+ net_trace!("rcode NXDomain");
+ q.set_state(State::Failure);
+ continue;
+ }
+
+ let payload = p.payload();
+ let (mut payload, question) = match Question::parse(payload) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("question malformed");
+ return;
+ }
+ };
+
+ if question.type_ != pq.type_ {
+ net_trace!("question type mismatch");
+ return;
+ }
+
+ match eq_names(p.parse_name(question.name), p.parse_name(&pq.name)) {
+ Ok(true) => {}
+ Ok(false) => {
+ net_trace!("question name mismatch");
+ return;
+ }
+ Err(_) => {
+ net_trace!("dns question name malformed");
+ return;
+ }
+ }
+
+ let mut addresses = Vec::new();
+
+ for _ in 0..p.answer_record_count() {
+ let (payload2, r) = match Record::parse(payload) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("dns answer record malformed");
+ return;
+ }
+ };
+ payload = payload2;
+
+ match eq_names(p.parse_name(r.name), p.parse_name(&pq.name)) {
+ Ok(true) => {}
+ Ok(false) => {
+ net_trace!("answer name mismatch: {:?}", r);
+ continue;
+ }
+ Err(_) => {
+ net_trace!("dns answer record name malformed");
+ return;
+ }
+ }
+
+ match r.data {
+ #[cfg(feature = "proto-ipv4")]
+ RecordData::A(addr) => {
+ net_trace!("A: {:?}", addr);
+ if addresses.push(addr.into()).is_err() {
+ net_trace!("too many addresses in response, ignoring {:?}", addr);
+ }
+ }
+ #[cfg(feature = "proto-ipv6")]
+ RecordData::Aaaa(addr) => {
+ net_trace!("AAAA: {:?}", addr);
+ if addresses.push(addr.into()).is_err() {
+ net_trace!("too many addresses in response, ignoring {:?}", addr);
+ }
+ }
+ RecordData::Cname(name) => {
+ net_trace!("CNAME: {:?}", name);
+
+ // When faced with a CNAME, recursive resolvers are supposed to
+ // resolve the CNAME and append the results for it.
+ //
+ // We update the query with the new name, so that we pick up the A/AAAA
+ // records for the CNAME when we parse them later.
+ // I believe it's mandatory the CNAME results MUST come *after* in the
+ // packet, so it's enough to do one linear pass over it.
+ if copy_name(&mut pq.name, p.parse_name(name)).is_err() {
+ net_trace!("dns answer cname malformed");
+ return;
+ }
+ }
+ RecordData::Other(type_, data) => {
+ net_trace!("unknown: {:?} {:?}", type_, data)
+ }
+ }
+ }
+
+ q.set_state(if addresses.is_empty() {
+ State::Failure
+ } else {
+ State::Completed(CompletedQuery { addresses })
+ });
+
+ // If we get here, packet matched the current query, stop processing.
+ return;
+ }
+ }
+
+ // If we get here, packet matched with no query.
+ net_trace!("no query matched");
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>,
+ {
+ let hop_limit = self.hop_limit.unwrap_or(64);
+
+ for q in self.queries.iter_mut().flatten() {
+ if let State::Pending(pq) = &mut q.state {
+ // As per RFC 6762 any DNS query ending in .local. MUST be sent as mdns
+ // so we internally overwrite the servers for any of those queries
+ // in this function.
+ let servers = match pq.mdns {
+ #[cfg(feature = "socket-mdns")]
+ MulticastDns::Enabled => &[
+ #[cfg(feature = "proto-ipv6")]
+ MDNS_IPV6_ADDR,
+ #[cfg(feature = "proto-ipv4")]
+ MDNS_IPV4_ADDR,
+ ],
+ MulticastDns::Disabled => self.servers.as_slice(),
+ };
+
+ let timeout = if let Some(timeout) = pq.timeout_at {
+ timeout
+ } else {
+ let v = cx.now() + RETRANSMIT_TIMEOUT;
+ pq.timeout_at = Some(v);
+ v
+ };
+
+ // Check timeout
+ if timeout < cx.now() {
+ // DNS timeout
+ pq.timeout_at = Some(cx.now() + RETRANSMIT_TIMEOUT);
+ pq.retransmit_at = Instant::ZERO;
+ pq.delay = RETRANSMIT_DELAY;
+
+ // Try next server. We check below whether we've tried all servers.
+ pq.server_idx += 1;
+ }
+ // Check if we've run out of servers to try.
+ if pq.server_idx >= servers.len() {
+ net_trace!("already tried all servers.");
+ q.set_state(State::Failure);
+ continue;
+ }
+
+ // Check so the IP address is valid
+ if servers[pq.server_idx].is_unspecified() {
+ net_trace!("invalid unspecified DNS server addr.");
+ q.set_state(State::Failure);
+ continue;
+ }
+
+ if pq.retransmit_at > cx.now() {
+ // query is waiting for retransmit
+ continue;
+ }
+
+ let repr = Repr {
+ transaction_id: pq.txid,
+ flags: Flags::RECURSION_DESIRED,
+ opcode: Opcode::Query,
+ question: Question {
+ name: &pq.name,
+ type_: pq.type_,
+ },
+ };
+
+ let mut payload = [0u8; 512];
+ let payload = &mut payload[..repr.buffer_len()];
+ repr.emit(&mut Packet::new_unchecked(payload));
+
+ let dst_port = match pq.mdns {
+ #[cfg(feature = "socket-mdns")]
+ MulticastDns::Enabled => MDNS_DNS_PORT,
+ MulticastDns::Disabled => DNS_PORT,
+ };
+
+ let udp_repr = UdpRepr {
+ src_port: pq.port,
+ dst_port,
+ };
+
+ let dst_addr = servers[pq.server_idx];
+ let src_addr = cx.get_source_address(&dst_addr).unwrap(); // TODO remove unwrap
+ let ip_repr = IpRepr::new(
+ src_addr,
+ dst_addr,
+ IpProtocol::Udp,
+ udp_repr.header_len() + payload.len(),
+ hop_limit,
+ );
+
+ net_trace!(
+ "sending {} octets to {} from port {}",
+ payload.len(),
+ ip_repr.dst_addr(),
+ udp_repr.src_port
+ );
+
+ emit(cx, (ip_repr, udp_repr, payload))?;
+
+ pq.retransmit_at = cx.now() + pq.delay;
+ pq.delay = MAX_RETRANSMIT_DELAY.min(pq.delay * 2);
+
+ return Ok(());
+ }
+ }
+
+ // Nothing to dispatch
+ Ok(())
+ }
+
+ pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
+ self.queries
+ .iter()
+ .flatten()
+ .filter_map(|q| match &q.state {
+ State::Pending(pq) => Some(PollAt::Time(pq.retransmit_at)),
+ State::Completed(_) => None,
+ State::Failure => None,
+ })
+ .min()
+ .unwrap_or(PollAt::Ingress)
+ }
+}
+
+fn eq_names<'a>(
+ mut a: impl Iterator<Item = wire::Result<&'a [u8]>>,
+ mut b: impl Iterator<Item = wire::Result<&'a [u8]>>,
+) -> wire::Result<bool> {
+ loop {
+ match (a.next(), b.next()) {
+ // Handle errors
+ (Some(Err(e)), _) => return Err(e),
+ (_, Some(Err(e))) => return Err(e),
+
+ // Both finished -> equal
+ (None, None) => return Ok(true),
+
+ // One finished before the other -> not equal
+ (None, _) => return Ok(false),
+ (_, None) => return Ok(false),
+
+ // Got two labels, check if they're equal
+ (Some(Ok(la)), Some(Ok(lb))) => {
+ if la != lb {
+ return Ok(false);
+ }
+ }
+ }
+ }
+}
+
+fn copy_name<'a, const N: usize>(
+ dest: &mut Vec<u8, N>,
+ name: impl Iterator<Item = wire::Result<&'a [u8]>>,
+) -> Result<(), wire::Error> {
+ dest.truncate(0);
+
+ for label in name {
+ let label = label?;
+ dest.push(label.len() as u8).map_err(|_| wire::Error)?;
+ dest.extend_from_slice(label).map_err(|_| wire::Error)?;
+ }
+
+ // Write terminator 0x00
+ dest.push(0).map_err(|_| wire::Error)?;
+
+ Ok(())
+}
diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs
new file mode 100644
index 0000000..c18b754
--- /dev/null
+++ b/src/socket/icmp.rs
@@ -0,0 +1,1240 @@
+use core::cmp;
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+use crate::phy::ChecksumCapabilities;
+#[cfg(feature = "async")]
+use crate::socket::WakerRegistration;
+use crate::socket::{Context, PollAt};
+
+use crate::storage::Empty;
+use crate::wire::IcmpRepr;
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::{Icmpv4Packet, Icmpv4Repr, Ipv4Repr};
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::{Icmpv6Packet, Icmpv6Repr, Ipv6Repr};
+use crate::wire::{IpAddress, IpListenEndpoint, IpProtocol, IpRepr};
+use crate::wire::{UdpPacket, UdpRepr};
+
+/// Error returned by [`Socket::bind`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum BindError {
+ InvalidState,
+ Unaddressable,
+}
+
+impl core::fmt::Display for BindError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ BindError::InvalidState => write!(f, "invalid state"),
+ BindError::Unaddressable => write!(f, "unaddressable"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BindError {}
+
+/// Error returned by [`Socket::send`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SendError {
+ Unaddressable,
+ BufferFull,
+}
+
+impl core::fmt::Display for SendError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ SendError::Unaddressable => write!(f, "unaddressable"),
+ SendError::BufferFull => write!(f, "buffer full"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for SendError {}
+
+/// Error returned by [`Socket::recv`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecvError {
+ Exhausted,
+ Truncated,
+}
+
+impl core::fmt::Display for RecvError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ RecvError::Exhausted => write!(f, "exhausted"),
+ RecvError::Truncated => write!(f, "truncated"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RecvError {}
+
+/// Type of endpoint to bind the ICMP socket to. See [IcmpSocket::bind] for
+/// more details.
+///
+/// [IcmpSocket::bind]: struct.IcmpSocket.html#method.bind
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Endpoint {
+ #[default]
+ Unspecified,
+ Ident(u16),
+ Udp(IpListenEndpoint),
+}
+
+impl Endpoint {
+ pub fn is_specified(&self) -> bool {
+ match *self {
+ Endpoint::Ident(_) => true,
+ Endpoint::Udp(endpoint) => endpoint.port != 0,
+ Endpoint::Unspecified => false,
+ }
+ }
+}
+
+/// An ICMP packet metadata.
+pub type PacketMetadata = crate::storage::PacketMetadata<IpAddress>;
+
+/// An ICMP packet ring buffer.
+pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, IpAddress>;
+
+/// A ICMP socket
+///
+/// An ICMP socket is bound to a specific [IcmpEndpoint] which may
+/// be a specific UDP port to listen for ICMP error messages related
+/// to the port or a specific ICMP identifier value. See [bind] for
+/// more details.
+///
+/// [IcmpEndpoint]: enum.IcmpEndpoint.html
+/// [bind]: #method.bind
+#[derive(Debug)]
+pub struct Socket<'a> {
+ rx_buffer: PacketBuffer<'a>,
+ tx_buffer: PacketBuffer<'a>,
+ /// The endpoint this socket is communicating with
+ endpoint: Endpoint,
+ /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ hop_limit: Option<u8>,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration,
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration,
+}
+
+impl<'a> Socket<'a> {
+ /// Create an ICMP socket with the given buffers.
+ pub fn new(rx_buffer: PacketBuffer<'a>, tx_buffer: PacketBuffer<'a>) -> Socket<'a> {
+ Socket {
+ rx_buffer,
+ tx_buffer,
+ endpoint: Default::default(),
+ hop_limit: None,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration::new(),
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration::new(),
+ }
+ }
+
+ /// Register a waker for receive operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `recv` method calls, such as receiving data, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_recv_waker(&mut self, waker: &Waker) {
+ self.rx_waker.register(waker)
+ }
+
+ /// Register a waker for send operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `send` method calls, such as space becoming available in the transmit
+ /// buffer, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_send_waker(&mut self, waker: &Waker) {
+ self.tx_waker.register(waker)
+ }
+
+ /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// See also the [set_hop_limit](#method.set_hop_limit) method
+ pub fn hop_limit(&self) -> Option<u8> {
+ self.hop_limit
+ }
+
+ /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
+ /// value (64).
+ ///
+ /// # Panics
+ ///
+ /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
+ ///
+ /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
+ /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
+ pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
+ // A host MUST NOT send a datagram with a hop limit value of 0
+ if let Some(0) = hop_limit {
+ panic!("the time-to-live value of a packet must not be zero")
+ }
+
+ self.hop_limit = hop_limit
+ }
+
+ /// Bind the socket to the given endpoint.
+ ///
+ /// This function returns `Err(Error::Illegal)` if the socket was open
+ /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)`
+ /// if `endpoint` is unspecified (see [is_specified]).
+ ///
+ /// # Examples
+ ///
+ /// ## Bind to ICMP Error messages associated with a specific UDP port:
+ ///
+ /// To [recv] ICMP error messages that are associated with a specific local
+ /// UDP port, the socket may be bound to a given port using [IcmpEndpoint::Udp].
+ /// This may be useful for applications using UDP attempting to detect and/or
+ /// diagnose connection problems.
+ ///
+ /// ```
+ /// use smoltcp::wire::IpListenEndpoint;
+ /// use smoltcp::socket::icmp;
+ /// # let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]);
+ /// # let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]);
+ ///
+ /// let mut icmp_socket = // ...
+ /// # icmp::Socket::new(rx_buffer, tx_buffer);
+ ///
+ /// // Bind to ICMP error responses for UDP packets sent from port 53.
+ /// let endpoint = IpListenEndpoint::from(53);
+ /// icmp_socket.bind(icmp::Endpoint::Udp(endpoint)).unwrap();
+ /// ```
+ ///
+ /// ## Bind to a specific ICMP identifier:
+ ///
+ /// To [send] and [recv] ICMP packets that are not associated with a specific UDP
+ /// port, the socket may be bound to a specific ICMP identifier using
+ /// [IcmpEndpoint::Ident]. This is useful for sending and receiving Echo Request/Reply
+ /// messages.
+ ///
+ /// ```
+ /// use smoltcp::wire::IpListenEndpoint;
+ /// use smoltcp::socket::icmp;
+ /// # let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]);
+ /// # let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]);
+ ///
+ /// let mut icmp_socket = // ...
+ /// # icmp::Socket::new(rx_buffer, tx_buffer);
+ ///
+ /// // Bind to ICMP messages with the ICMP identifier 0x1234
+ /// icmp_socket.bind(icmp::Endpoint::Ident(0x1234)).unwrap();
+ /// ```
+ ///
+ /// [is_specified]: enum.IcmpEndpoint.html#method.is_specified
+ /// [IcmpEndpoint::Ident]: enum.IcmpEndpoint.html#variant.Ident
+ /// [IcmpEndpoint::Udp]: enum.IcmpEndpoint.html#variant.Udp
+ /// [send]: #method.send
+ /// [recv]: #method.recv
+ pub fn bind<T: Into<Endpoint>>(&mut self, endpoint: T) -> Result<(), BindError> {
+ let endpoint = endpoint.into();
+ if !endpoint.is_specified() {
+ return Err(BindError::Unaddressable);
+ }
+
+ if self.is_open() {
+ return Err(BindError::InvalidState);
+ }
+
+ self.endpoint = endpoint;
+
+ #[cfg(feature = "async")]
+ {
+ self.rx_waker.wake();
+ self.tx_waker.wake();
+ }
+
+ Ok(())
+ }
+
+ /// Check whether the transmit buffer is full.
+ #[inline]
+ pub fn can_send(&self) -> bool {
+ !self.tx_buffer.is_full()
+ }
+
+ /// Check whether the receive buffer is not empty.
+ #[inline]
+ pub fn can_recv(&self) -> bool {
+ !self.rx_buffer.is_empty()
+ }
+
+ /// Return the maximum number packets the socket can receive.
+ #[inline]
+ pub fn packet_recv_capacity(&self) -> usize {
+ self.rx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number packets the socket can transmit.
+ #[inline]
+ pub fn packet_send_capacity(&self) -> usize {
+ self.tx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the recv buffer.
+ #[inline]
+ pub fn payload_recv_capacity(&self) -> usize {
+ self.rx_buffer.payload_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the transmit buffer.
+ #[inline]
+ pub fn payload_send_capacity(&self) -> usize {
+ self.tx_buffer.payload_capacity()
+ }
+
+ /// Check whether the socket is open.
+ #[inline]
+ pub fn is_open(&self) -> bool {
+ self.endpoint != Endpoint::Unspecified
+ }
+
+ /// Enqueue a packet to be sent to a given remote address, and return a pointer
+ /// to its payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full,
+ /// `Err(Error::Truncated)` if the requested size is larger than the packet buffer
+ /// size, and `Err(Error::Unaddressable)` if the remote address is unspecified.
+ pub fn send(&mut self, size: usize, endpoint: IpAddress) -> Result<&mut [u8], SendError> {
+ if endpoint.is_unspecified() {
+ return Err(SendError::Unaddressable);
+ }
+
+ let packet_buf = self
+ .tx_buffer
+ .enqueue(size, endpoint)
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!("icmp:{}: buffer to send {} octets", endpoint, size);
+ Ok(packet_buf)
+ }
+
+ /// Enqueue a packet to be send to a given remote address and pass the buffer
+ /// to the provided closure. The closure then returns the size of the data written
+ /// into the buffer.
+ ///
+ /// Also see [send](#method.send).
+ pub fn send_with<F>(
+ &mut self,
+ max_size: usize,
+ endpoint: IpAddress,
+ f: F,
+ ) -> Result<usize, SendError>
+ where
+ F: FnOnce(&mut [u8]) -> usize,
+ {
+ if endpoint.is_unspecified() {
+ return Err(SendError::Unaddressable);
+ }
+
+ let size = self
+ .tx_buffer
+ .enqueue_with_infallible(max_size, endpoint, f)
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!("icmp:{}: buffer to send {} octets", endpoint, size);
+ Ok(size)
+ }
+
+ /// Enqueue a packet to be sent to a given remote address, and fill it from a slice.
+ ///
+ /// See also [send](#method.send).
+ pub fn send_slice(&mut self, data: &[u8], endpoint: IpAddress) -> Result<(), SendError> {
+ let packet_buf = self.send(data.len(), endpoint)?;
+ packet_buf.copy_from_slice(data);
+ Ok(())
+ }
+
+ /// Dequeue a packet received from a remote endpoint, and return the `IpAddress` as well
+ /// as a pointer to the payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty.
+ pub fn recv(&mut self) -> Result<(&[u8], IpAddress), RecvError> {
+ let (endpoint, packet_buf) = self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?;
+
+ net_trace!(
+ "icmp:{}: receive {} buffered octets",
+ endpoint,
+ packet_buf.len()
+ );
+ Ok((packet_buf, endpoint))
+ }
+
+ /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice,
+ /// and return the amount of octets copied as well as the `IpAddress`
+ ///
+ /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
+ /// the packet is dropped and a `RecvError::Truncated` error is returned.
+ ///
+ /// See also [recv](#method.recv).
+ pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<(usize, IpAddress), RecvError> {
+ let (buffer, endpoint) = self.recv()?;
+
+ if data.len() < buffer.len() {
+ return Err(RecvError::Truncated);
+ }
+
+ let length = cmp::min(data.len(), buffer.len());
+ data[..length].copy_from_slice(&buffer[..length]);
+ Ok((length, endpoint))
+ }
+
+ /// Filter determining which packets received by the interface are appended to
+ /// the given sockets received buffer.
+ pub(crate) fn accepts(&self, cx: &mut Context, ip_repr: &IpRepr, icmp_repr: &IcmpRepr) -> bool {
+ match (&self.endpoint, icmp_repr) {
+ // If we are bound to ICMP errors associated to a UDP port, only
+ // accept Destination Unreachable or Time Exceeded messages with
+ // the data containing a UDP packet send from the local port we
+ // are bound to.
+ #[cfg(feature = "proto-ipv4")]
+ (
+ &Endpoint::Udp(endpoint),
+ &IcmpRepr::Ipv4(
+ Icmpv4Repr::DstUnreachable { data, header, .. }
+ | Icmpv4Repr::TimeExceeded { data, header, .. },
+ ),
+ ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr()) => {
+ let packet = UdpPacket::new_unchecked(data);
+ match UdpRepr::parse(
+ &packet,
+ &header.src_addr.into(),
+ &header.dst_addr.into(),
+ &cx.checksum_caps(),
+ ) {
+ Ok(repr) => endpoint.port == repr.src_port,
+ Err(_) => false,
+ }
+ }
+ #[cfg(feature = "proto-ipv6")]
+ (
+ &Endpoint::Udp(endpoint),
+ &IcmpRepr::Ipv6(
+ Icmpv6Repr::DstUnreachable { data, header, .. }
+ | Icmpv6Repr::TimeExceeded { data, header, .. },
+ ),
+ ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr()) => {
+ let packet = UdpPacket::new_unchecked(data);
+ match UdpRepr::parse(
+ &packet,
+ &header.src_addr.into(),
+ &header.dst_addr.into(),
+ &cx.checksum_caps(),
+ ) {
+ Ok(repr) => endpoint.port == repr.src_port,
+ Err(_) => false,
+ }
+ }
+ // If we are bound to a specific ICMP identifier value, only accept an
+ // Echo Request/Reply with the identifier field matching the endpoint
+ // port.
+ #[cfg(feature = "proto-ipv4")]
+ (
+ &Endpoint::Ident(bound_ident),
+ &IcmpRepr::Ipv4(Icmpv4Repr::EchoRequest { ident, .. }),
+ )
+ | (
+ &Endpoint::Ident(bound_ident),
+ &IcmpRepr::Ipv4(Icmpv4Repr::EchoReply { ident, .. }),
+ ) => ident == bound_ident,
+ #[cfg(feature = "proto-ipv6")]
+ (
+ &Endpoint::Ident(bound_ident),
+ &IcmpRepr::Ipv6(Icmpv6Repr::EchoRequest { ident, .. }),
+ )
+ | (
+ &Endpoint::Ident(bound_ident),
+ &IcmpRepr::Ipv6(Icmpv6Repr::EchoReply { ident, .. }),
+ ) => ident == bound_ident,
+ _ => false,
+ }
+ }
+
+ pub(crate) fn process(&mut self, _cx: &mut Context, ip_repr: &IpRepr, icmp_repr: &IcmpRepr) {
+ match icmp_repr {
+ #[cfg(feature = "proto-ipv4")]
+ IcmpRepr::Ipv4(icmp_repr) => {
+ net_trace!("icmp: receiving {} octets", icmp_repr.buffer_len());
+
+ match self
+ .rx_buffer
+ .enqueue(icmp_repr.buffer_len(), ip_repr.src_addr())
+ {
+ Ok(packet_buf) => {
+ icmp_repr.emit(
+ &mut Icmpv4Packet::new_unchecked(packet_buf),
+ &ChecksumCapabilities::default(),
+ );
+ }
+ Err(_) => net_trace!("icmp: buffer full, dropped incoming packet"),
+ }
+ }
+ #[cfg(feature = "proto-ipv6")]
+ IcmpRepr::Ipv6(icmp_repr) => {
+ net_trace!("icmp: receiving {} octets", icmp_repr.buffer_len());
+
+ match self
+ .rx_buffer
+ .enqueue(icmp_repr.buffer_len(), ip_repr.src_addr())
+ {
+ Ok(packet_buf) => icmp_repr.emit(
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ &mut Icmpv6Packet::new_unchecked(packet_buf),
+ &ChecksumCapabilities::default(),
+ ),
+ Err(_) => net_trace!("icmp: buffer full, dropped incoming packet"),
+ }
+ }
+ }
+
+ #[cfg(feature = "async")]
+ self.rx_waker.wake();
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, (IpRepr, IcmpRepr)) -> Result<(), E>,
+ {
+ let hop_limit = self.hop_limit.unwrap_or(64);
+ let res = self.tx_buffer.dequeue_with(|remote_endpoint, packet_buf| {
+ net_trace!(
+ "icmp:{}: sending {} octets",
+ remote_endpoint,
+ packet_buf.len()
+ );
+ match *remote_endpoint {
+ #[cfg(feature = "proto-ipv4")]
+ IpAddress::Ipv4(dst_addr) => {
+ let src_addr = match cx.get_source_address_ipv4(&dst_addr) {
+ Some(addr) => addr,
+ None => {
+ net_trace!(
+ "icmp:{}: not find suitable source address, dropping",
+ remote_endpoint
+ );
+ return Ok(());
+ }
+ };
+ let packet = Icmpv4Packet::new_unchecked(&*packet_buf);
+ let repr = match Icmpv4Repr::parse(&packet, &ChecksumCapabilities::ignored()) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!(
+ "icmp:{}: malformed packet in queue, dropping",
+ remote_endpoint
+ );
+ return Ok(());
+ }
+ };
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Icmp,
+ payload_len: repr.buffer_len(),
+ hop_limit,
+ });
+ emit(cx, (ip_repr, IcmpRepr::Ipv4(repr)))
+ }
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(dst_addr) => {
+ let src_addr = match cx.get_source_address_ipv6(&dst_addr) {
+ Some(addr) => addr,
+ None => {
+ net_trace!(
+ "icmp:{}: not find suitable source address, dropping",
+ remote_endpoint
+ );
+ return Ok(());
+ }
+ };
+ let packet = Icmpv6Packet::new_unchecked(&*packet_buf);
+ let repr = match Icmpv6Repr::parse(
+ &src_addr.into(),
+ &dst_addr.into(),
+ &packet,
+ &ChecksumCapabilities::ignored(),
+ ) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!(
+ "icmp:{}: malformed packet in queue, dropping",
+ remote_endpoint
+ );
+ return Ok(());
+ }
+ };
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: repr.buffer_len(),
+ hop_limit,
+ });
+ emit(cx, (ip_repr, IcmpRepr::Ipv6(repr)))
+ }
+ }
+ });
+ match res {
+ Err(Empty) => Ok(()),
+ Ok(Err(e)) => Err(e),
+ Ok(Ok(())) => {
+ #[cfg(feature = "async")]
+ self.tx_waker.wake();
+ Ok(())
+ }
+ }
+ }
+
+ pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
+ if self.tx_buffer.is_empty() {
+ PollAt::Ingress
+ } else {
+ PollAt::Now
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests_common {
+ pub use super::*;
+ pub use crate::wire::IpAddress;
+
+ pub fn buffer(packets: usize) -> PacketBuffer<'static> {
+ PacketBuffer::new(vec![PacketMetadata::EMPTY; packets], vec![0; 66 * packets])
+ }
+
+ pub fn socket(
+ rx_buffer: PacketBuffer<'static>,
+ tx_buffer: PacketBuffer<'static>,
+ ) -> Socket<'static> {
+ Socket::new(rx_buffer, tx_buffer)
+ }
+
+ pub const LOCAL_PORT: u16 = 53;
+
+ pub static UDP_REPR: UdpRepr = UdpRepr {
+ src_port: 53,
+ dst_port: 9090,
+ };
+
+ pub static UDP_PAYLOAD: &[u8] = &[0xff; 10];
+}
+
+#[cfg(all(test, feature = "proto-ipv4"))]
+mod test_ipv4 {
+ use crate::phy::Medium;
+ use crate::tests::setup;
+ use rstest::*;
+
+ use super::tests_common::*;
+ use crate::wire::{Icmpv4DstUnreachable, IpEndpoint, Ipv4Address};
+
+ const REMOTE_IPV4: Ipv4Address = Ipv4Address([192, 168, 1, 2]);
+ const LOCAL_IPV4: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
+ const LOCAL_END_V4: IpEndpoint = IpEndpoint {
+ addr: IpAddress::Ipv4(LOCAL_IPV4),
+ port: LOCAL_PORT,
+ };
+
+ static ECHOV4_REPR: Icmpv4Repr = Icmpv4Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0x5678,
+ data: &[0xff; 16],
+ };
+
+ static LOCAL_IPV4_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: LOCAL_IPV4,
+ dst_addr: REMOTE_IPV4,
+ next_header: IpProtocol::Icmp,
+ payload_len: 24,
+ hop_limit: 0x40,
+ });
+
+ static REMOTE_IPV4_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: REMOTE_IPV4,
+ dst_addr: LOCAL_IPV4,
+ next_header: IpProtocol::Icmp,
+ payload_len: 24,
+ hop_limit: 0x40,
+ });
+
+ #[test]
+ fn test_send_unaddressable() {
+ let mut socket = socket(buffer(0), buffer(1));
+ assert_eq!(
+ socket.send_slice(b"abcdef", IpAddress::Ipv4(Ipv4Address::default())),
+ Err(SendError::Unaddressable)
+ );
+ assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV4.into()), Ok(()));
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_send_dispatch(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(0), buffer(1));
+ let checksum = ChecksumCapabilities::default();
+
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+
+ // This buffer is too long
+ assert_eq!(
+ socket.send_slice(&[0xff; 67], REMOTE_IPV4.into()),
+ Err(SendError::BufferFull)
+ );
+ assert!(socket.can_send());
+
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes);
+ ECHOV4_REPR.emit(&mut packet, &checksum);
+
+ assert_eq!(
+ socket.send_slice(&*packet.into_inner(), REMOTE_IPV4.into()),
+ Ok(())
+ );
+ assert_eq!(
+ socket.send_slice(b"123456", REMOTE_IPV4.into()),
+ Err(SendError::BufferFull)
+ );
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
+ assert_eq!(ip_repr, LOCAL_IPV4_REPR);
+ assert_eq!(icmp_repr, ECHOV4_REPR.into());
+ Err(())
+ }),
+ Err(())
+ );
+ // buffer is not taken off of the tx queue due to the error
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
+ assert_eq!(ip_repr, LOCAL_IPV4_REPR);
+ assert_eq!(icmp_repr, ECHOV4_REPR.into());
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ // buffer is taken off of the queue this time
+ assert!(socket.can_send());
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_set_hop_limit_v4(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut s = socket(buffer(0), buffer(1));
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes);
+ ECHOV4_REPR.emit(&mut packet, &checksum);
+
+ s.set_hop_limit(Some(0x2a));
+
+ assert_eq!(
+ s.send_slice(&*packet.into_inner(), REMOTE_IPV4.into()),
+ Ok(())
+ );
+ assert_eq!(
+ s.dispatch(cx, |_, (ip_repr, _)| {
+ assert_eq!(
+ ip_repr,
+ IpRepr::Ipv4(Ipv4Repr {
+ src_addr: LOCAL_IPV4,
+ dst_addr: REMOTE_IPV4,
+ next_header: IpProtocol::Icmp,
+ payload_len: ECHOV4_REPR.buffer_len(),
+ hop_limit: 0x2a,
+ })
+ );
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_recv_process(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
+
+ assert!(!socket.can_recv());
+ assert_eq!(socket.recv(), Err(RecvError::Exhausted));
+
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]);
+ ECHOV4_REPR.emit(&mut packet, &checksum);
+ let data = &*packet.into_inner();
+
+ assert!(socket.accepts(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into()));
+ socket.process(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into());
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into()));
+ socket.process(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into());
+
+ assert_eq!(socket.recv(), Ok((data, REMOTE_IPV4.into())));
+ assert!(!socket.can_recv());
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_accept_bad_id(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
+
+ let checksum = ChecksumCapabilities::default();
+ let mut bytes = [0xff; 20];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes);
+ let icmp_repr = Icmpv4Repr::EchoRequest {
+ ident: 0x4321,
+ seq_no: 0x5678,
+ data: &[0xff; 16],
+ };
+ icmp_repr.emit(&mut packet, &checksum);
+
+ // Ensure that a packet with an identifier that isn't the bound
+ // ID is not accepted
+ assert!(!socket.accepts(cx, &REMOTE_IPV4_REPR, &icmp_repr.into()));
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_accepts_udp(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V4.into())), Ok(()));
+
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 18];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes);
+ UDP_REPR.emit(
+ &mut packet,
+ &REMOTE_IPV4.into(),
+ &LOCAL_IPV4.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(UDP_PAYLOAD),
+ &checksum,
+ );
+
+ let data = &*packet.into_inner();
+
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: Ipv4Repr {
+ src_addr: LOCAL_IPV4,
+ dst_addr: REMOTE_IPV4,
+ next_header: IpProtocol::Icmp,
+ payload_len: 12,
+ hop_limit: 0x40,
+ },
+ data,
+ };
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: REMOTE_IPV4,
+ dst_addr: LOCAL_IPV4,
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 0x40,
+ });
+
+ assert!(!socket.can_recv());
+
+ // Ensure we can accept ICMP error response to the bound
+ // UDP port
+ assert!(socket.accepts(cx, &ip_repr, &icmp_repr.into()));
+ socket.process(cx, &ip_repr, &icmp_repr.into());
+ assert!(socket.can_recv());
+
+ let mut bytes = [0x00; 46];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]);
+ icmp_repr.emit(&mut packet, &checksum);
+ assert_eq!(
+ socket.recv(),
+ Ok((&*packet.into_inner(), REMOTE_IPV4.into()))
+ );
+ assert!(!socket.can_recv());
+ }
+}
+
+#[cfg(all(test, feature = "proto-ipv6"))]
+mod test_ipv6 {
+ use crate::phy::Medium;
+ use crate::tests::setup;
+ use rstest::*;
+
+ use super::tests_common::*;
+
+ use crate::wire::{Icmpv6DstUnreachable, IpEndpoint, Ipv6Address};
+
+ const REMOTE_IPV6: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
+ const LOCAL_IPV6: Ipv6Address =
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+ const LOCAL_END_V6: IpEndpoint = IpEndpoint {
+ addr: IpAddress::Ipv6(LOCAL_IPV6),
+ port: LOCAL_PORT,
+ };
+ static ECHOV6_REPR: Icmpv6Repr = Icmpv6Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0x5678,
+ data: &[0xff; 16],
+ };
+
+ static LOCAL_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: LOCAL_IPV6,
+ dst_addr: REMOTE_IPV6,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 24,
+ hop_limit: 0x40,
+ });
+
+ static REMOTE_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: REMOTE_IPV6,
+ dst_addr: LOCAL_IPV6,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 24,
+ hop_limit: 0x40,
+ });
+
+ #[test]
+ fn test_send_unaddressable() {
+ let mut socket = socket(buffer(0), buffer(1));
+ assert_eq!(
+ socket.send_slice(b"abcdef", IpAddress::Ipv6(Ipv6Address::default())),
+ Err(SendError::Unaddressable)
+ );
+ assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV6.into()), Ok(()));
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_send_dispatch(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(0), buffer(1));
+ let checksum = ChecksumCapabilities::default();
+
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+
+ // This buffer is too long
+ assert_eq!(
+ socket.send_slice(&[0xff; 67], REMOTE_IPV6.into()),
+ Err(SendError::BufferFull)
+ );
+ assert!(socket.can_send());
+
+ let mut bytes = vec![0xff; 24];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes);
+ ECHOV6_REPR.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+
+ assert_eq!(
+ socket.send_slice(&*packet.into_inner(), REMOTE_IPV6.into()),
+ Ok(())
+ );
+ assert_eq!(
+ socket.send_slice(b"123456", REMOTE_IPV6.into()),
+ Err(SendError::BufferFull)
+ );
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
+ assert_eq!(ip_repr, LOCAL_IPV6_REPR);
+ assert_eq!(icmp_repr, ECHOV6_REPR.into());
+ Err(())
+ }),
+ Err(())
+ );
+ // buffer is not taken off of the tx queue due to the error
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
+ assert_eq!(ip_repr, LOCAL_IPV6_REPR);
+ assert_eq!(icmp_repr, ECHOV6_REPR.into());
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ // buffer is taken off of the queue this time
+ assert!(socket.can_send());
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_set_hop_limit(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut s = socket(buffer(0), buffer(1));
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = vec![0xff; 24];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes);
+ ECHOV6_REPR.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+
+ s.set_hop_limit(Some(0x2a));
+
+ assert_eq!(
+ s.send_slice(&*packet.into_inner(), REMOTE_IPV6.into()),
+ Ok(())
+ );
+ assert_eq!(
+ s.dispatch(cx, |_, (ip_repr, _)| {
+ assert_eq!(
+ ip_repr,
+ IpRepr::Ipv6(Ipv6Repr {
+ src_addr: LOCAL_IPV6,
+ dst_addr: REMOTE_IPV6,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: ECHOV6_REPR.buffer_len(),
+ hop_limit: 0x2a,
+ })
+ );
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_recv_process(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
+
+ assert!(!socket.can_recv());
+ assert_eq!(socket.recv(), Err(RecvError::Exhausted));
+
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]);
+ ECHOV6_REPR.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+ let data = &*packet.into_inner();
+
+ assert!(socket.accepts(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
+ socket.process(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
+ socket.process(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+
+ assert_eq!(socket.recv(), Ok((data, REMOTE_IPV6.into())));
+ assert!(!socket.can_recv());
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_truncated_recv_slice(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
+
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]);
+ ECHOV6_REPR.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+
+ assert!(socket.accepts(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
+ socket.process(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
+ socket.process(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+
+ let mut buffer = [0u8; 1];
+ assert_eq!(
+ socket.recv_slice(&mut buffer[..]),
+ Err(RecvError::Truncated)
+ );
+ assert!(!socket.can_recv());
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_accept_bad_id(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
+
+ let checksum = ChecksumCapabilities::default();
+ let mut bytes = [0xff; 20];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes);
+ let icmp_repr = Icmpv6Repr::EchoRequest {
+ ident: 0x4321,
+ seq_no: 0x5678,
+ data: &[0xff; 16],
+ };
+ icmp_repr.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+
+ // Ensure that a packet with an identifier that isn't the bound
+ // ID is not accepted
+ assert!(!socket.accepts(cx, &REMOTE_IPV6_REPR, &icmp_repr.into()));
+ }
+
+ #[rstest]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ fn test_accepts_udp(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(1));
+ assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V6.into())), Ok(()));
+
+ let checksum = ChecksumCapabilities::default();
+
+ let mut bytes = [0xff; 18];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes);
+ UDP_REPR.emit(
+ &mut packet,
+ &REMOTE_IPV6.into(),
+ &LOCAL_IPV6.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(UDP_PAYLOAD),
+ &checksum,
+ );
+
+ let data = &*packet.into_inner();
+
+ let icmp_repr = Icmpv6Repr::DstUnreachable {
+ reason: Icmpv6DstUnreachable::PortUnreachable,
+ header: Ipv6Repr {
+ src_addr: LOCAL_IPV6,
+ dst_addr: REMOTE_IPV6,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 12,
+ hop_limit: 0x40,
+ },
+ data,
+ };
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: REMOTE_IPV6,
+ dst_addr: LOCAL_IPV6,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 0x40,
+ });
+
+ assert!(!socket.can_recv());
+
+ // Ensure we can accept ICMP error response to the bound
+ // UDP port
+ assert!(socket.accepts(cx, &ip_repr, &icmp_repr.into()));
+ socket.process(cx, &ip_repr, &icmp_repr.into());
+ assert!(socket.can_recv());
+
+ let mut bytes = [0x00; 66];
+ let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]);
+ icmp_repr.emit(
+ &LOCAL_IPV6.into(),
+ &REMOTE_IPV6.into(),
+ &mut packet,
+ &checksum,
+ );
+ assert_eq!(
+ socket.recv(),
+ Ok((&*packet.into_inner(), REMOTE_IPV6.into()))
+ );
+ assert!(!socket.can_recv());
+ }
+}
diff --git a/src/socket/mod.rs b/src/socket/mod.rs
new file mode 100644
index 0000000..7d48b42
--- /dev/null
+++ b/src/socket/mod.rs
@@ -0,0 +1,141 @@
+/*! Communication between endpoints.
+
+The `socket` module deals with *network endpoints* and *buffering*.
+It provides interfaces for accessing buffers of data, and protocol state machines
+for filling and emptying these buffers.
+
+The programming interface implemented here differs greatly from the common Berkeley socket
+interface. Specifically, in the Berkeley interface the buffering is implicit:
+the operating system decides on the good size for a buffer and manages it.
+The interface implemented by this module uses explicit buffering: you decide on the good
+size for a buffer, allocate it, and let the networking stack use it.
+*/
+
+use crate::iface::Context;
+use crate::time::Instant;
+
+#[cfg(feature = "socket-dhcpv4")]
+pub mod dhcpv4;
+#[cfg(feature = "socket-dns")]
+pub mod dns;
+#[cfg(feature = "socket-icmp")]
+pub mod icmp;
+#[cfg(feature = "socket-raw")]
+pub mod raw;
+#[cfg(feature = "socket-tcp")]
+pub mod tcp;
+#[cfg(feature = "socket-udp")]
+pub mod udp;
+
+#[cfg(feature = "async")]
+mod waker;
+
+#[cfg(feature = "async")]
+pub(crate) use self::waker::WakerRegistration;
+
+/// Gives an indication on the next time the socket should be polled.
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub(crate) enum PollAt {
+ /// The socket needs to be polled immediately.
+ Now,
+ /// The socket needs to be polled at given [Instant][struct.Instant].
+ Time(Instant),
+ /// The socket does not need to be polled unless there are external changes.
+ Ingress,
+}
+
+/// A network socket.
+///
+/// This enumeration abstracts the various types of sockets based on the IP protocol.
+/// To downcast a `Socket` value to a concrete socket, use the [AnySocket] trait,
+/// e.g. to get `udp::Socket`, call `udp::Socket::downcast(socket)`.
+///
+/// It is usually more convenient to use [SocketSet::get] instead.
+///
+/// [AnySocket]: trait.AnySocket.html
+/// [SocketSet::get]: struct.SocketSet.html#method.get
+#[derive(Debug)]
+pub enum Socket<'a> {
+ #[cfg(feature = "socket-raw")]
+ Raw(raw::Socket<'a>),
+ #[cfg(feature = "socket-icmp")]
+ Icmp(icmp::Socket<'a>),
+ #[cfg(feature = "socket-udp")]
+ Udp(udp::Socket<'a>),
+ #[cfg(feature = "socket-tcp")]
+ Tcp(tcp::Socket<'a>),
+ #[cfg(feature = "socket-dhcpv4")]
+ Dhcpv4(dhcpv4::Socket<'a>),
+ #[cfg(feature = "socket-dns")]
+ Dns(dns::Socket<'a>),
+}
+
+impl<'a> Socket<'a> {
+ pub(crate) fn poll_at(&self, cx: &mut Context) -> PollAt {
+ match self {
+ #[cfg(feature = "socket-raw")]
+ Socket::Raw(s) => s.poll_at(cx),
+ #[cfg(feature = "socket-icmp")]
+ Socket::Icmp(s) => s.poll_at(cx),
+ #[cfg(feature = "socket-udp")]
+ Socket::Udp(s) => s.poll_at(cx),
+ #[cfg(feature = "socket-tcp")]
+ Socket::Tcp(s) => s.poll_at(cx),
+ #[cfg(feature = "socket-dhcpv4")]
+ Socket::Dhcpv4(s) => s.poll_at(cx),
+ #[cfg(feature = "socket-dns")]
+ Socket::Dns(s) => s.poll_at(cx),
+ }
+ }
+}
+
+/// A conversion trait for network sockets.
+pub trait AnySocket<'a> {
+ fn upcast(self) -> Socket<'a>;
+ fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self>
+ where
+ Self: Sized;
+ fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self>
+ where
+ Self: Sized;
+}
+
+macro_rules! from_socket {
+ ($socket:ty, $variant:ident) => {
+ impl<'a> AnySocket<'a> for $socket {
+ fn upcast(self) -> Socket<'a> {
+ Socket::$variant(self)
+ }
+
+ fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> {
+ #[allow(unreachable_patterns)]
+ match socket {
+ Socket::$variant(socket) => Some(socket),
+ _ => None,
+ }
+ }
+
+ fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> {
+ #[allow(unreachable_patterns)]
+ match socket {
+ Socket::$variant(socket) => Some(socket),
+ _ => None,
+ }
+ }
+ }
+ };
+}
+
+#[cfg(feature = "socket-raw")]
+from_socket!(raw::Socket<'a>, Raw);
+#[cfg(feature = "socket-icmp")]
+from_socket!(icmp::Socket<'a>, Icmp);
+#[cfg(feature = "socket-udp")]
+from_socket!(udp::Socket<'a>, Udp);
+#[cfg(feature = "socket-tcp")]
+from_socket!(tcp::Socket<'a>, Tcp);
+#[cfg(feature = "socket-dhcpv4")]
+from_socket!(dhcpv4::Socket<'a>, Dhcpv4);
+#[cfg(feature = "socket-dns")]
+from_socket!(dns::Socket<'a>, Dns);
diff --git a/src/socket/raw.rs b/src/socket/raw.rs
new file mode 100644
index 0000000..bb3a204
--- /dev/null
+++ b/src/socket/raw.rs
@@ -0,0 +1,848 @@
+use core::cmp::min;
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+use crate::iface::Context;
+use crate::socket::PollAt;
+#[cfg(feature = "async")]
+use crate::socket::WakerRegistration;
+
+use crate::storage::Empty;
+use crate::wire::{IpProtocol, IpRepr, IpVersion};
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::{Ipv4Packet, Ipv4Repr};
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::{Ipv6Packet, Ipv6Repr};
+
+/// Error returned by [`Socket::bind`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum BindError {
+ InvalidState,
+ Unaddressable,
+}
+
+impl core::fmt::Display for BindError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ BindError::InvalidState => write!(f, "invalid state"),
+ BindError::Unaddressable => write!(f, "unaddressable"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BindError {}
+
+/// Error returned by [`Socket::send`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SendError {
+ BufferFull,
+}
+
+impl core::fmt::Display for SendError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ SendError::BufferFull => write!(f, "buffer full"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for SendError {}
+
+/// Error returned by [`Socket::recv`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecvError {
+ Exhausted,
+ Truncated,
+}
+
+impl core::fmt::Display for RecvError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ RecvError::Exhausted => write!(f, "exhausted"),
+ RecvError::Truncated => write!(f, "truncated"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RecvError {}
+
+/// A UDP packet metadata.
+pub type PacketMetadata = crate::storage::PacketMetadata<()>;
+
+/// A UDP packet ring buffer.
+pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, ()>;
+
+/// A raw IP socket.
+///
+/// A raw socket is bound to a specific IP protocol, and owns
+/// transmit and receive packet buffers.
+#[derive(Debug)]
+pub struct Socket<'a> {
+ ip_version: IpVersion,
+ ip_protocol: IpProtocol,
+ rx_buffer: PacketBuffer<'a>,
+ tx_buffer: PacketBuffer<'a>,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration,
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration,
+}
+
+impl<'a> Socket<'a> {
+ /// Create a raw IP socket bound to the given IP version and datagram protocol,
+ /// with the given buffers.
+ pub fn new(
+ ip_version: IpVersion,
+ ip_protocol: IpProtocol,
+ rx_buffer: PacketBuffer<'a>,
+ tx_buffer: PacketBuffer<'a>,
+ ) -> Socket<'a> {
+ Socket {
+ ip_version,
+ ip_protocol,
+ rx_buffer,
+ tx_buffer,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration::new(),
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration::new(),
+ }
+ }
+
+ /// Register a waker for receive operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `recv` method calls, such as receiving data, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_recv_waker(&mut self, waker: &Waker) {
+ self.rx_waker.register(waker)
+ }
+
+ /// Register a waker for send operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `send` method calls, such as space becoming available in the transmit
+ /// buffer, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_send_waker(&mut self, waker: &Waker) {
+ self.tx_waker.register(waker)
+ }
+
+ /// Return the IP version the socket is bound to.
+ #[inline]
+ pub fn ip_version(&self) -> IpVersion {
+ self.ip_version
+ }
+
+ /// Return the IP protocol the socket is bound to.
+ #[inline]
+ pub fn ip_protocol(&self) -> IpProtocol {
+ self.ip_protocol
+ }
+
+ /// Check whether the transmit buffer is full.
+ #[inline]
+ pub fn can_send(&self) -> bool {
+ !self.tx_buffer.is_full()
+ }
+
+ /// Check whether the receive buffer is not empty.
+ #[inline]
+ pub fn can_recv(&self) -> bool {
+ !self.rx_buffer.is_empty()
+ }
+
+ /// Return the maximum number packets the socket can receive.
+ #[inline]
+ pub fn packet_recv_capacity(&self) -> usize {
+ self.rx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number packets the socket can transmit.
+ #[inline]
+ pub fn packet_send_capacity(&self) -> usize {
+ self.tx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the recv buffer.
+ #[inline]
+ pub fn payload_recv_capacity(&self) -> usize {
+ self.rx_buffer.payload_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the transmit buffer.
+ #[inline]
+ pub fn payload_send_capacity(&self) -> usize {
+ self.tx_buffer.payload_capacity()
+ }
+
+ /// Enqueue a packet to send, and return a pointer to its payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full,
+ /// and `Err(Error::Truncated)` if there is not enough transmit buffer capacity
+ /// to ever send this packet.
+ ///
+ /// If the buffer is filled in a way that does not match the socket's
+ /// IP version or protocol, the packet will be silently dropped.
+ ///
+ /// **Note:** The IP header is parsed and re-serialized, and may not match
+ /// the header actually transmitted bit for bit.
+ pub fn send(&mut self, size: usize) -> Result<&mut [u8], SendError> {
+ let packet_buf = self
+ .tx_buffer
+ .enqueue(size, ())
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!(
+ "raw:{}:{}: buffer to send {} octets",
+ self.ip_version,
+ self.ip_protocol,
+ packet_buf.len()
+ );
+ Ok(packet_buf)
+ }
+
+ /// Enqueue a packet to be send and pass the buffer to the provided closure.
+ /// The closure then returns the size of the data written into the buffer.
+ ///
+ /// Also see [send](#method.send).
+ pub fn send_with<F>(&mut self, max_size: usize, f: F) -> Result<usize, SendError>
+ where
+ F: FnOnce(&mut [u8]) -> usize,
+ {
+ let size = self
+ .tx_buffer
+ .enqueue_with_infallible(max_size, (), f)
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!(
+ "raw:{}:{}: buffer to send {} octets",
+ self.ip_version,
+ self.ip_protocol,
+ size
+ );
+
+ Ok(size)
+ }
+
+ /// Enqueue a packet to send, and fill it from a slice.
+ ///
+ /// See also [send](#method.send).
+ pub fn send_slice(&mut self, data: &[u8]) -> Result<(), SendError> {
+ self.send(data.len())?.copy_from_slice(data);
+ Ok(())
+ }
+
+ /// Dequeue a packet, and return a pointer to the payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty.
+ ///
+ /// **Note:** The IP header is parsed and re-serialized, and may not match
+ /// the header actually received bit for bit.
+ pub fn recv(&mut self) -> Result<&[u8], RecvError> {
+ let ((), packet_buf) = self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?;
+
+ net_trace!(
+ "raw:{}:{}: receive {} buffered octets",
+ self.ip_version,
+ self.ip_protocol,
+ packet_buf.len()
+ );
+ Ok(packet_buf)
+ }
+
+ /// Dequeue a packet, and copy the payload into the given slice.
+ ///
+ /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
+ /// the packet is dropped and a `RecvError::Truncated` error is returned.
+ ///
+ /// See also [recv](#method.recv).
+ pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<usize, RecvError> {
+ let buffer = self.recv()?;
+ if data.len() < buffer.len() {
+ return Err(RecvError::Truncated);
+ }
+
+ let length = min(data.len(), buffer.len());
+ data[..length].copy_from_slice(&buffer[..length]);
+ Ok(length)
+ }
+
+ /// Peek at a packet in the receive buffer and return a pointer to the
+ /// payload without removing the packet from the receive buffer.
+ /// This function otherwise behaves identically to [recv](#method.recv).
+ ///
+ /// It returns `Err(Error::Exhausted)` if the receive buffer is empty.
+ pub fn peek(&mut self) -> Result<&[u8], RecvError> {
+ let ((), packet_buf) = self.rx_buffer.peek().map_err(|_| RecvError::Exhausted)?;
+
+ net_trace!(
+ "raw:{}:{}: receive {} buffered octets",
+ self.ip_version,
+ self.ip_protocol,
+ packet_buf.len()
+ );
+
+ Ok(packet_buf)
+ }
+
+ /// Peek at a packet in the receive buffer, copy the payload into the given slice,
+ /// and return the amount of octets copied without removing the packet from the receive buffer.
+ /// This function otherwise behaves identically to [recv_slice](#method.recv_slice).
+ ///
+ /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
+ /// no data is copied into the provided buffer and a `RecvError::Truncated` error is returned.
+ ///
+ /// See also [peek](#method.peek).
+ pub fn peek_slice(&mut self, data: &mut [u8]) -> Result<usize, RecvError> {
+ let buffer = self.peek()?;
+ if data.len() < buffer.len() {
+ return Err(RecvError::Truncated);
+ }
+
+ let length = min(data.len(), buffer.len());
+ data[..length].copy_from_slice(&buffer[..length]);
+ Ok(length)
+ }
+
+ pub(crate) fn accepts(&self, ip_repr: &IpRepr) -> bool {
+ if ip_repr.version() != self.ip_version {
+ return false;
+ }
+ if ip_repr.next_header() != self.ip_protocol {
+ return false;
+ }
+
+ true
+ }
+
+ pub(crate) fn process(&mut self, cx: &mut Context, ip_repr: &IpRepr, payload: &[u8]) {
+ debug_assert!(self.accepts(ip_repr));
+
+ let header_len = ip_repr.header_len();
+ let total_len = header_len + payload.len();
+
+ net_trace!(
+ "raw:{}:{}: receiving {} octets",
+ self.ip_version,
+ self.ip_protocol,
+ total_len
+ );
+
+ match self.rx_buffer.enqueue(total_len, ()) {
+ Ok(buf) => {
+ ip_repr.emit(&mut buf[..header_len], &cx.checksum_caps());
+ buf[header_len..].copy_from_slice(payload);
+ }
+ Err(_) => net_trace!(
+ "raw:{}:{}: buffer full, dropped incoming packet",
+ self.ip_version,
+ self.ip_protocol
+ ),
+ }
+
+ #[cfg(feature = "async")]
+ self.rx_waker.wake();
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, (IpRepr, &[u8])) -> Result<(), E>,
+ {
+ let ip_protocol = self.ip_protocol;
+ let ip_version = self.ip_version;
+ let _checksum_caps = &cx.checksum_caps();
+ let res = self.tx_buffer.dequeue_with(|&mut (), buffer| {
+ match IpVersion::of_packet(buffer) {
+ #[cfg(feature = "proto-ipv4")]
+ Ok(IpVersion::Ipv4) => {
+ let mut packet = match Ipv4Packet::new_checked(buffer) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("raw: malformed ipv6 packet in queue, dropping.");
+ return Ok(());
+ }
+ };
+ if packet.next_header() != ip_protocol {
+ net_trace!("raw: sent packet with wrong ip protocol, dropping.");
+ return Ok(());
+ }
+ if _checksum_caps.ipv4.tx() {
+ packet.fill_checksum();
+ } else {
+ // make sure we get a consistently zeroed checksum,
+ // since implementations might rely on it
+ packet.set_checksum(0);
+ }
+
+ let packet = Ipv4Packet::new_unchecked(&*packet.into_inner());
+ let ipv4_repr = match Ipv4Repr::parse(&packet, _checksum_caps) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("raw: malformed ipv4 packet in queue, dropping.");
+ return Ok(());
+ }
+ };
+ net_trace!("raw:{}:{}: sending", ip_version, ip_protocol);
+ emit(cx, (IpRepr::Ipv4(ipv4_repr), packet.payload()))
+ }
+ #[cfg(feature = "proto-ipv6")]
+ Ok(IpVersion::Ipv6) => {
+ let packet = match Ipv6Packet::new_checked(buffer) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("raw: malformed ipv6 packet in queue, dropping.");
+ return Ok(());
+ }
+ };
+ if packet.next_header() != ip_protocol {
+ net_trace!("raw: sent ipv6 packet with wrong ip protocol, dropping.");
+ return Ok(());
+ }
+ let packet = Ipv6Packet::new_unchecked(&*packet.into_inner());
+ let ipv6_repr = match Ipv6Repr::parse(&packet) {
+ Ok(x) => x,
+ Err(_) => {
+ net_trace!("raw: malformed ipv6 packet in queue, dropping.");
+ return Ok(());
+ }
+ };
+
+ net_trace!("raw:{}:{}: sending", ip_version, ip_protocol);
+ emit(cx, (IpRepr::Ipv6(ipv6_repr), packet.payload()))
+ }
+ Err(_) => {
+ net_trace!("raw: sent packet with invalid IP version, dropping.");
+ Ok(())
+ }
+ }
+ });
+ match res {
+ Err(Empty) => Ok(()),
+ Ok(Err(e)) => Err(e),
+ Ok(Ok(())) => {
+ #[cfg(feature = "async")]
+ self.tx_waker.wake();
+ Ok(())
+ }
+ }
+ }
+
+ pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
+ if self.tx_buffer.is_empty() {
+ PollAt::Ingress
+ } else {
+ PollAt::Now
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::phy::Medium;
+ use crate::tests::setup;
+ use rstest::*;
+
+ use super::*;
+ use crate::wire::IpRepr;
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::{Ipv4Address, Ipv4Repr};
+ #[cfg(feature = "proto-ipv6")]
+ use crate::wire::{Ipv6Address, Ipv6Repr};
+
+ fn buffer(packets: usize) -> PacketBuffer<'static> {
+ PacketBuffer::new(vec![PacketMetadata::EMPTY; packets], vec![0; 48 * packets])
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ mod ipv4_locals {
+ use super::*;
+
+ pub fn socket(
+ rx_buffer: PacketBuffer<'static>,
+ tx_buffer: PacketBuffer<'static>,
+ ) -> Socket<'static> {
+ Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Unknown(IP_PROTO),
+ rx_buffer,
+ tx_buffer,
+ )
+ }
+
+ pub const IP_PROTO: u8 = 63;
+ pub const HEADER_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([10, 0, 0, 1]),
+ dst_addr: Ipv4Address([10, 0, 0, 2]),
+ next_header: IpProtocol::Unknown(IP_PROTO),
+ payload_len: 4,
+ hop_limit: 64,
+ });
+ pub const PACKET_BYTES: [u8; 24] = [
+ 0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x3f, 0x00, 0x00, 0x0a, 0x00,
+ 0x00, 0x01, 0x0a, 0x00, 0x00, 0x02, 0xaa, 0x00, 0x00, 0xff,
+ ];
+ pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ mod ipv6_locals {
+ use super::*;
+
+ pub fn socket(
+ rx_buffer: PacketBuffer<'static>,
+ tx_buffer: PacketBuffer<'static>,
+ ) -> Socket<'static> {
+ Socket::new(
+ IpVersion::Ipv6,
+ IpProtocol::Unknown(IP_PROTO),
+ rx_buffer,
+ tx_buffer,
+ )
+ }
+
+ pub const IP_PROTO: u8 = 63;
+ pub const HEADER_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: Ipv6Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ]),
+ dst_addr: Ipv6Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02,
+ ]),
+ next_header: IpProtocol::Unknown(IP_PROTO),
+ payload_len: 4,
+ hop_limit: 64,
+ });
+
+ pub const PACKET_BYTES: [u8; 44] = [
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3f, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x00,
+ 0x00, 0xff,
+ ];
+
+ pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+ }
+
+ macro_rules! reusable_ip_specific_tests {
+ ($module:ident, $socket:path, $hdr:path, $packet:path, $payload:path) => {
+ mod $module {
+ use super::*;
+
+ #[test]
+ fn test_send_truncated() {
+ let mut socket = $socket(buffer(0), buffer(1));
+ assert_eq!(socket.send_slice(&[0; 56][..]), Err(SendError::BufferFull));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_send_dispatch(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut cx = iface.context();
+ let mut socket = $socket(buffer(0), buffer(1));
+
+ assert!(socket.can_send());
+ assert_eq!(
+ socket.dispatch(&mut cx, |_, _| unreachable!()),
+ Ok::<_, ()>(())
+ );
+
+ assert_eq!(socket.send_slice(&$packet[..]), Ok(()));
+ assert_eq!(socket.send_slice(b""), Err(SendError::BufferFull));
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(&mut cx, |_, (ip_repr, ip_payload)| {
+ assert_eq!(ip_repr, $hdr);
+ assert_eq!(ip_payload, &$payload);
+ Err(())
+ }),
+ Err(())
+ );
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(&mut cx, |_, (ip_repr, ip_payload)| {
+ assert_eq!(ip_repr, $hdr);
+ assert_eq!(ip_payload, &$payload);
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ assert!(socket.can_send());
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_recv_truncated_slice(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut cx = iface.context();
+ let mut socket = $socket(buffer(1), buffer(0));
+
+ assert!(socket.accepts(&$hdr));
+ socket.process(&mut cx, &$hdr, &$payload);
+
+ let mut slice = [0; 4];
+ assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_recv_truncated_packet(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut cx = iface.context();
+ let mut socket = $socket(buffer(1), buffer(0));
+
+ let mut buffer = vec![0; 128];
+ buffer[..$packet.len()].copy_from_slice(&$packet[..]);
+
+ assert!(socket.accepts(&$hdr));
+ socket.process(&mut cx, &$hdr, &buffer);
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_peek_truncated_slice(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut cx = iface.context();
+ let mut socket = $socket(buffer(1), buffer(0));
+
+ assert!(socket.accepts(&$hdr));
+ socket.process(&mut cx, &$hdr, &$payload);
+
+ let mut slice = [0; 4];
+ assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Truncated));
+ assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated));
+ assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Exhausted));
+ }
+ }
+ };
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ reusable_ip_specific_tests!(
+ ipv4,
+ ipv4_locals::socket,
+ ipv4_locals::HEADER_REPR,
+ ipv4_locals::PACKET_BYTES,
+ ipv4_locals::PACKET_PAYLOAD
+ );
+
+ #[cfg(feature = "proto-ipv6")]
+ reusable_ip_specific_tests!(
+ ipv6,
+ ipv6_locals::socket,
+ ipv6_locals::HEADER_REPR,
+ ipv6_locals::PACKET_BYTES,
+ ipv6_locals::PACKET_PAYLOAD
+ );
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_send_illegal(#[case] medium: Medium) {
+ #[cfg(feature = "proto-ipv4")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv4_locals::socket(buffer(0), buffer(2));
+
+ let mut wrong_version = ipv4_locals::PACKET_BYTES;
+ Ipv4Packet::new_unchecked(&mut wrong_version).set_version(6);
+
+ assert_eq!(socket.send_slice(&wrong_version[..]), Ok(()));
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+
+ let mut wrong_protocol = ipv4_locals::PACKET_BYTES;
+ Ipv4Packet::new_unchecked(&mut wrong_protocol).set_next_header(IpProtocol::Tcp);
+
+ assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(()));
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+ }
+ #[cfg(feature = "proto-ipv6")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv6_locals::socket(buffer(0), buffer(2));
+
+ let mut wrong_version = ipv6_locals::PACKET_BYTES;
+ Ipv6Packet::new_unchecked(&mut wrong_version[..]).set_version(4);
+
+ assert_eq!(socket.send_slice(&wrong_version[..]), Ok(()));
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+
+ let mut wrong_protocol = ipv6_locals::PACKET_BYTES;
+ Ipv6Packet::new_unchecked(&mut wrong_protocol[..]).set_next_header(IpProtocol::Tcp);
+
+ assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(()));
+ assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_recv_process(#[case] medium: Medium) {
+ #[cfg(feature = "proto-ipv4")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv4_locals::socket(buffer(1), buffer(0));
+ assert!(!socket.can_recv());
+
+ let mut cksumd_packet = ipv4_locals::PACKET_BYTES;
+ Ipv4Packet::new_unchecked(&mut cksumd_packet).fill_checksum();
+
+ assert_eq!(socket.recv(), Err(RecvError::Exhausted));
+ assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
+ socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
+ socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
+ assert_eq!(socket.recv(), Ok(&cksumd_packet[..]));
+ assert!(!socket.can_recv());
+ }
+ #[cfg(feature = "proto-ipv6")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv6_locals::socket(buffer(1), buffer(0));
+ assert!(!socket.can_recv());
+
+ assert_eq!(socket.recv(), Err(RecvError::Exhausted));
+ assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
+ socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
+ socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
+ assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..]));
+ assert!(!socket.can_recv());
+ }
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_peek_process(#[case] medium: Medium) {
+ #[cfg(feature = "proto-ipv4")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv4_locals::socket(buffer(1), buffer(0));
+
+ let mut cksumd_packet = ipv4_locals::PACKET_BYTES;
+ Ipv4Packet::new_unchecked(&mut cksumd_packet).fill_checksum();
+
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+ assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
+ socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
+
+ assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
+ socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
+ assert_eq!(socket.peek(), Ok(&cksumd_packet[..]));
+ assert_eq!(socket.recv(), Ok(&cksumd_packet[..]));
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+ }
+ #[cfg(feature = "proto-ipv6")]
+ {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = ipv6_locals::socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+ assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
+ socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
+
+ assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
+ socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
+ assert_eq!(socket.peek(), Ok(&ipv6_locals::PACKET_BYTES[..]));
+ assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..]));
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+ }
+ }
+
+ #[test]
+ fn test_doesnt_accept_wrong_proto() {
+ #[cfg(feature = "proto-ipv4")]
+ {
+ let socket = Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Unknown(ipv4_locals::IP_PROTO + 1),
+ buffer(1),
+ buffer(1),
+ );
+ assert!(!socket.accepts(&ipv4_locals::HEADER_REPR));
+ #[cfg(feature = "proto-ipv6")]
+ assert!(!socket.accepts(&ipv6_locals::HEADER_REPR));
+ }
+ #[cfg(feature = "proto-ipv6")]
+ {
+ let socket = Socket::new(
+ IpVersion::Ipv6,
+ IpProtocol::Unknown(ipv6_locals::IP_PROTO + 1),
+ buffer(1),
+ buffer(1),
+ );
+ assert!(!socket.accepts(&ipv6_locals::HEADER_REPR));
+ #[cfg(feature = "proto-ipv4")]
+ assert!(!socket.accepts(&ipv4_locals::HEADER_REPR));
+ }
+ }
+}
diff --git a/src/socket/tcp.rs b/src/socket/tcp.rs
new file mode 100644
index 0000000..d7b85ab
--- /dev/null
+++ b/src/socket/tcp.rs
@@ -0,0 +1,7312 @@
+// Heads up! Before working on this file you should read, at least, RFC 793 and
+// the parts of RFC 1122 that discuss TCP. Consult RFC 7414 when implementing
+// a new feature.
+
+use core::fmt::Display;
+#[cfg(feature = "async")]
+use core::task::Waker;
+use core::{cmp, fmt, mem};
+
+#[cfg(feature = "async")]
+use crate::socket::WakerRegistration;
+use crate::socket::{Context, PollAt};
+use crate::storage::{Assembler, RingBuffer};
+use crate::time::{Duration, Instant};
+use crate::wire::{
+ IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, TcpControl, TcpRepr, TcpSeqNumber,
+ TCP_HEADER_LEN,
+};
+
+macro_rules! tcp_trace {
+ ($($arg:expr),*) => (net_log!(trace, $($arg),*));
+}
+
+/// Error returned by [`Socket::listen`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum ListenError {
+ InvalidState,
+ Unaddressable,
+}
+
+impl Display for ListenError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ListenError::InvalidState => write!(f, "invalid state"),
+ ListenError::Unaddressable => write!(f, "unaddressable destination"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ListenError {}
+
+/// Error returned by [`Socket::connect`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum ConnectError {
+ InvalidState,
+ Unaddressable,
+}
+
+impl Display for ConnectError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ConnectError::InvalidState => write!(f, "invalid state"),
+ ConnectError::Unaddressable => write!(f, "unaddressable destination"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ConnectError {}
+
+/// Error returned by [`Socket::send`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SendError {
+ InvalidState,
+}
+
+impl Display for SendError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ SendError::InvalidState => write!(f, "invalid state"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for SendError {}
+
+/// Error returned by [`Socket::recv`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecvError {
+ InvalidState,
+ Finished,
+}
+
+impl Display for RecvError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ RecvError::InvalidState => write!(f, "invalid state"),
+ RecvError::Finished => write!(f, "operation finished"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RecvError {}
+
+/// A TCP socket ring buffer.
+pub type SocketBuffer<'a> = RingBuffer<'a, u8>;
+
+/// The state of a TCP socket, according to [RFC 793].
+///
+/// [RFC 793]: https://tools.ietf.org/html/rfc793
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum State {
+ Closed,
+ Listen,
+ SynSent,
+ SynReceived,
+ Established,
+ FinWait1,
+ FinWait2,
+ CloseWait,
+ Closing,
+ LastAck,
+ TimeWait,
+}
+
+impl fmt::Display for State {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ State::Closed => write!(f, "CLOSED"),
+ State::Listen => write!(f, "LISTEN"),
+ State::SynSent => write!(f, "SYN-SENT"),
+ State::SynReceived => write!(f, "SYN-RECEIVED"),
+ State::Established => write!(f, "ESTABLISHED"),
+ State::FinWait1 => write!(f, "FIN-WAIT-1"),
+ State::FinWait2 => write!(f, "FIN-WAIT-2"),
+ State::CloseWait => write!(f, "CLOSE-WAIT"),
+ State::Closing => write!(f, "CLOSING"),
+ State::LastAck => write!(f, "LAST-ACK"),
+ State::TimeWait => write!(f, "TIME-WAIT"),
+ }
+ }
+}
+
+// Conservative initial RTT estimate.
+const RTTE_INITIAL_RTT: u32 = 300;
+const RTTE_INITIAL_DEV: u32 = 100;
+
+// Minimum "safety margin" for the RTO that kicks in when the
+// variance gets very low.
+const RTTE_MIN_MARGIN: u32 = 5;
+
+const RTTE_MIN_RTO: u32 = 10;
+const RTTE_MAX_RTO: u32 = 10000;
+
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct RttEstimator {
+ // Using u32 instead of Duration to save space (Duration is i64)
+ rtt: u32,
+ deviation: u32,
+ timestamp: Option<(Instant, TcpSeqNumber)>,
+ max_seq_sent: Option<TcpSeqNumber>,
+ rto_count: u8,
+}
+
+impl Default for RttEstimator {
+ fn default() -> Self {
+ Self {
+ rtt: RTTE_INITIAL_RTT,
+ deviation: RTTE_INITIAL_DEV,
+ timestamp: None,
+ max_seq_sent: None,
+ rto_count: 0,
+ }
+ }
+}
+
+impl RttEstimator {
+ fn retransmission_timeout(&self) -> Duration {
+ let margin = RTTE_MIN_MARGIN.max(self.deviation * 4);
+ let ms = (self.rtt + margin).clamp(RTTE_MIN_RTO, RTTE_MAX_RTO);
+ Duration::from_millis(ms as u64)
+ }
+
+ fn sample(&mut self, new_rtt: u32) {
+ // "Congestion Avoidance and Control", Van Jacobson, Michael J. Karels, 1988
+ self.rtt = (self.rtt * 7 + new_rtt + 7) / 8;
+ let diff = (self.rtt as i32 - new_rtt as i32).unsigned_abs();
+ self.deviation = (self.deviation * 3 + diff + 3) / 4;
+
+ self.rto_count = 0;
+
+ let rto = self.retransmission_timeout().total_millis();
+ tcp_trace!(
+ "rtte: sample={:?} rtt={:?} dev={:?} rto={:?}",
+ new_rtt,
+ self.rtt,
+ self.deviation,
+ rto
+ );
+ }
+
+ fn on_send(&mut self, timestamp: Instant, seq: TcpSeqNumber) {
+ if self
+ .max_seq_sent
+ .map(|max_seq_sent| seq > max_seq_sent)
+ .unwrap_or(true)
+ {
+ self.max_seq_sent = Some(seq);
+ if self.timestamp.is_none() {
+ self.timestamp = Some((timestamp, seq));
+ tcp_trace!("rtte: sampling at seq={:?}", seq);
+ }
+ }
+ }
+
+ fn on_ack(&mut self, timestamp: Instant, seq: TcpSeqNumber) {
+ if let Some((sent_timestamp, sent_seq)) = self.timestamp {
+ if seq >= sent_seq {
+ self.sample((timestamp - sent_timestamp).total_millis() as u32);
+ self.timestamp = None;
+ }
+ }
+ }
+
+ fn on_retransmit(&mut self) {
+ if self.timestamp.is_some() {
+ tcp_trace!("rtte: abort sampling due to retransmit");
+ }
+ self.timestamp = None;
+ self.rto_count = self.rto_count.saturating_add(1);
+ if self.rto_count >= 3 {
+ // This happens in 2 scenarios:
+ // - The RTT is higher than the initial estimate
+ // - The network conditions change, suddenly making the RTT much higher
+ // In these cases, the estimator can get stuck, because it can't sample because
+ // all packets sent would incur a retransmit. To avoid this, force an estimate
+ // increase if we see 3 consecutive retransmissions without any successful sample.
+ self.rto_count = 0;
+ self.rtt = RTTE_MAX_RTO.min(self.rtt * 2);
+ let rto = self.retransmission_timeout().total_millis();
+ tcp_trace!(
+ "rtte: too many retransmissions, increasing: rtt={:?} dev={:?} rto={:?}",
+ self.rtt,
+ self.deviation,
+ rto
+ );
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+enum Timer {
+ Idle {
+ keep_alive_at: Option<Instant>,
+ },
+ Retransmit {
+ expires_at: Instant,
+ delay: Duration,
+ },
+ FastRetransmit,
+ Close {
+ expires_at: Instant,
+ },
+}
+
+const ACK_DELAY_DEFAULT: Duration = Duration::from_millis(10);
+const CLOSE_DELAY: Duration = Duration::from_millis(10_000);
+
+impl Timer {
+ fn new() -> Timer {
+ Timer::Idle {
+ keep_alive_at: None,
+ }
+ }
+
+ fn should_keep_alive(&self, timestamp: Instant) -> bool {
+ match *self {
+ Timer::Idle {
+ keep_alive_at: Some(keep_alive_at),
+ } if timestamp >= keep_alive_at => true,
+ _ => false,
+ }
+ }
+
+ fn should_retransmit(&self, timestamp: Instant) -> Option<Duration> {
+ match *self {
+ Timer::Retransmit { expires_at, delay } if timestamp >= expires_at => {
+ Some(timestamp - expires_at + delay)
+ }
+ Timer::FastRetransmit => Some(Duration::from_millis(0)),
+ _ => None,
+ }
+ }
+
+ fn should_close(&self, timestamp: Instant) -> bool {
+ match *self {
+ Timer::Close { expires_at } if timestamp >= expires_at => true,
+ _ => false,
+ }
+ }
+
+ fn poll_at(&self) -> PollAt {
+ match *self {
+ Timer::Idle {
+ keep_alive_at: Some(keep_alive_at),
+ } => PollAt::Time(keep_alive_at),
+ Timer::Idle {
+ keep_alive_at: None,
+ } => PollAt::Ingress,
+ Timer::Retransmit { expires_at, .. } => PollAt::Time(expires_at),
+ Timer::FastRetransmit => PollAt::Now,
+ Timer::Close { expires_at } => PollAt::Time(expires_at),
+ }
+ }
+
+ fn set_for_idle(&mut self, timestamp: Instant, interval: Option<Duration>) {
+ *self = Timer::Idle {
+ keep_alive_at: interval.map(|interval| timestamp + interval),
+ }
+ }
+
+ fn set_keep_alive(&mut self) {
+ if let Timer::Idle { keep_alive_at } = self {
+ if keep_alive_at.is_none() {
+ *keep_alive_at = Some(Instant::from_millis(0))
+ }
+ }
+ }
+
+ fn rewind_keep_alive(&mut self, timestamp: Instant, interval: Option<Duration>) {
+ if let Timer::Idle { keep_alive_at } = self {
+ *keep_alive_at = interval.map(|interval| timestamp + interval)
+ }
+ }
+
+ fn set_for_retransmit(&mut self, timestamp: Instant, delay: Duration) {
+ match *self {
+ Timer::Idle { .. } | Timer::FastRetransmit { .. } => {
+ *self = Timer::Retransmit {
+ expires_at: timestamp + delay,
+ delay,
+ }
+ }
+ Timer::Retransmit { expires_at, delay } if timestamp >= expires_at => {
+ *self = Timer::Retransmit {
+ expires_at: timestamp + delay,
+ delay: delay * 2,
+ }
+ }
+ Timer::Retransmit { .. } => (),
+ Timer::Close { .. } => (),
+ }
+ }
+
+ fn set_for_fast_retransmit(&mut self) {
+ *self = Timer::FastRetransmit
+ }
+
+ fn set_for_close(&mut self, timestamp: Instant) {
+ *self = Timer::Close {
+ expires_at: timestamp + CLOSE_DELAY,
+ }
+ }
+
+ fn is_retransmit(&self) -> bool {
+ match *self {
+ Timer::Retransmit { .. } | Timer::FastRetransmit => true,
+ _ => false,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum AckDelayTimer {
+ Idle,
+ Waiting(Instant),
+ Immediate,
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct Tuple {
+ local: IpEndpoint,
+ remote: IpEndpoint,
+}
+
+impl Display for Tuple {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}", self.local, self.remote)
+ }
+}
+
+/// A Transmission Control Protocol socket.
+///
+/// A TCP socket may passively listen for connections or actively connect to another endpoint.
+/// Note that, for listening sockets, there is no "backlog"; to be able to simultaneously
+/// accept several connections, as many sockets must be allocated, or any new connection
+/// attempts will be reset.
+#[derive(Debug)]
+pub struct Socket<'a> {
+ state: State,
+ timer: Timer,
+ rtte: RttEstimator,
+ assembler: Assembler,
+ rx_buffer: SocketBuffer<'a>,
+ rx_fin_received: bool,
+ tx_buffer: SocketBuffer<'a>,
+ /// Interval after which, if no inbound packets are received, the connection is aborted.
+ timeout: Option<Duration>,
+ /// Interval at which keep-alive packets will be sent.
+ keep_alive: Option<Duration>,
+ /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ hop_limit: Option<u8>,
+ /// Address passed to listen(). Listen address is set when listen() is called and
+ /// used every time the socket is reset back to the LISTEN state.
+ listen_endpoint: IpListenEndpoint,
+ /// Current 4-tuple (local and remote endpoints).
+ tuple: Option<Tuple>,
+ /// The sequence number corresponding to the beginning of the transmit buffer.
+ /// I.e. an ACK(local_seq_no+n) packet removes n bytes from the transmit buffer.
+ local_seq_no: TcpSeqNumber,
+ /// The sequence number corresponding to the beginning of the receive buffer.
+ /// I.e. userspace reading n bytes adds n to remote_seq_no.
+ remote_seq_no: TcpSeqNumber,
+ /// The last sequence number sent.
+ /// I.e. in an idle socket, local_seq_no+tx_buffer.len().
+ remote_last_seq: TcpSeqNumber,
+ /// The last acknowledgement number sent.
+ /// I.e. in an idle socket, remote_seq_no+rx_buffer.len().
+ remote_last_ack: Option<TcpSeqNumber>,
+ /// The last window length sent.
+ remote_last_win: u16,
+ /// The sending window scaling factor advertised to remotes which support RFC 1323.
+ /// It is zero if the window <= 64KiB and/or the remote does not support it.
+ remote_win_shift: u8,
+ /// The remote window size, relative to local_seq_no
+ /// I.e. we're allowed to send octets until local_seq_no+remote_win_len
+ remote_win_len: usize,
+ /// The receive window scaling factor for remotes which support RFC 1323, None if unsupported.
+ remote_win_scale: Option<u8>,
+ /// Whether or not the remote supports selective ACK as described in RFC 2018.
+ remote_has_sack: bool,
+ /// The maximum number of data octets that the remote side may receive.
+ remote_mss: usize,
+ /// The timestamp of the last packet received.
+ remote_last_ts: Option<Instant>,
+ /// The sequence number of the last packet received, used for sACK
+ local_rx_last_seq: Option<TcpSeqNumber>,
+ /// The ACK number of the last packet received.
+ local_rx_last_ack: Option<TcpSeqNumber>,
+ /// The number of packets received directly after
+ /// each other which have the same ACK number.
+ local_rx_dup_acks: u8,
+
+ /// Duration for Delayed ACK. If None no ACKs will be delayed.
+ ack_delay: Option<Duration>,
+ /// Delayed ack timer. If set, packets containing exclusively
+ /// ACK or window updates (ie, no data) won't be sent until expiry.
+ ack_delay_timer: AckDelayTimer,
+
+ /// Used for rate-limiting: No more challenge ACKs will be sent until this instant.
+ challenge_ack_timer: Instant,
+
+ /// Nagle's Algorithm enabled.
+ nagle: bool,
+
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration,
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration,
+}
+
+const DEFAULT_MSS: usize = 536;
+
+impl<'a> Socket<'a> {
+ #[allow(unused_comparisons)] // small usize platforms always pass rx_capacity check
+ /// Create a socket using the given buffers.
+ pub fn new<T>(rx_buffer: T, tx_buffer: T) -> Socket<'a>
+ where
+ T: Into<SocketBuffer<'a>>,
+ {
+ let (rx_buffer, tx_buffer) = (rx_buffer.into(), tx_buffer.into());
+ let rx_capacity = rx_buffer.capacity();
+
+ // From RFC 1323:
+ // [...] the above constraints imply that 2 * the max window size must be less
+ // than 2**31 [...] Thus, the shift count must be limited to 14 (which allows
+ // windows of 2**30 = 1 Gbyte).
+ if rx_capacity > (1 << 30) {
+ panic!("receiving buffer too large, cannot exceed 1 GiB")
+ }
+ let rx_cap_log2 = mem::size_of::<usize>() * 8 - rx_capacity.leading_zeros() as usize;
+
+ Socket {
+ state: State::Closed,
+ timer: Timer::new(),
+ rtte: RttEstimator::default(),
+ assembler: Assembler::new(),
+ tx_buffer,
+ rx_buffer,
+ rx_fin_received: false,
+ timeout: None,
+ keep_alive: None,
+ hop_limit: None,
+ listen_endpoint: IpListenEndpoint::default(),
+ tuple: None,
+ local_seq_no: TcpSeqNumber::default(),
+ remote_seq_no: TcpSeqNumber::default(),
+ remote_last_seq: TcpSeqNumber::default(),
+ remote_last_ack: None,
+ remote_last_win: 0,
+ remote_win_len: 0,
+ remote_win_shift: rx_cap_log2.saturating_sub(16) as u8,
+ remote_win_scale: None,
+ remote_has_sack: false,
+ remote_mss: DEFAULT_MSS,
+ remote_last_ts: None,
+ local_rx_last_ack: None,
+ local_rx_last_seq: None,
+ local_rx_dup_acks: 0,
+ ack_delay: Some(ACK_DELAY_DEFAULT),
+ ack_delay_timer: AckDelayTimer::Idle,
+ challenge_ack_timer: Instant::from_secs(0),
+ nagle: true,
+
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration::new(),
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration::new(),
+ }
+ }
+
+ /// Register a waker for receive operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `recv` method calls, such as receiving data, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_recv_waker(&mut self, waker: &Waker) {
+ self.rx_waker.register(waker)
+ }
+
+ /// Register a waker for send operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `send` method calls, such as space becoming available in the transmit
+ /// buffer, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_send_waker(&mut self, waker: &Waker) {
+ self.tx_waker.register(waker)
+ }
+
+ /// Return the timeout duration.
+ ///
+ /// See also the [set_timeout](#method.set_timeout) method.
+ pub fn timeout(&self) -> Option<Duration> {
+ self.timeout
+ }
+
+ /// Return the ACK delay duration.
+ ///
+ /// See also the [set_ack_delay](#method.set_ack_delay) method.
+ pub fn ack_delay(&self) -> Option<Duration> {
+ self.ack_delay
+ }
+
+ /// Return whether Nagle's Algorithm is enabled.
+ ///
+ /// See also the [set_nagle_enabled](#method.set_nagle_enabled) method.
+ pub fn nagle_enabled(&self) -> bool {
+ self.nagle
+ }
+
+ /// Return the current window field value, including scaling according to RFC 1323.
+ ///
+ /// Used in internal calculations as well as packet generation.
+ ///
+ #[inline]
+ fn scaled_window(&self) -> u16 {
+ cmp::min(
+ self.rx_buffer.window() >> self.remote_win_shift as usize,
+ (1 << 16) - 1,
+ ) as u16
+ }
+
+ /// Set the timeout duration.
+ ///
+ /// A socket with a timeout duration set will abort the connection if either of the following
+ /// occurs:
+ ///
+ /// * After a [connect](#method.connect) call, the remote endpoint does not respond within
+ /// the specified duration;
+ /// * After establishing a connection, there is data in the transmit buffer and the remote
+ /// endpoint exceeds the specified duration between any two packets it sends;
+ /// * After enabling [keep-alive](#method.set_keep_alive), the remote endpoint exceeds
+ /// the specified duration between any two packets it sends.
+ pub fn set_timeout(&mut self, duration: Option<Duration>) {
+ self.timeout = duration
+ }
+
+ /// Set the ACK delay duration.
+ ///
+ /// By default, the ACK delay is set to 10ms.
+ pub fn set_ack_delay(&mut self, duration: Option<Duration>) {
+ self.ack_delay = duration
+ }
+
+ /// Enable or disable Nagle's Algorithm.
+ ///
+ /// Also known as "tinygram prevention". By default, it is enabled.
+ /// Disabling it is equivalent to Linux's TCP_NODELAY flag.
+ ///
+ /// When enabled, Nagle's Algorithm prevents sending segments smaller than MSS if
+ /// there is data in flight (sent but not acknowledged). In other words, it ensures
+ /// at most only one segment smaller than MSS is in flight at a time.
+ ///
+ /// It ensures better network utilization by preventing sending many very small packets,
+ /// at the cost of increased latency in some situations, particularly when the remote peer
+ /// has ACK delay enabled.
+ pub fn set_nagle_enabled(&mut self, enabled: bool) {
+ self.nagle = enabled
+ }
+
+ /// Return the keep-alive interval.
+ ///
+ /// See also the [set_keep_alive](#method.set_keep_alive) method.
+ pub fn keep_alive(&self) -> Option<Duration> {
+ self.keep_alive
+ }
+
+ /// Set the keep-alive interval.
+ ///
+ /// An idle socket with a keep-alive interval set will transmit a "keep-alive ACK" packet
+ /// every time it receives no communication during that interval. As a result, three things
+ /// may happen:
+ ///
+ /// * The remote endpoint is fine and answers with an ACK packet.
+ /// * The remote endpoint has rebooted and answers with an RST packet.
+ /// * The remote endpoint has crashed and does not answer.
+ ///
+ /// The keep-alive functionality together with the timeout functionality allows to react
+ /// to these error conditions.
+ pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
+ self.keep_alive = interval;
+ if self.keep_alive.is_some() {
+ // If the connection is idle and we've just set the option, it would not take effect
+ // until the next packet, unless we wind up the timer explicitly.
+ self.timer.set_keep_alive();
+ }
+ }
+
+ /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// See also the [set_hop_limit](#method.set_hop_limit) method
+ pub fn hop_limit(&self) -> Option<u8> {
+ self.hop_limit
+ }
+
+ /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
+ /// value (64).
+ ///
+ /// # Panics
+ ///
+ /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
+ ///
+ /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
+ /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
+ pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
+ // A host MUST NOT send a datagram with a hop limit value of 0
+ if let Some(0) = hop_limit {
+ panic!("the time-to-live value of a packet must not be zero")
+ }
+
+ self.hop_limit = hop_limit
+ }
+
+ /// Return the local endpoint, or None if not connected.
+ #[inline]
+ pub fn local_endpoint(&self) -> Option<IpEndpoint> {
+ Some(self.tuple?.local)
+ }
+
+ /// Return the remote endpoint, or None if not connected.
+ #[inline]
+ pub fn remote_endpoint(&self) -> Option<IpEndpoint> {
+ Some(self.tuple?.remote)
+ }
+
+ /// Return the connection state, in terms of the TCP state machine.
+ #[inline]
+ pub fn state(&self) -> State {
+ self.state
+ }
+
+ fn reset(&mut self) {
+ let rx_cap_log2 =
+ mem::size_of::<usize>() * 8 - self.rx_buffer.capacity().leading_zeros() as usize;
+
+ self.state = State::Closed;
+ self.timer = Timer::new();
+ self.rtte = RttEstimator::default();
+ self.assembler = Assembler::new();
+ self.tx_buffer.clear();
+ self.rx_buffer.clear();
+ self.rx_fin_received = false;
+ self.listen_endpoint = IpListenEndpoint::default();
+ self.tuple = None;
+ self.local_seq_no = TcpSeqNumber::default();
+ self.remote_seq_no = TcpSeqNumber::default();
+ self.remote_last_seq = TcpSeqNumber::default();
+ self.remote_last_ack = None;
+ self.remote_last_win = 0;
+ self.remote_win_len = 0;
+ self.remote_win_scale = None;
+ self.remote_win_shift = rx_cap_log2.saturating_sub(16) as u8;
+ self.remote_mss = DEFAULT_MSS;
+ self.remote_last_ts = None;
+ self.ack_delay_timer = AckDelayTimer::Idle;
+ self.challenge_ack_timer = Instant::from_secs(0);
+
+ #[cfg(feature = "async")]
+ {
+ self.rx_waker.wake();
+ self.tx_waker.wake();
+ }
+ }
+
+ /// Start listening on the given endpoint.
+ ///
+ /// This function returns `Err(Error::InvalidState)` if the socket was already open
+ /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)`
+ /// if the port in the given endpoint is zero.
+ pub fn listen<T>(&mut self, local_endpoint: T) -> Result<(), ListenError>
+ where
+ T: Into<IpListenEndpoint>,
+ {
+ let local_endpoint = local_endpoint.into();
+ if local_endpoint.port == 0 {
+ return Err(ListenError::Unaddressable);
+ }
+
+ if self.is_open() {
+ // If we were already listening to same endpoint there is nothing to do; exit early.
+ //
+ // In the past listening on an socket that was already listening was an error,
+ // however this makes writing an acceptor loop with multiple sockets impossible.
+ // Without this early exit, if you tried to listen on a socket that's already listening you'll
+ // immediately get an error. The only way around this is to abort the socket first
+ // before listening again, but this means that incoming connections can actually
+ // get aborted between the abort() and the next listen().
+ if matches!(self.state, State::Listen) && self.listen_endpoint == local_endpoint {
+ return Ok(());
+ } else {
+ return Err(ListenError::InvalidState);
+ }
+ }
+
+ self.reset();
+ self.listen_endpoint = local_endpoint;
+ self.tuple = None;
+ self.set_state(State::Listen);
+ Ok(())
+ }
+
+ /// Connect to a given endpoint.
+ ///
+ /// The local port must be provided explicitly. Assuming `fn get_ephemeral_port() -> u16`
+ /// allocates a port between 49152 and 65535, a connection may be established as follows:
+ ///
+ /// ```no_run
+ /// # #[cfg(all(
+ /// # feature = "medium-ethernet",
+ /// # feature = "proto-ipv4",
+ /// # ))]
+ /// # {
+ /// # use smoltcp::socket::tcp::{Socket, SocketBuffer};
+ /// # use smoltcp::iface::Interface;
+ /// # use smoltcp::wire::IpAddress;
+ /// #
+ /// # fn get_ephemeral_port() -> u16 {
+ /// # 49152
+ /// # }
+ /// #
+ /// # let mut socket = Socket::new(
+ /// # SocketBuffer::new(vec![0; 1200]),
+ /// # SocketBuffer::new(vec![0; 1200])
+ /// # );
+ /// #
+ /// # let mut iface: Interface = todo!();
+ /// #
+ /// socket.connect(
+ /// iface.context(),
+ /// (IpAddress::v4(10, 0, 0, 1), 80),
+ /// get_ephemeral_port()
+ /// ).unwrap();
+ /// # }
+ /// ```
+ ///
+ /// The local address may optionally be provided.
+ ///
+ /// This function returns an error if the socket was open; see [is_open](#method.is_open).
+ /// It also returns an error if the local or remote port is zero, or if the remote address
+ /// is unspecified.
+ pub fn connect<T, U>(
+ &mut self,
+ cx: &mut Context,
+ remote_endpoint: T,
+ local_endpoint: U,
+ ) -> Result<(), ConnectError>
+ where
+ T: Into<IpEndpoint>,
+ U: Into<IpListenEndpoint>,
+ {
+ let remote_endpoint: IpEndpoint = remote_endpoint.into();
+ let local_endpoint: IpListenEndpoint = local_endpoint.into();
+
+ if self.is_open() {
+ return Err(ConnectError::InvalidState);
+ }
+ if remote_endpoint.port == 0 || remote_endpoint.addr.is_unspecified() {
+ return Err(ConnectError::Unaddressable);
+ }
+ if local_endpoint.port == 0 {
+ return Err(ConnectError::Unaddressable);
+ }
+
+ // If local address is not provided, choose it automatically.
+ let local_endpoint = IpEndpoint {
+ addr: match local_endpoint.addr {
+ Some(addr) => {
+ if addr.is_unspecified() {
+ return Err(ConnectError::Unaddressable);
+ }
+ addr
+ }
+ None => cx
+ .get_source_address(&remote_endpoint.addr)
+ .ok_or(ConnectError::Unaddressable)?,
+ },
+ port: local_endpoint.port,
+ };
+
+ if local_endpoint.addr.version() != remote_endpoint.addr.version() {
+ return Err(ConnectError::Unaddressable);
+ }
+
+ self.reset();
+ self.tuple = Some(Tuple {
+ local: local_endpoint,
+ remote: remote_endpoint,
+ });
+ self.set_state(State::SynSent);
+
+ let seq = Self::random_seq_no(cx);
+ self.local_seq_no = seq;
+ self.remote_last_seq = seq;
+ Ok(())
+ }
+
+ #[cfg(test)]
+ fn random_seq_no(_cx: &mut Context) -> TcpSeqNumber {
+ TcpSeqNumber(10000)
+ }
+
+ #[cfg(not(test))]
+ fn random_seq_no(cx: &mut Context) -> TcpSeqNumber {
+ TcpSeqNumber(cx.rand().rand_u32() as i32)
+ }
+
+ /// Close the transmit half of the full-duplex connection.
+ ///
+ /// Note that there is no corresponding function for the receive half of the full-duplex
+ /// connection; only the remote end can close it. If you no longer wish to receive any
+ /// data and would like to reuse the socket right away, use [abort](#method.abort).
+ pub fn close(&mut self) {
+ match self.state {
+ // In the LISTEN state there is no established connection.
+ State::Listen => self.set_state(State::Closed),
+ // In the SYN-SENT state the remote endpoint is not yet synchronized and, upon
+ // receiving an RST, will abort the connection.
+ State::SynSent => self.set_state(State::Closed),
+ // In the SYN-RECEIVED, ESTABLISHED and CLOSE-WAIT states the transmit half
+ // of the connection is open, and needs to be explicitly closed with a FIN.
+ State::SynReceived | State::Established => self.set_state(State::FinWait1),
+ State::CloseWait => self.set_state(State::LastAck),
+ // In the FIN-WAIT-1, FIN-WAIT-2, CLOSING, LAST-ACK, TIME-WAIT and CLOSED states,
+ // the transmit half of the connection is already closed, and no further
+ // action is needed.
+ State::FinWait1
+ | State::FinWait2
+ | State::Closing
+ | State::TimeWait
+ | State::LastAck
+ | State::Closed => (),
+ }
+ }
+
+ /// Aborts the connection, if any.
+ ///
+ /// This function instantly closes the socket. One reset packet will be sent to the remote
+ /// endpoint.
+ ///
+ /// In terms of the TCP state machine, the socket may be in any state and is moved to
+ /// the `CLOSED` state.
+ pub fn abort(&mut self) {
+ self.set_state(State::Closed);
+ }
+
+ /// Return whether the socket is passively listening for incoming connections.
+ ///
+ /// In terms of the TCP state machine, the socket must be in the `LISTEN` state.
+ #[inline]
+ pub fn is_listening(&self) -> bool {
+ match self.state {
+ State::Listen => true,
+ _ => false,
+ }
+ }
+
+ /// Return whether the socket is open.
+ ///
+ /// This function returns true if the socket will process incoming or dispatch outgoing
+ /// packets. Note that this does not mean that it is possible to send or receive data through
+ /// the socket; for that, use [can_send](#method.can_send) or [can_recv](#method.can_recv).
+ ///
+ /// In terms of the TCP state machine, the socket must not be in the `CLOSED`
+ /// or `TIME-WAIT` states.
+ #[inline]
+ pub fn is_open(&self) -> bool {
+ match self.state {
+ State::Closed => false,
+ State::TimeWait => false,
+ _ => true,
+ }
+ }
+
+ /// Return whether a connection is active.
+ ///
+ /// This function returns true if the socket is actively exchanging packets with
+ /// a remote endpoint. Note that this does not mean that it is possible to send or receive
+ /// data through the socket; for that, use [can_send](#method.can_send) or
+ /// [can_recv](#method.can_recv).
+ ///
+ /// If a connection is established, [abort](#method.close) will send a reset to
+ /// the remote endpoint.
+ ///
+ /// In terms of the TCP state machine, the socket must not be in the `CLOSED`, `TIME-WAIT`,
+ /// or `LISTEN` state.
+ #[inline]
+ pub fn is_active(&self) -> bool {
+ match self.state {
+ State::Closed => false,
+ State::TimeWait => false,
+ State::Listen => false,
+ _ => true,
+ }
+ }
+
+ /// Return whether the transmit half of the full-duplex connection is open.
+ ///
+ /// This function returns true if it's possible to send data and have it arrive
+ /// to the remote endpoint. However, it does not make any guarantees about the state
+ /// of the transmit buffer, and even if it returns true, [send](#method.send) may
+ /// not be able to enqueue any octets.
+ ///
+ /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or
+ /// `CLOSE-WAIT` state.
+ #[inline]
+ pub fn may_send(&self) -> bool {
+ match self.state {
+ State::Established => true,
+ // In CLOSE-WAIT, the remote endpoint has closed our receive half of the connection
+ // but we still can transmit indefinitely.
+ State::CloseWait => true,
+ _ => false,
+ }
+ }
+
+ /// Return whether the receive half of the full-duplex connection is open.
+ ///
+ /// This function returns true if it's possible to receive data from the remote endpoint.
+ /// It will return true while there is data in the receive buffer, and if there isn't,
+ /// as long as the remote endpoint has not closed the connection.
+ ///
+ /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED`,
+ /// `FIN-WAIT-1`, or `FIN-WAIT-2` state, or have data in the receive buffer instead.
+ #[inline]
+ pub fn may_recv(&self) -> bool {
+ match self.state {
+ State::Established => true,
+ // In FIN-WAIT-1/2, we have closed our transmit half of the connection but
+ // we still can receive indefinitely.
+ State::FinWait1 | State::FinWait2 => true,
+ // If we have something in the receive buffer, we can receive that.
+ _ if !self.rx_buffer.is_empty() => true,
+ _ => false,
+ }
+ }
+
+ /// Check whether the transmit half of the full-duplex connection is open
+ /// (see [may_send](#method.may_send)), and the transmit buffer is not full.
+ #[inline]
+ pub fn can_send(&self) -> bool {
+ if !self.may_send() {
+ return false;
+ }
+
+ !self.tx_buffer.is_full()
+ }
+
+ /// Return the maximum number of bytes inside the recv buffer.
+ #[inline]
+ pub fn recv_capacity(&self) -> usize {
+ self.rx_buffer.capacity()
+ }
+
+ /// Return the maximum number of bytes inside the transmit buffer.
+ #[inline]
+ pub fn send_capacity(&self) -> usize {
+ self.tx_buffer.capacity()
+ }
+
+ /// Check whether the receive half of the full-duplex connection buffer is open
+ /// (see [may_recv](#method.may_recv)), and the receive buffer is not empty.
+ #[inline]
+ pub fn can_recv(&self) -> bool {
+ if !self.may_recv() {
+ return false;
+ }
+
+ !self.rx_buffer.is_empty()
+ }
+
+ fn send_impl<'b, F, R>(&'b mut self, f: F) -> Result<R, SendError>
+ where
+ F: FnOnce(&'b mut SocketBuffer<'a>) -> (usize, R),
+ {
+ if !self.may_send() {
+ return Err(SendError::InvalidState);
+ }
+
+ // The connection might have been idle for a long time, and so remote_last_ts
+ // would be far in the past. Unless we clear it here, we'll abort the connection
+ // down over in dispatch() by erroneously detecting it as timed out.
+ if self.tx_buffer.is_empty() {
+ self.remote_last_ts = None
+ }
+
+ let _old_length = self.tx_buffer.len();
+ let (size, result) = f(&mut self.tx_buffer);
+ if size > 0 {
+ #[cfg(any(test, feature = "verbose"))]
+ tcp_trace!(
+ "tx buffer: enqueueing {} octets (now {})",
+ size,
+ _old_length + size
+ );
+ }
+ Ok(result)
+ }
+
+ /// Call `f` with the largest contiguous slice of octets in the transmit buffer,
+ /// and enqueue the amount of elements returned by `f`.
+ ///
+ /// This function returns `Err(Error::Illegal)` if the transmit half of
+ /// the connection is not open; see [may_send](#method.may_send).
+ pub fn send<'b, F, R>(&'b mut self, f: F) -> Result<R, SendError>
+ where
+ F: FnOnce(&'b mut [u8]) -> (usize, R),
+ {
+ self.send_impl(|tx_buffer| tx_buffer.enqueue_many_with(f))
+ }
+
+ /// Enqueue a sequence of octets to be sent, and fill it from a slice.
+ ///
+ /// This function returns the amount of octets actually enqueued, which is limited
+ /// by the amount of free space in the transmit buffer; down to zero.
+ ///
+ /// See also [send](#method.send).
+ pub fn send_slice(&mut self, data: &[u8]) -> Result<usize, SendError> {
+ self.send_impl(|tx_buffer| {
+ let size = tx_buffer.enqueue_slice(data);
+ (size, size)
+ })
+ }
+
+ fn recv_error_check(&mut self) -> Result<(), RecvError> {
+ // We may have received some data inside the initial SYN, but until the connection
+ // is fully open we must not dequeue any data, as it may be overwritten by e.g.
+ // another (stale) SYN. (We do not support TCP Fast Open.)
+ if !self.may_recv() {
+ if self.rx_fin_received {
+ return Err(RecvError::Finished);
+ }
+ return Err(RecvError::InvalidState);
+ }
+
+ Ok(())
+ }
+
+ fn recv_impl<'b, F, R>(&'b mut self, f: F) -> Result<R, RecvError>
+ where
+ F: FnOnce(&'b mut SocketBuffer<'a>) -> (usize, R),
+ {
+ self.recv_error_check()?;
+
+ let _old_length = self.rx_buffer.len();
+ let (size, result) = f(&mut self.rx_buffer);
+ self.remote_seq_no += size;
+ if size > 0 {
+ #[cfg(any(test, feature = "verbose"))]
+ tcp_trace!(
+ "rx buffer: dequeueing {} octets (now {})",
+ size,
+ _old_length - size
+ );
+ }
+ Ok(result)
+ }
+
+ /// Call `f` with the largest contiguous slice of octets in the receive buffer,
+ /// and dequeue the amount of elements returned by `f`.
+ ///
+ /// This function errors if the receive half of the connection is not open.
+ ///
+ /// If the receive half has been gracefully closed (with a FIN packet), `Err(Error::Finished)`
+ /// is returned. In this case, the previously received data is guaranteed to be complete.
+ ///
+ /// In all other cases, `Err(Error::Illegal)` is returned and previously received data (if any)
+ /// may be incomplete (truncated).
+ pub fn recv<'b, F, R>(&'b mut self, f: F) -> Result<R, RecvError>
+ where
+ F: FnOnce(&'b mut [u8]) -> (usize, R),
+ {
+ self.recv_impl(|rx_buffer| rx_buffer.dequeue_many_with(f))
+ }
+
+ /// Dequeue a sequence of received octets, and fill a slice from it.
+ ///
+ /// This function returns the amount of octets actually dequeued, which is limited
+ /// by the amount of occupied space in the receive buffer; down to zero.
+ ///
+ /// See also [recv](#method.recv).
+ pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<usize, RecvError> {
+ self.recv_impl(|rx_buffer| {
+ let size = rx_buffer.dequeue_slice(data);
+ (size, size)
+ })
+ }
+
+ /// Peek at a sequence of received octets without removing them from
+ /// the receive buffer, and return a pointer to it.
+ ///
+ /// This function otherwise behaves identically to [recv](#method.recv).
+ pub fn peek(&mut self, size: usize) -> Result<&[u8], RecvError> {
+ self.recv_error_check()?;
+
+ let buffer = self.rx_buffer.get_allocated(0, size);
+ if !buffer.is_empty() {
+ #[cfg(any(test, feature = "verbose"))]
+ tcp_trace!("rx buffer: peeking at {} octets", buffer.len());
+ }
+ Ok(buffer)
+ }
+
+ /// Peek at a sequence of received octets without removing them from
+ /// the receive buffer, and fill a slice from it.
+ ///
+ /// This function otherwise behaves identically to [recv_slice](#method.recv_slice).
+ pub fn peek_slice(&mut self, data: &mut [u8]) -> Result<usize, RecvError> {
+ Ok(self.rx_buffer.read_allocated(0, data))
+ }
+
+ /// Return the amount of octets queued in the transmit buffer.
+ ///
+ /// Note that the Berkeley sockets interface does not have an equivalent of this API.
+ pub fn send_queue(&self) -> usize {
+ self.tx_buffer.len()
+ }
+
+ /// Return the amount of octets queued in the receive buffer. This value can be larger than
+ /// the slice read by the next `recv` or `peek` call because it includes all queued octets,
+ /// and not only the octets that may be returned as a contiguous slice.
+ ///
+ /// Note that the Berkeley sockets interface does not have an equivalent of this API.
+ pub fn recv_queue(&self) -> usize {
+ self.rx_buffer.len()
+ }
+
+ fn set_state(&mut self, state: State) {
+ if self.state != state {
+ tcp_trace!("state={}=>{}", self.state, state);
+ }
+
+ self.state = state;
+
+ #[cfg(feature = "async")]
+ {
+ // Wake all tasks waiting. Even if we haven't received/sent data, this
+ // is needed because return values of functions may change depending on the state.
+ // For example, a pending read has to fail with an error if the socket is closed.
+ self.rx_waker.wake();
+ self.tx_waker.wake();
+ }
+ }
+
+ pub(crate) fn reply(ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) {
+ let reply_repr = TcpRepr {
+ src_port: repr.dst_port,
+ dst_port: repr.src_port,
+ control: TcpControl::None,
+ seq_number: TcpSeqNumber(0),
+ ack_number: None,
+ window_len: 0,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+ let ip_reply_repr = IpRepr::new(
+ ip_repr.dst_addr(),
+ ip_repr.src_addr(),
+ IpProtocol::Tcp,
+ reply_repr.buffer_len(),
+ 64,
+ );
+ (ip_reply_repr, reply_repr)
+ }
+
+ pub(crate) fn rst_reply(ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) {
+ debug_assert!(repr.control != TcpControl::Rst);
+
+ let (ip_reply_repr, mut reply_repr) = Self::reply(ip_repr, repr);
+
+ // See https://www.snellman.net/blog/archive/2016-02-01-tcp-rst/ for explanation
+ // of why we sometimes send an RST and sometimes an RST|ACK
+ reply_repr.control = TcpControl::Rst;
+ reply_repr.seq_number = repr.ack_number.unwrap_or_default();
+ if repr.control == TcpControl::Syn && repr.ack_number.is_none() {
+ reply_repr.ack_number = Some(repr.seq_number + repr.segment_len());
+ }
+
+ (ip_reply_repr, reply_repr)
+ }
+
+ fn ack_reply(&mut self, ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) {
+ let (mut ip_reply_repr, mut reply_repr) = Self::reply(ip_repr, repr);
+
+ // From RFC 793:
+ // [...] an empty acknowledgment segment containing the current send-sequence number
+ // and an acknowledgment indicating the next sequence number expected
+ // to be received.
+ reply_repr.seq_number = self.remote_last_seq;
+ reply_repr.ack_number = Some(self.remote_seq_no + self.rx_buffer.len());
+ self.remote_last_ack = reply_repr.ack_number;
+
+ // From RFC 1323:
+ // The window field [...] of every outgoing segment, with the exception of SYN
+ // segments, is right-shifted by [advertised scale value] bits[...]
+ reply_repr.window_len = self.scaled_window();
+ self.remote_last_win = reply_repr.window_len;
+
+ // If the remote supports selective acknowledgement, add the option to the outgoing
+ // segment.
+ if self.remote_has_sack {
+ net_debug!("sending sACK option with current assembler ranges");
+
+ // RFC 2018: The first SACK block (i.e., the one immediately following the kind and
+ // length fields in the option) MUST specify the contiguous block of data containing
+ // the segment which triggered this ACK, unless that segment advanced the
+ // Acknowledgment Number field in the header.
+ reply_repr.sack_ranges[0] = None;
+
+ if let Some(last_seg_seq) = self.local_rx_last_seq.map(|s| s.0 as u32) {
+ reply_repr.sack_ranges[0] = self
+ .assembler
+ .iter_data(reply_repr.ack_number.map(|s| s.0 as usize).unwrap_or(0))
+ .map(|(left, right)| (left as u32, right as u32))
+ .find(|(left, right)| *left <= last_seg_seq && *right >= last_seg_seq);
+ }
+
+ if reply_repr.sack_ranges[0].is_none() {
+ // The matching segment was removed from the assembler, meaning the acknowledgement
+ // number has advanced, or there was no previous sACK.
+ //
+ // While the RFC says we SHOULD keep a list of reported sACK ranges, and iterate
+ // through those, that is currently infeasible. Instead, we offer the range with
+ // the lowest sequence number (if one exists) to hint at what segments would
+ // most quickly advance the acknowledgement number.
+ reply_repr.sack_ranges[0] = self
+ .assembler
+ .iter_data(reply_repr.ack_number.map(|s| s.0 as usize).unwrap_or(0))
+ .map(|(left, right)| (left as u32, right as u32))
+ .next();
+ }
+ }
+
+ // Since the sACK option may have changed the length of the payload, update that.
+ ip_reply_repr.set_payload_len(reply_repr.buffer_len());
+ (ip_reply_repr, reply_repr)
+ }
+
+ fn challenge_ack_reply(
+ &mut self,
+ cx: &mut Context,
+ ip_repr: &IpRepr,
+ repr: &TcpRepr,
+ ) -> Option<(IpRepr, TcpRepr<'static>)> {
+ if cx.now() < self.challenge_ack_timer {
+ return None;
+ }
+
+ // Rate-limit to 1 per second max.
+ self.challenge_ack_timer = cx.now() + Duration::from_secs(1);
+
+ Some(self.ack_reply(ip_repr, repr))
+ }
+
+ pub(crate) fn accepts(&self, _cx: &mut Context, ip_repr: &IpRepr, repr: &TcpRepr) -> bool {
+ if self.state == State::Closed {
+ return false;
+ }
+
+ // If we're still listening for SYNs and the packet has an ACK, it cannot
+ // be destined to this socket, but another one may well listen on the same
+ // local endpoint.
+ if self.state == State::Listen && repr.ack_number.is_some() {
+ return false;
+ }
+
+ if let Some(tuple) = &self.tuple {
+ // Reject packets not matching the 4-tuple
+ ip_repr.dst_addr() == tuple.local.addr
+ && repr.dst_port == tuple.local.port
+ && ip_repr.src_addr() == tuple.remote.addr
+ && repr.src_port == tuple.remote.port
+ } else {
+ // We're listening, reject packets not matching the listen endpoint.
+ let addr_ok = match self.listen_endpoint.addr {
+ Some(addr) => ip_repr.dst_addr() == addr,
+ None => true,
+ };
+ addr_ok && repr.dst_port != 0 && repr.dst_port == self.listen_endpoint.port
+ }
+ }
+
+ pub(crate) fn process(
+ &mut self,
+ cx: &mut Context,
+ ip_repr: &IpRepr,
+ repr: &TcpRepr,
+ ) -> Option<(IpRepr, TcpRepr<'static>)> {
+ debug_assert!(self.accepts(cx, ip_repr, repr));
+
+ // Consider how much the sequence number space differs from the transmit buffer space.
+ let (sent_syn, sent_fin) = match self.state {
+ // In SYN-SENT or SYN-RECEIVED, we've just sent a SYN.
+ State::SynSent | State::SynReceived => (true, false),
+ // In FIN-WAIT-1, LAST-ACK, or CLOSING, we've just sent a FIN.
+ State::FinWait1 | State::LastAck | State::Closing => (false, true),
+ // In all other states we've already got acknowledgements for
+ // all of the control flags we sent.
+ _ => (false, false),
+ };
+ let control_len = (sent_syn as usize) + (sent_fin as usize);
+
+ // Reject unacceptable acknowledgements.
+ match (self.state, repr.control, repr.ack_number) {
+ // An RST received in response to initial SYN is acceptable if it acknowledges
+ // the initial SYN.
+ (State::SynSent, TcpControl::Rst, None) => {
+ net_debug!("unacceptable RST (expecting RST|ACK) in response to initial SYN");
+ return None;
+ }
+ (State::SynSent, TcpControl::Rst, Some(ack_number)) => {
+ if ack_number != self.local_seq_no + 1 {
+ net_debug!("unacceptable RST|ACK in response to initial SYN");
+ return None;
+ }
+ }
+ // Any other RST need only have a valid sequence number.
+ (_, TcpControl::Rst, _) => (),
+ // The initial SYN cannot contain an acknowledgement.
+ (State::Listen, _, None) => (),
+ // This case is handled in `accepts()`.
+ (State::Listen, _, Some(_)) => unreachable!(),
+ // Every packet after the initial SYN must be an acknowledgement.
+ (_, _, None) => {
+ net_debug!("expecting an ACK");
+ return None;
+ }
+ // SYN|ACK in the SYN-SENT state must have the exact ACK number.
+ (State::SynSent, TcpControl::Syn, Some(ack_number)) => {
+ if ack_number != self.local_seq_no + 1 {
+ net_debug!("unacceptable SYN|ACK in response to initial SYN");
+ return Some(Self::rst_reply(ip_repr, repr));
+ }
+ }
+ // ACKs in the SYN-SENT state are invalid.
+ (State::SynSent, TcpControl::None, Some(ack_number)) => {
+ // If the sequence number matches, ignore it instead of RSTing.
+ // I'm not sure why, I think it may be a workaround for broken TCP
+ // servers, or a defense against reordering. Either way, if Linux
+ // does it, we do too.
+ if ack_number == self.local_seq_no + 1 {
+ net_debug!(
+ "expecting a SYN|ACK, received an ACK with the right ack_number, ignoring."
+ );
+ return None;
+ }
+
+ net_debug!(
+ "expecting a SYN|ACK, received an ACK with the wrong ack_number, sending RST."
+ );
+ return Some(Self::rst_reply(ip_repr, repr));
+ }
+ // Anything else in the SYN-SENT state is invalid.
+ (State::SynSent, _, _) => {
+ net_debug!("expecting a SYN|ACK");
+ return None;
+ }
+ // ACK in the SYN-RECEIVED state must have the exact ACK number, or we RST it.
+ (State::SynReceived, _, Some(ack_number)) => {
+ if ack_number != self.local_seq_no + 1 {
+ net_debug!("unacceptable ACK in response to SYN|ACK");
+ return Some(Self::rst_reply(ip_repr, repr));
+ }
+ }
+ // Every acknowledgement must be for transmitted but unacknowledged data.
+ (_, _, Some(ack_number)) => {
+ let unacknowledged = self.tx_buffer.len() + control_len;
+
+ // Acceptable ACK range (both inclusive)
+ let mut ack_min = self.local_seq_no;
+ let ack_max = self.local_seq_no + unacknowledged;
+
+ // If we have sent a SYN, it MUST be acknowledged.
+ if sent_syn {
+ ack_min += 1;
+ }
+
+ if ack_number < ack_min {
+ net_debug!(
+ "duplicate ACK ({} not in {}...{})",
+ ack_number,
+ ack_min,
+ ack_max
+ );
+ return None;
+ }
+
+ if ack_number > ack_max {
+ net_debug!(
+ "unacceptable ACK ({} not in {}...{})",
+ ack_number,
+ ack_min,
+ ack_max
+ );
+ return self.challenge_ack_reply(cx, ip_repr, repr);
+ }
+ }
+ }
+
+ let window_start = self.remote_seq_no + self.rx_buffer.len();
+ let window_end = self.remote_seq_no + self.rx_buffer.capacity();
+ let segment_start = repr.seq_number;
+ let segment_end = repr.seq_number + repr.payload.len();
+
+ let (payload, payload_offset) = match self.state {
+ // In LISTEN and SYN-SENT states, we have not yet synchronized with the remote end.
+ State::Listen | State::SynSent => (&[][..], 0),
+ _ => {
+ // https://www.rfc-editor.org/rfc/rfc9293.html#name-segment-acceptability-tests
+ let segment_in_window = match (
+ segment_start == segment_end,
+ window_start == window_end,
+ ) {
+ (true, _) if segment_end == window_start - 1 => {
+ net_debug!(
+ "received a keep-alive or window probe packet, will send an ACK"
+ );
+ false
+ }
+ (true, true) => {
+ if window_start == segment_start {
+ true
+ } else {
+ net_debug!(
+ "zero-length segment not inside zero-length window, will send an ACK."
+ );
+ false
+ }
+ }
+ (true, false) => {
+ if window_start <= segment_start && segment_start < window_end {
+ true
+ } else {
+ net_debug!("zero-length segment not inside window, will send an ACK.");
+ false
+ }
+ }
+ (false, true) => {
+ net_debug!(
+ "non-zero-length segment with zero receive window, will only send an ACK"
+ );
+ false
+ }
+ (false, false) => {
+ if (window_start <= segment_start && segment_start < window_end)
+ || (window_start < segment_end && segment_end <= window_end)
+ {
+ true
+ } else {
+ net_debug!(
+ "segment not in receive window ({}..{} not intersecting {}..{}), will send challenge ACK",
+ segment_start,
+ segment_end,
+ window_start,
+ window_end
+ );
+ false
+ }
+ }
+ };
+
+ if segment_in_window {
+ let overlap_start = window_start.max(segment_start);
+ let overlap_end = window_end.min(segment_end);
+
+ // the checks done above imply this.
+ debug_assert!(overlap_start <= overlap_end);
+
+ self.local_rx_last_seq = Some(repr.seq_number);
+
+ (
+ &repr.payload[overlap_start - segment_start..overlap_end - segment_start],
+ overlap_start - window_start,
+ )
+ } else {
+ // If we're in the TIME-WAIT state, restart the TIME-WAIT timeout, since
+ // the remote end may not have realized we've closed the connection.
+ if self.state == State::TimeWait {
+ self.timer.set_for_close(cx.now());
+ }
+
+ return self.challenge_ack_reply(cx, ip_repr, repr);
+ }
+ }
+ };
+
+ // Compute the amount of acknowledged octets, removing the SYN and FIN bits
+ // from the sequence space.
+ let mut ack_len = 0;
+ let mut ack_of_fin = false;
+ let mut ack_all = false;
+ if repr.control != TcpControl::Rst {
+ if let Some(ack_number) = repr.ack_number {
+ // Sequence number corresponding to the first byte in `tx_buffer`.
+ // This normally equals `local_seq_no`, but is 1 higher if we have sent a SYN,
+ // as the SYN occupies 1 sequence number "before" the data.
+ let tx_buffer_start_seq = self.local_seq_no + (sent_syn as usize);
+
+ if ack_number >= tx_buffer_start_seq {
+ ack_len = ack_number - tx_buffer_start_seq;
+
+ // We could've sent data before the FIN, so only remove FIN from the sequence
+ // space if all of that data is acknowledged.
+ if sent_fin && self.tx_buffer.len() + 1 == ack_len {
+ ack_len -= 1;
+ tcp_trace!("received ACK of FIN");
+ ack_of_fin = true;
+ }
+
+ ack_all = self.remote_last_seq == ack_number
+ }
+
+ self.rtte.on_ack(cx.now(), ack_number);
+ }
+ }
+
+ // Disregard control flags we don't care about or shouldn't act on yet.
+ let mut control = repr.control;
+ control = control.quash_psh();
+
+ // If a FIN is received at the end of the current segment but the start of the segment
+ // is not at the start of the receive window, disregard this FIN.
+ if control == TcpControl::Fin && window_start != segment_start {
+ tcp_trace!("ignoring FIN because we don't have full data yet. window_start={} segment_start={}", window_start, segment_start);
+ control = TcpControl::None;
+ }
+
+ // Validate and update the state.
+ match (self.state, control) {
+ // RSTs are not accepted in the LISTEN state.
+ (State::Listen, TcpControl::Rst) => return None,
+
+ // RSTs in SYN-RECEIVED flip the socket back to the LISTEN state.
+ (State::SynReceived, TcpControl::Rst) => {
+ tcp_trace!("received RST");
+ self.tuple = None;
+ self.set_state(State::Listen);
+ return None;
+ }
+
+ // RSTs in any other state close the socket.
+ (_, TcpControl::Rst) => {
+ tcp_trace!("received RST");
+ self.set_state(State::Closed);
+ self.tuple = None;
+ return None;
+ }
+
+ // SYN packets in the LISTEN state change it to SYN-RECEIVED.
+ (State::Listen, TcpControl::Syn) => {
+ tcp_trace!("received SYN");
+ if let Some(max_seg_size) = repr.max_seg_size {
+ if max_seg_size == 0 {
+ tcp_trace!("received SYNACK with zero MSS, ignoring");
+ return None;
+ }
+ self.remote_mss = max_seg_size as usize
+ }
+
+ self.tuple = Some(Tuple {
+ local: IpEndpoint::new(ip_repr.dst_addr(), repr.dst_port),
+ remote: IpEndpoint::new(ip_repr.src_addr(), repr.src_port),
+ });
+ self.local_seq_no = Self::random_seq_no(cx);
+ self.remote_seq_no = repr.seq_number + 1;
+ self.remote_last_seq = self.local_seq_no;
+ self.remote_has_sack = repr.sack_permitted;
+ self.remote_win_scale = repr.window_scale;
+ // Remote doesn't support window scaling, don't do it.
+ if self.remote_win_scale.is_none() {
+ self.remote_win_shift = 0;
+ }
+ self.set_state(State::SynReceived);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // ACK packets in the SYN-RECEIVED state change it to ESTABLISHED.
+ (State::SynReceived, TcpControl::None) => {
+ self.set_state(State::Established);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // FIN packets in the SYN-RECEIVED state change it to CLOSE-WAIT.
+ // It's not obvious from RFC 793 that this is permitted, but
+ // 7th and 8th steps in the "SEGMENT ARRIVES" event describe this behavior.
+ (State::SynReceived, TcpControl::Fin) => {
+ self.remote_seq_no += 1;
+ self.rx_fin_received = true;
+ self.set_state(State::CloseWait);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // SYN|ACK packets in the SYN-SENT state change it to ESTABLISHED.
+ (State::SynSent, TcpControl::Syn) => {
+ tcp_trace!("received SYN|ACK");
+ if let Some(max_seg_size) = repr.max_seg_size {
+ if max_seg_size == 0 {
+ tcp_trace!("received SYNACK with zero MSS, ignoring");
+ return None;
+ }
+ self.remote_mss = max_seg_size as usize;
+ }
+
+ self.remote_seq_no = repr.seq_number + 1;
+ self.remote_last_seq = self.local_seq_no + 1;
+ self.remote_last_ack = Some(repr.seq_number);
+ self.remote_win_scale = repr.window_scale;
+ // Remote doesn't support window scaling, don't do it.
+ if self.remote_win_scale.is_none() {
+ self.remote_win_shift = 0;
+ }
+
+ self.set_state(State::Established);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // ACK packets in ESTABLISHED state reset the retransmit timer,
+ // except for duplicate ACK packets which preserve it.
+ (State::Established, TcpControl::None) => {
+ if !self.timer.is_retransmit() || ack_all {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+ }
+
+ // FIN packets in ESTABLISHED state indicate the remote side has closed.
+ (State::Established, TcpControl::Fin) => {
+ self.remote_seq_no += 1;
+ self.rx_fin_received = true;
+ self.set_state(State::CloseWait);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // ACK packets in FIN-WAIT-1 state change it to FIN-WAIT-2, if we've already
+ // sent everything in the transmit buffer. If not, they reset the retransmit timer.
+ (State::FinWait1, TcpControl::None) => {
+ if ack_of_fin {
+ self.set_state(State::FinWait2);
+ }
+ if ack_all {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+ }
+
+ // FIN packets in FIN-WAIT-1 state change it to CLOSING, or to TIME-WAIT
+ // if they also acknowledge our FIN.
+ (State::FinWait1, TcpControl::Fin) => {
+ self.remote_seq_no += 1;
+ self.rx_fin_received = true;
+ if ack_of_fin {
+ self.set_state(State::TimeWait);
+ self.timer.set_for_close(cx.now());
+ } else {
+ self.set_state(State::Closing);
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+ }
+
+ // Data packets in FIN-WAIT-2 reset the idle timer.
+ (State::FinWait2, TcpControl::None) => {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // FIN packets in FIN-WAIT-2 state change it to TIME-WAIT.
+ (State::FinWait2, TcpControl::Fin) => {
+ self.remote_seq_no += 1;
+ self.rx_fin_received = true;
+ self.set_state(State::TimeWait);
+ self.timer.set_for_close(cx.now());
+ }
+
+ // ACK packets in CLOSING state change it to TIME-WAIT.
+ (State::Closing, TcpControl::None) => {
+ if ack_of_fin {
+ self.set_state(State::TimeWait);
+ self.timer.set_for_close(cx.now());
+ } else {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+ }
+
+ // ACK packets in CLOSE-WAIT state reset the retransmit timer.
+ (State::CloseWait, TcpControl::None) => {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+
+ // ACK packets in LAST-ACK state change it to CLOSED.
+ (State::LastAck, TcpControl::None) => {
+ if ack_of_fin {
+ // Clear the remote endpoint, or we'll send an RST there.
+ self.set_state(State::Closed);
+ self.tuple = None;
+ } else {
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+ }
+ }
+
+ _ => {
+ net_debug!("unexpected packet {}", repr);
+ return None;
+ }
+ }
+
+ // Update remote state.
+ self.remote_last_ts = Some(cx.now());
+
+ // RFC 1323: The window field (SEG.WND) in the header of every incoming segment, with the
+ // exception of SYN segments, is left-shifted by Snd.Wind.Scale bits before updating SND.WND.
+ let scale = match repr.control {
+ TcpControl::Syn => 0,
+ _ => self.remote_win_scale.unwrap_or(0),
+ };
+ let new_remote_win_len = (repr.window_len as usize) << (scale as usize);
+ let is_window_update = new_remote_win_len != self.remote_win_len;
+ self.remote_win_len = new_remote_win_len;
+
+ if ack_len > 0 {
+ // Dequeue acknowledged octets.
+ debug_assert!(self.tx_buffer.len() >= ack_len);
+ tcp_trace!(
+ "tx buffer: dequeueing {} octets (now {})",
+ ack_len,
+ self.tx_buffer.len() - ack_len
+ );
+ self.tx_buffer.dequeue_allocated(ack_len);
+
+ // There's new room available in tx_buffer, wake the waiting task if any.
+ #[cfg(feature = "async")]
+ self.tx_waker.wake();
+ }
+
+ if let Some(ack_number) = repr.ack_number {
+ // TODO: When flow control is implemented,
+ // refractor the following block within that implementation
+
+ // Detect and react to duplicate ACKs by:
+ // 1. Check if duplicate ACK and change self.local_rx_dup_acks accordingly
+ // 2. If exactly 3 duplicate ACKs received, set for fast retransmit
+ // 3. Update the last received ACK (self.local_rx_last_ack)
+ match self.local_rx_last_ack {
+ // Duplicate ACK if payload empty and ACK doesn't move send window ->
+ // Increment duplicate ACK count and set for retransmit if we just received
+ // the third duplicate ACK
+ Some(last_rx_ack)
+ if repr.payload.is_empty()
+ && last_rx_ack == ack_number
+ && ack_number < self.remote_last_seq
+ && !is_window_update =>
+ {
+ // Increment duplicate ACK count
+ self.local_rx_dup_acks = self.local_rx_dup_acks.saturating_add(1);
+
+ net_debug!(
+ "received duplicate ACK for seq {} (duplicate nr {}{})",
+ ack_number,
+ self.local_rx_dup_acks,
+ if self.local_rx_dup_acks == u8::max_value() {
+ "+"
+ } else {
+ ""
+ }
+ );
+
+ if self.local_rx_dup_acks == 3 {
+ self.timer.set_for_fast_retransmit();
+ net_debug!("started fast retransmit");
+ }
+ }
+ // No duplicate ACK -> Reset state and update last received ACK
+ _ => {
+ if self.local_rx_dup_acks > 0 {
+ self.local_rx_dup_acks = 0;
+ net_debug!("reset duplicate ACK count");
+ }
+ self.local_rx_last_ack = Some(ack_number);
+ }
+ };
+ // We've processed everything in the incoming segment, so advance the local
+ // sequence number past it.
+ self.local_seq_no = ack_number;
+ // During retransmission, if an earlier segment got lost but later was
+ // successfully received, self.local_seq_no can move past self.remote_last_seq.
+ // Do not attempt to retransmit the latter segments; not only this is pointless
+ // in theory but also impossible in practice, since they have been already
+ // deallocated from the buffer.
+ if self.remote_last_seq < self.local_seq_no {
+ self.remote_last_seq = self.local_seq_no
+ }
+ }
+
+ let payload_len = payload.len();
+ if payload_len == 0 {
+ return None;
+ }
+
+ let assembler_was_empty = self.assembler.is_empty();
+
+ // Try adding payload octets to the assembler.
+ let Ok(contig_len) = self
+ .assembler
+ .add_then_remove_front(payload_offset, payload_len)
+ else {
+ net_debug!(
+ "assembler: too many holes to add {} octets at offset {}",
+ payload_len,
+ payload_offset
+ );
+ return None;
+ };
+
+ // Place payload octets into the buffer.
+ tcp_trace!(
+ "rx buffer: receiving {} octets at offset {}",
+ payload_len,
+ payload_offset
+ );
+ let len_written = self.rx_buffer.write_unallocated(payload_offset, payload);
+ debug_assert!(len_written == payload_len);
+
+ if contig_len != 0 {
+ // Enqueue the contiguous data octets in front of the buffer.
+ tcp_trace!(
+ "rx buffer: enqueueing {} octets (now {})",
+ contig_len,
+ self.rx_buffer.len() + contig_len
+ );
+ self.rx_buffer.enqueue_unallocated(contig_len);
+
+ // There's new data in rx_buffer, notify waiting task if any.
+ #[cfg(feature = "async")]
+ self.rx_waker.wake();
+ }
+
+ if !self.assembler.is_empty() {
+ // Print the ranges recorded in the assembler.
+ tcp_trace!("assembler: {}", self.assembler);
+ }
+
+ // Handle delayed acks
+ if let Some(ack_delay) = self.ack_delay {
+ if self.ack_to_transmit() || self.window_to_update() {
+ self.ack_delay_timer = match self.ack_delay_timer {
+ AckDelayTimer::Idle => {
+ tcp_trace!("starting delayed ack timer");
+
+ AckDelayTimer::Waiting(cx.now() + ack_delay)
+ }
+ // RFC1122 says "in a stream of full-sized segments there SHOULD be an ACK
+ // for at least every second segment".
+ // For now, we send an ACK every second received packet, full-sized or not.
+ AckDelayTimer::Waiting(_) => {
+ tcp_trace!("delayed ack timer already started, forcing expiry");
+ AckDelayTimer::Immediate
+ }
+ AckDelayTimer::Immediate => {
+ tcp_trace!("delayed ack timer already force-expired");
+ AckDelayTimer::Immediate
+ }
+ };
+ }
+ }
+
+ // Per RFC 5681, we should send an immediate ACK when either:
+ // 1) an out-of-order segment is received, or
+ // 2) a segment arrives that fills in all or part of a gap in sequence space.
+ if !self.assembler.is_empty() || !assembler_was_empty {
+ // Note that we change the transmitter state here.
+ // This is fine because smoltcp assumes that it can always transmit zero or one
+ // packets for every packet it receives.
+ tcp_trace!("ACKing incoming segment");
+ Some(self.ack_reply(ip_repr, repr))
+ } else {
+ None
+ }
+ }
+
+ fn timed_out(&self, timestamp: Instant) -> bool {
+ match (self.remote_last_ts, self.timeout) {
+ (Some(remote_last_ts), Some(timeout)) => timestamp >= remote_last_ts + timeout,
+ (_, _) => false,
+ }
+ }
+
+ fn seq_to_transmit(&self, cx: &mut Context) -> bool {
+ let ip_header_len = match self.tuple.unwrap().local.addr {
+ #[cfg(feature = "proto-ipv4")]
+ IpAddress::Ipv4(_) => crate::wire::IPV4_HEADER_LEN,
+ #[cfg(feature = "proto-ipv6")]
+ IpAddress::Ipv6(_) => crate::wire::IPV6_HEADER_LEN,
+ };
+
+ // Max segment size we're able to send due to MTU limitations.
+ let local_mss = cx.ip_mtu() - ip_header_len - TCP_HEADER_LEN;
+
+ // The effective max segment size, taking into account our and remote's limits.
+ let effective_mss = local_mss.min(self.remote_mss);
+
+ // Have we sent data that hasn't been ACKed yet?
+ let data_in_flight = self.remote_last_seq != self.local_seq_no;
+
+ // If we want to send a SYN and we haven't done so, do it!
+ if matches!(self.state, State::SynSent | State::SynReceived) && !data_in_flight {
+ return true;
+ }
+
+ // max sequence number we can send.
+ let max_send_seq =
+ self.local_seq_no + core::cmp::min(self.remote_win_len, self.tx_buffer.len());
+
+ // Max amount of octets we can send.
+ let max_send = if max_send_seq >= self.remote_last_seq {
+ max_send_seq - self.remote_last_seq
+ } else {
+ 0
+ };
+
+ // Can we send at least 1 octet?
+ let mut can_send = max_send != 0;
+ // Can we send at least 1 full segment?
+ let can_send_full = max_send >= effective_mss;
+
+ // Do we have to send a FIN?
+ let want_fin = match self.state {
+ State::FinWait1 => true,
+ State::Closing => true,
+ State::LastAck => true,
+ _ => false,
+ };
+
+ // If we're applying the Nagle algorithm we don't want to send more
+ // until one of:
+ // * There's no data in flight
+ // * We can send a full packet
+ // * We have all the data we'll ever send (we're closing send)
+ if self.nagle && data_in_flight && !can_send_full && !want_fin {
+ can_send = false;
+ }
+
+ // Can we actually send the FIN? We can send it if:
+ // 1. We have unsent data that fits in the remote window.
+ // 2. We have no unsent data.
+ // This condition matches only if #2, because #1 is already covered by can_data and we're ORing them.
+ let can_fin = want_fin && self.remote_last_seq == self.local_seq_no + self.tx_buffer.len();
+
+ can_send || can_fin
+ }
+
+ fn delayed_ack_expired(&self, timestamp: Instant) -> bool {
+ match self.ack_delay_timer {
+ AckDelayTimer::Idle => true,
+ AckDelayTimer::Waiting(t) => t <= timestamp,
+ AckDelayTimer::Immediate => true,
+ }
+ }
+
+ fn ack_to_transmit(&self) -> bool {
+ if let Some(remote_last_ack) = self.remote_last_ack {
+ remote_last_ack < self.remote_seq_no + self.rx_buffer.len()
+ } else {
+ false
+ }
+ }
+
+ fn window_to_update(&self) -> bool {
+ match self.state {
+ State::SynSent
+ | State::SynReceived
+ | State::Established
+ | State::FinWait1
+ | State::FinWait2 => self.scaled_window() > self.remote_last_win,
+ _ => false,
+ }
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, (IpRepr, TcpRepr)) -> Result<(), E>,
+ {
+ if self.tuple.is_none() {
+ return Ok(());
+ }
+
+ if self.remote_last_ts.is_none() {
+ // We get here in exactly two cases:
+ // 1) This socket just transitioned into SYN-SENT.
+ // 2) This socket had an empty transmit buffer and some data was added there.
+ // Both are similar in that the socket has been quiet for an indefinite
+ // period of time, it isn't anymore, and the local endpoint is talking.
+ // So, we start counting the timeout not from the last received packet
+ // but from the first transmitted one.
+ self.remote_last_ts = Some(cx.now());
+ }
+
+ // Check if any state needs to be changed because of a timer.
+ if self.timed_out(cx.now()) {
+ // If a timeout expires, we should abort the connection.
+ net_debug!("timeout exceeded");
+ self.set_state(State::Closed);
+ } else if !self.seq_to_transmit(cx) {
+ if let Some(retransmit_delta) = self.timer.should_retransmit(cx.now()) {
+ // If a retransmit timer expired, we should resend data starting at the last ACK.
+ net_debug!("retransmitting at t+{}", retransmit_delta);
+
+ // Rewind "last sequence number sent", as if we never
+ // had sent them. This will cause all data in the queue
+ // to be sent again.
+ self.remote_last_seq = self.local_seq_no;
+
+ // Clear the `should_retransmit` state. If we can't retransmit right
+ // now for whatever reason (like zero window), this avoids an
+ // infinite polling loop where `poll_at` returns `Now` but `dispatch`
+ // can't actually do anything.
+ self.timer.set_for_idle(cx.now(), self.keep_alive);
+
+ // Inform RTTE, so that it can avoid bogus measurements.
+ self.rtte.on_retransmit();
+ }
+ }
+
+ // Decide whether we're sending a packet.
+ if self.seq_to_transmit(cx) {
+ // If we have data to transmit and it fits into partner's window, do it.
+ tcp_trace!("outgoing segment will send data or flags");
+ } else if self.ack_to_transmit() && self.delayed_ack_expired(cx.now()) {
+ // If we have data to acknowledge, do it.
+ tcp_trace!("outgoing segment will acknowledge");
+ } else if self.window_to_update() && self.delayed_ack_expired(cx.now()) {
+ // If we have window length increase to advertise, do it.
+ tcp_trace!("outgoing segment will update window");
+ } else if self.state == State::Closed {
+ // If we need to abort the connection, do it.
+ tcp_trace!("outgoing segment will abort connection");
+ } else if self.timer.should_keep_alive(cx.now()) {
+ // If we need to transmit a keep-alive packet, do it.
+ tcp_trace!("keep-alive timer expired");
+ } else if self.timer.should_close(cx.now()) {
+ // If we have spent enough time in the TIME-WAIT state, close the socket.
+ tcp_trace!("TIME-WAIT timer expired");
+ self.reset();
+ return Ok(());
+ } else {
+ return Ok(());
+ }
+
+ // NOTE(unwrap): we check tuple is not None the first thing in this function.
+ let tuple = self.tuple.unwrap();
+
+ // Construct the lowered IP representation.
+ // We might need this to calculate the MSS, so do it early.
+ let mut ip_repr = IpRepr::new(
+ tuple.local.addr,
+ tuple.remote.addr,
+ IpProtocol::Tcp,
+ 0,
+ self.hop_limit.unwrap_or(64),
+ );
+
+ // Construct the basic TCP representation, an empty ACK packet.
+ // We'll adjust this to be more specific as needed.
+ let mut repr = TcpRepr {
+ src_port: tuple.local.port,
+ dst_port: tuple.remote.port,
+ control: TcpControl::None,
+ seq_number: self.remote_last_seq,
+ ack_number: Some(self.remote_seq_no + self.rx_buffer.len()),
+ window_len: self.scaled_window(),
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+
+ match self.state {
+ // We transmit an RST in the CLOSED state. If we ended up in the CLOSED state
+ // with a specified endpoint, it means that the socket was aborted.
+ State::Closed => {
+ repr.control = TcpControl::Rst;
+ }
+
+ // We never transmit anything in the LISTEN state.
+ State::Listen => return Ok(()),
+
+ // We transmit a SYN in the SYN-SENT state.
+ // We transmit a SYN|ACK in the SYN-RECEIVED state.
+ State::SynSent | State::SynReceived => {
+ repr.control = TcpControl::Syn;
+ // window len must NOT be scaled in SYNs.
+ repr.window_len = self.rx_buffer.window().min((1 << 16) - 1) as u16;
+ if self.state == State::SynSent {
+ repr.ack_number = None;
+ repr.window_scale = Some(self.remote_win_shift);
+ repr.sack_permitted = true;
+ } else {
+ repr.sack_permitted = self.remote_has_sack;
+ repr.window_scale = self.remote_win_scale.map(|_| self.remote_win_shift);
+ }
+ }
+
+ // We transmit data in all states where we may have data in the buffer,
+ // or the transmit half of the connection is still open.
+ State::Established
+ | State::FinWait1
+ | State::Closing
+ | State::CloseWait
+ | State::LastAck => {
+ // Extract as much data as the remote side can receive in this packet
+ // from the transmit buffer.
+
+ // Right edge of window, ie the max sequence number we're allowed to send.
+ let win_right_edge = self.local_seq_no + self.remote_win_len;
+
+ // Max amount of octets we're allowed to send according to the remote window.
+ let win_limit = if win_right_edge >= self.remote_last_seq {
+ win_right_edge - self.remote_last_seq
+ } else {
+ // This can happen if we've sent some data and later the remote side
+ // has shrunk its window so that data is no longer inside the window.
+ // This should be very rare and is strongly discouraged by the RFCs,
+ // but it does happen in practice.
+ // http://www.tcpipguide.com/free/t_TCPWindowManagementIssues.htm
+ 0
+ };
+
+ // Maximum size we're allowed to send. This can be limited by 3 factors:
+ // 1. remote window
+ // 2. MSS the remote is willing to accept, probably determined by their MTU
+ // 3. MSS we can send, determined by our MTU.
+ let size = win_limit
+ .min(self.remote_mss)
+ .min(cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN);
+
+ let offset = self.remote_last_seq - self.local_seq_no;
+ repr.payload = self.tx_buffer.get_allocated(offset, size);
+
+ // If we've sent everything we had in the buffer, follow it with the PSH or FIN
+ // flags, depending on whether the transmit half of the connection is open.
+ if offset + repr.payload.len() == self.tx_buffer.len() {
+ match self.state {
+ State::FinWait1 | State::LastAck | State::Closing => {
+ repr.control = TcpControl::Fin
+ }
+ State::Established | State::CloseWait if !repr.payload.is_empty() => {
+ repr.control = TcpControl::Psh
+ }
+ _ => (),
+ }
+ }
+ }
+
+ // In FIN-WAIT-2 and TIME-WAIT states we may only transmit ACKs for incoming data or FIN
+ State::FinWait2 | State::TimeWait => {}
+ }
+
+ // There might be more than one reason to send a packet. E.g. the keep-alive timer
+ // has expired, and we also have data in transmit buffer. Since any packet that occupies
+ // sequence space will elicit an ACK, we only need to send an explicit packet if we
+ // couldn't fill the sequence space with anything.
+ let is_keep_alive;
+ if self.timer.should_keep_alive(cx.now()) && repr.is_empty() {
+ repr.seq_number = repr.seq_number - 1;
+ repr.payload = b"\x00"; // RFC 1122 says we should do this
+ is_keep_alive = true;
+ } else {
+ is_keep_alive = false;
+ }
+
+ // Trace a summary of what will be sent.
+ if is_keep_alive {
+ tcp_trace!("sending a keep-alive");
+ } else if !repr.payload.is_empty() {
+ tcp_trace!(
+ "tx buffer: sending {} octets at offset {}",
+ repr.payload.len(),
+ self.remote_last_seq - self.local_seq_no
+ );
+ }
+ if repr.control != TcpControl::None || repr.payload.is_empty() {
+ let flags = match (repr.control, repr.ack_number) {
+ (TcpControl::Syn, None) => "SYN",
+ (TcpControl::Syn, Some(_)) => "SYN|ACK",
+ (TcpControl::Fin, Some(_)) => "FIN|ACK",
+ (TcpControl::Rst, Some(_)) => "RST|ACK",
+ (TcpControl::Psh, Some(_)) => "PSH|ACK",
+ (TcpControl::None, Some(_)) => "ACK",
+ _ => "<unreachable>",
+ };
+ tcp_trace!("sending {}", flags);
+ }
+
+ if repr.control == TcpControl::Syn {
+ // Fill the MSS option. See RFC 6691 for an explanation of this calculation.
+ let max_segment_size = cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN;
+ repr.max_seg_size = Some(max_segment_size as u16);
+ }
+
+ // Actually send the packet. If this succeeds, it means the packet is in
+ // the device buffer, and its transmission is imminent. If not, we might have
+ // a number of problems, e.g. we need neighbor discovery.
+ //
+ // Bailing out if the packet isn't placed in the device buffer allows us
+ // to not waste time waiting for the retransmit timer on packets that we know
+ // for sure will not be successfully transmitted.
+ ip_repr.set_payload_len(repr.buffer_len());
+ emit(cx, (ip_repr, repr))?;
+
+ // We've sent something, whether useful data or a keep-alive packet, so rewind
+ // the keep-alive timer.
+ self.timer.rewind_keep_alive(cx.now(), self.keep_alive);
+
+ // Reset delayed-ack timer
+ match self.ack_delay_timer {
+ AckDelayTimer::Idle => {}
+ AckDelayTimer::Waiting(_) => {
+ tcp_trace!("stop delayed ack timer")
+ }
+ AckDelayTimer::Immediate => {
+ tcp_trace!("stop delayed ack timer (was force-expired)")
+ }
+ }
+ self.ack_delay_timer = AckDelayTimer::Idle;
+
+ // Leave the rest of the state intact if sending a keep-alive packet, since those
+ // carry a fake segment.
+ if is_keep_alive {
+ return Ok(());
+ }
+
+ // We've sent a packet successfully, so we can update the internal state now.
+ self.remote_last_seq = repr.seq_number + repr.segment_len();
+ self.remote_last_ack = repr.ack_number;
+ self.remote_last_win = repr.window_len;
+
+ if repr.segment_len() > 0 {
+ self.rtte
+ .on_send(cx.now(), repr.seq_number + repr.segment_len());
+ }
+
+ if !self.seq_to_transmit(cx) && repr.segment_len() > 0 {
+ // If we've transmitted all data we could (and there was something at all,
+ // data or flag, to transmit, not just an ACK), wind up the retransmit timer.
+ self.timer
+ .set_for_retransmit(cx.now(), self.rtte.retransmission_timeout());
+ }
+
+ if self.state == State::Closed {
+ // When aborting a connection, forget about it after sending a single RST packet.
+ self.tuple = None;
+ #[cfg(feature = "async")]
+ {
+ // Wake tx now so that async users can wait for the RST to be sent
+ self.tx_waker.wake();
+ }
+ }
+
+ Ok(())
+ }
+
+ #[allow(clippy::if_same_then_else)]
+ pub(crate) fn poll_at(&self, cx: &mut Context) -> PollAt {
+ // The logic here mirrors the beginning of dispatch() closely.
+ if self.tuple.is_none() {
+ // No one to talk to, nothing to transmit.
+ PollAt::Ingress
+ } else if self.remote_last_ts.is_none() {
+ // Socket stopped being quiet recently, we need to acquire a timestamp.
+ PollAt::Now
+ } else if self.state == State::Closed {
+ // Socket was aborted, we have an RST packet to transmit.
+ PollAt::Now
+ } else if self.seq_to_transmit(cx) {
+ // We have a data or flag packet to transmit.
+ PollAt::Now
+ } else {
+ let want_ack = self.ack_to_transmit() || self.window_to_update();
+
+ let delayed_ack_poll_at = match (want_ack, self.ack_delay_timer) {
+ (false, _) => PollAt::Ingress,
+ (true, AckDelayTimer::Idle) => PollAt::Now,
+ (true, AckDelayTimer::Waiting(t)) => PollAt::Time(t),
+ (true, AckDelayTimer::Immediate) => PollAt::Now,
+ };
+
+ let timeout_poll_at = match (self.remote_last_ts, self.timeout) {
+ // If we're transmitting or retransmitting data, we need to poll at the moment
+ // when the timeout would expire.
+ (Some(remote_last_ts), Some(timeout)) => PollAt::Time(remote_last_ts + timeout),
+ // Otherwise we have no timeout.
+ (_, _) => PollAt::Ingress,
+ };
+
+ // We wait for the earliest of our timers to fire.
+ *[self.timer.poll_at(), timeout_poll_at, delayed_ack_poll_at]
+ .iter()
+ .min()
+ .unwrap_or(&PollAt::Ingress)
+ }
+ }
+}
+
+impl<'a> fmt::Write for Socket<'a> {
+ fn write_str(&mut self, slice: &str) -> fmt::Result {
+ let slice = slice.as_bytes();
+ if self.send_slice(slice) == Ok(slice.len()) {
+ Ok(())
+ } else {
+ Err(fmt::Error)
+ }
+ }
+}
+
+// TODO: TCP should work for all features. For now, we only test with the IP feature. We could do
+// it for other features as well with rstest, however, this means we have to modify a lot of the
+// tests in here, which I didn't had the time for at the moment.
+#[cfg(all(test, feature = "medium-ip"))]
+mod test {
+ use super::*;
+ use crate::wire::IpRepr;
+ use core::i32;
+ use std::ops::{Deref, DerefMut};
+ use std::vec::Vec;
+
+ // =========================================================================================//
+ // Constants
+ // =========================================================================================//
+
+ const LOCAL_PORT: u16 = 80;
+ const REMOTE_PORT: u16 = 49500;
+ const LISTEN_END: IpListenEndpoint = IpListenEndpoint {
+ addr: None,
+ port: LOCAL_PORT,
+ };
+ const LOCAL_END: IpEndpoint = IpEndpoint {
+ addr: LOCAL_ADDR.into_address(),
+ port: LOCAL_PORT,
+ };
+ const REMOTE_END: IpEndpoint = IpEndpoint {
+ addr: REMOTE_ADDR.into_address(),
+ port: REMOTE_PORT,
+ };
+ const TUPLE: Tuple = Tuple {
+ local: LOCAL_END,
+ remote: REMOTE_END,
+ };
+ const LOCAL_SEQ: TcpSeqNumber = TcpSeqNumber(10000);
+ const REMOTE_SEQ: TcpSeqNumber = TcpSeqNumber(-10001);
+
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "proto-ipv4")] {
+ use crate::wire::Ipv4Address as IpvXAddress;
+ use crate::wire::Ipv4Repr as IpvXRepr;
+ use IpRepr::Ipv4 as IpReprIpvX;
+
+ const LOCAL_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 1]);
+ const REMOTE_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 2]);
+ const OTHER_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 3]);
+
+ const BASE_MSS: u16 = 1460;
+ } else {
+ use crate::wire::Ipv6Address as IpvXAddress;
+ use crate::wire::Ipv6Repr as IpvXRepr;
+ use IpRepr::Ipv6 as IpReprIpvX;
+
+ const LOCAL_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ ]);
+ const REMOTE_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ ]);
+ const OTHER_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ ]);
+
+ const BASE_MSS: u16 = 1440;
+ }
+ }
+
+ const SEND_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr {
+ src_addr: LOCAL_ADDR,
+ dst_addr: REMOTE_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: 20,
+ hop_limit: 64,
+ });
+ const SEND_TEMPL: TcpRepr<'static> = TcpRepr {
+ src_port: REMOTE_PORT,
+ dst_port: LOCAL_PORT,
+ control: TcpControl::None,
+ seq_number: TcpSeqNumber(0),
+ ack_number: Some(TcpSeqNumber(0)),
+ window_len: 256,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+ const _RECV_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr {
+ src_addr: LOCAL_ADDR,
+ dst_addr: REMOTE_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: 20,
+ hop_limit: 64,
+ });
+ const RECV_TEMPL: TcpRepr<'static> = TcpRepr {
+ src_port: LOCAL_PORT,
+ dst_port: REMOTE_PORT,
+ control: TcpControl::None,
+ seq_number: TcpSeqNumber(0),
+ ack_number: Some(TcpSeqNumber(0)),
+ window_len: 64,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+
+ // =========================================================================================//
+ // Helper functions
+ // =========================================================================================//
+
+ struct TestSocket {
+ socket: Socket<'static>,
+ cx: Context,
+ }
+
+ impl Deref for TestSocket {
+ type Target = Socket<'static>;
+ fn deref(&self) -> &Self::Target {
+ &self.socket
+ }
+ }
+
+ impl DerefMut for TestSocket {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.socket
+ }
+ }
+
+ fn send(
+ socket: &mut TestSocket,
+ timestamp: Instant,
+ repr: &TcpRepr,
+ ) -> Option<TcpRepr<'static>> {
+ socket.cx.set_now(timestamp);
+
+ let ip_repr = IpReprIpvX(IpvXRepr {
+ src_addr: REMOTE_ADDR,
+ dst_addr: LOCAL_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: repr.buffer_len(),
+ hop_limit: 64,
+ });
+ net_trace!("send: {}", repr);
+
+ assert!(socket.socket.accepts(&mut socket.cx, &ip_repr, repr));
+
+ match socket.socket.process(&mut socket.cx, &ip_repr, repr) {
+ Some((_ip_repr, repr)) => {
+ net_trace!("recv: {}", repr);
+ Some(repr)
+ }
+ None => None,
+ }
+ }
+
+ fn recv<F>(socket: &mut TestSocket, timestamp: Instant, mut f: F)
+ where
+ F: FnMut(Result<TcpRepr, ()>),
+ {
+ socket.cx.set_now(timestamp);
+
+ let mut sent = 0;
+ let result = socket
+ .socket
+ .dispatch(&mut socket.cx, |_, (ip_repr, tcp_repr)| {
+ assert_eq!(ip_repr.next_header(), IpProtocol::Tcp);
+ assert_eq!(ip_repr.src_addr(), LOCAL_ADDR.into());
+ assert_eq!(ip_repr.dst_addr(), REMOTE_ADDR.into());
+ assert_eq!(ip_repr.payload_len(), tcp_repr.buffer_len());
+
+ net_trace!("recv: {}", tcp_repr);
+ sent += 1;
+ Ok(f(Ok(tcp_repr)))
+ });
+ match result {
+ Ok(()) => assert_eq!(sent, 1, "Exactly one packet should be sent"),
+ Err(e) => f(Err(e)),
+ }
+ }
+
+ fn recv_nothing(socket: &mut TestSocket, timestamp: Instant) {
+ socket.cx.set_now(timestamp);
+
+ let result: Result<(), ()> = socket
+ .socket
+ .dispatch(&mut socket.cx, |_, (_ip_repr, _tcp_repr)| {
+ panic!("Should not send a packet")
+ });
+
+ assert_eq!(result, Ok(()))
+ }
+
+ macro_rules! send {
+ ($socket:ident, $repr:expr) =>
+ (send!($socket, time 0, $repr));
+ ($socket:ident, $repr:expr, $result:expr) =>
+ (send!($socket, time 0, $repr, $result));
+ ($socket:ident, time $time:expr, $repr:expr) =>
+ (send!($socket, time $time, $repr, None));
+ ($socket:ident, time $time:expr, $repr:expr, $result:expr) =>
+ (assert_eq!(send(&mut $socket, Instant::from_millis($time), &$repr), $result));
+ }
+
+ macro_rules! recv {
+ ($socket:ident, [$( $repr:expr ),*]) => ({
+ $( recv!($socket, Ok($repr)); )*
+ recv_nothing!($socket)
+ });
+ ($socket:ident, $result:expr) =>
+ (recv!($socket, time 0, $result));
+ ($socket:ident, time $time:expr, $result:expr) =>
+ (recv(&mut $socket, Instant::from_millis($time), |result| {
+ // Most of the time we don't care about the PSH flag.
+ let result = result.map(|mut repr| {
+ repr.control = repr.control.quash_psh();
+ repr
+ });
+ assert_eq!(result, $result)
+ }));
+ ($socket:ident, time $time:expr, $result:expr, exact) =>
+ (recv(&mut $socket, Instant::from_millis($time), |repr| assert_eq!(repr, $result)));
+ }
+
+ macro_rules! recv_nothing {
+ ($socket:ident) => (recv_nothing!($socket, time 0));
+ ($socket:ident, time $time:expr) => (recv_nothing(&mut $socket, Instant::from_millis($time)));
+ }
+
+ macro_rules! sanity {
+ ($socket1:expr, $socket2:expr) => {{
+ let (s1, s2) = ($socket1, $socket2);
+ assert_eq!(s1.state, s2.state, "state");
+ assert_eq!(s1.tuple, s2.tuple, "tuple");
+ assert_eq!(s1.local_seq_no, s2.local_seq_no, "local_seq_no");
+ assert_eq!(s1.remote_seq_no, s2.remote_seq_no, "remote_seq_no");
+ assert_eq!(s1.remote_last_seq, s2.remote_last_seq, "remote_last_seq");
+ assert_eq!(s1.remote_last_ack, s2.remote_last_ack, "remote_last_ack");
+ assert_eq!(s1.remote_last_win, s2.remote_last_win, "remote_last_win");
+ assert_eq!(s1.remote_win_len, s2.remote_win_len, "remote_win_len");
+ assert_eq!(s1.timer, s2.timer, "timer");
+ }};
+ }
+
+ fn socket() -> TestSocket {
+ socket_with_buffer_sizes(64, 64)
+ }
+
+ fn socket_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket {
+ let (iface, _, _) = crate::tests::setup(crate::phy::Medium::Ip);
+
+ let rx_buffer = SocketBuffer::new(vec![0; rx_len]);
+ let tx_buffer = SocketBuffer::new(vec![0; tx_len]);
+ let mut socket = Socket::new(rx_buffer, tx_buffer);
+ socket.set_ack_delay(None);
+ TestSocket {
+ socket,
+ cx: iface.inner,
+ }
+ }
+
+ fn socket_syn_received_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket {
+ let mut s = socket_with_buffer_sizes(tx_len, rx_len);
+ s.state = State::SynReceived;
+ s.tuple = Some(TUPLE);
+ s.local_seq_no = LOCAL_SEQ;
+ s.remote_seq_no = REMOTE_SEQ + 1;
+ s.remote_last_seq = LOCAL_SEQ;
+ s.remote_win_len = 256;
+ s
+ }
+
+ fn socket_syn_received() -> TestSocket {
+ socket_syn_received_with_buffer_sizes(64, 64)
+ }
+
+ fn socket_syn_sent_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket {
+ let mut s = socket_with_buffer_sizes(tx_len, rx_len);
+ s.state = State::SynSent;
+ s.tuple = Some(TUPLE);
+ s.local_seq_no = LOCAL_SEQ;
+ s.remote_last_seq = LOCAL_SEQ;
+ s
+ }
+
+ fn socket_syn_sent() -> TestSocket {
+ socket_syn_sent_with_buffer_sizes(64, 64)
+ }
+
+ fn socket_established_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket {
+ let mut s = socket_syn_received_with_buffer_sizes(tx_len, rx_len);
+ s.state = State::Established;
+ s.local_seq_no = LOCAL_SEQ + 1;
+ s.remote_last_seq = LOCAL_SEQ + 1;
+ s.remote_last_ack = Some(REMOTE_SEQ + 1);
+ s.remote_last_win = 64;
+ s
+ }
+
+ fn socket_established() -> TestSocket {
+ socket_established_with_buffer_sizes(64, 64)
+ }
+
+ fn socket_fin_wait_1() -> TestSocket {
+ let mut s = socket_established();
+ s.state = State::FinWait1;
+ s
+ }
+
+ fn socket_fin_wait_2() -> TestSocket {
+ let mut s = socket_fin_wait_1();
+ s.state = State::FinWait2;
+ s.local_seq_no = LOCAL_SEQ + 1 + 1;
+ s.remote_last_seq = LOCAL_SEQ + 1 + 1;
+ s
+ }
+
+ fn socket_closing() -> TestSocket {
+ let mut s = socket_fin_wait_1();
+ s.state = State::Closing;
+ s.remote_last_seq = LOCAL_SEQ + 1 + 1;
+ s.remote_seq_no = REMOTE_SEQ + 1 + 1;
+ s
+ }
+
+ fn socket_time_wait(from_closing: bool) -> TestSocket {
+ let mut s = socket_fin_wait_2();
+ s.state = State::TimeWait;
+ s.remote_seq_no = REMOTE_SEQ + 1 + 1;
+ if from_closing {
+ s.remote_last_ack = Some(REMOTE_SEQ + 1 + 1);
+ }
+ s.timer = Timer::Close {
+ expires_at: Instant::from_secs(1) + CLOSE_DELAY,
+ };
+ s
+ }
+
+ fn socket_close_wait() -> TestSocket {
+ let mut s = socket_established();
+ s.state = State::CloseWait;
+ s.remote_seq_no = REMOTE_SEQ + 1 + 1;
+ s.remote_last_ack = Some(REMOTE_SEQ + 1 + 1);
+ s
+ }
+
+ fn socket_last_ack() -> TestSocket {
+ let mut s = socket_close_wait();
+ s.state = State::LastAck;
+ s
+ }
+
+ fn socket_recved() -> TestSocket {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ }]
+ );
+ s
+ }
+
+ // =========================================================================================//
+ // Tests for the CLOSED state.
+ // =========================================================================================//
+ #[test]
+ fn test_closed_reject() {
+ let mut s = socket();
+ assert_eq!(s.state, State::Closed);
+
+ let tcp_repr = TcpRepr {
+ control: TcpControl::Syn,
+ ..SEND_TEMPL
+ };
+ assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr));
+ }
+
+ #[test]
+ fn test_closed_reject_after_listen() {
+ let mut s = socket();
+ s.listen(LOCAL_END).unwrap();
+ s.close();
+
+ let tcp_repr = TcpRepr {
+ control: TcpControl::Syn,
+ ..SEND_TEMPL
+ };
+ assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr));
+ }
+
+ #[test]
+ fn test_closed_close() {
+ let mut s = socket();
+ s.close();
+ assert_eq!(s.state, State::Closed);
+ }
+
+ // =========================================================================================//
+ // Tests for the LISTEN state.
+ // =========================================================================================//
+ fn socket_listen() -> TestSocket {
+ let mut s = socket();
+ s.state = State::Listen;
+ s.listen_endpoint = LISTEN_END;
+ s
+ }
+
+ #[test]
+ fn test_listen_sack_option() {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ sack_permitted: false,
+ ..SEND_TEMPL
+ }
+ );
+ assert!(!s.remote_has_sack);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ sack_permitted: true,
+ ..SEND_TEMPL
+ }
+ );
+ assert!(s.remote_has_sack);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_listen_syn_win_scale_buffers() {
+ for (buffer_size, shift_amt) in &[
+ (64, 0),
+ (128, 0),
+ (1024, 0),
+ (65535, 0),
+ (65536, 1),
+ (65537, 1),
+ (131071, 1),
+ (131072, 2),
+ (524287, 3),
+ (524288, 4),
+ (655350, 4),
+ (1048576, 5),
+ ] {
+ let mut s = socket_with_buffer_sizes(64, *buffer_size);
+ s.state = State::Listen;
+ s.listen_endpoint = LISTEN_END;
+ assert_eq!(s.remote_win_shift, *shift_amt);
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ window_scale: Some(0),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.remote_win_shift, *shift_amt);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(*shift_amt),
+ window_len: cmp::min(*buffer_size, 65535) as u16,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+ }
+
+ #[test]
+ fn test_listen_sanity() {
+ let mut s = socket();
+ s.listen(LOCAL_PORT).unwrap();
+ sanity!(s, socket_listen());
+ }
+
+ #[test]
+ fn test_listen_validation() {
+ let mut s = socket();
+ assert_eq!(s.listen(0), Err(ListenError::Unaddressable));
+ }
+
+ #[test]
+ fn test_listen_twice() {
+ let mut s = socket();
+ assert_eq!(s.listen(80), Ok(()));
+ // multiple calls to listen are okay if its the same local endpoint and the state is still in listening
+ assert_eq!(s.listen(80), Ok(()));
+ s.set_state(State::SynReceived); // state change, simulate incoming connection
+ assert_eq!(s.listen(80), Err(ListenError::InvalidState));
+ }
+
+ #[test]
+ fn test_listen_syn() {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ sanity!(s, socket_syn_received());
+ }
+
+ #[test]
+ fn test_listen_syn_reject_ack() {
+ let mut s = socket_listen();
+
+ let tcp_repr = TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ),
+ ..SEND_TEMPL
+ };
+ assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr));
+
+ assert_eq!(s.state, State::Listen);
+ }
+
+ #[test]
+ fn test_listen_rst() {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Listen);
+ }
+
+ #[test]
+ fn test_listen_close() {
+ let mut s = socket_listen();
+ s.close();
+ assert_eq!(s.state, State::Closed);
+ }
+
+ // =========================================================================================//
+ // Tests for the SYN-RECEIVED state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_syn_received_ack() {
+ let mut s = socket_syn_received();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Established);
+ sanity!(s, socket_established());
+ }
+
+ #[test]
+ fn test_syn_received_ack_too_low() {
+ let mut s = socket_syn_received();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ), // wrong
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.state, State::SynReceived);
+ }
+
+ #[test]
+ fn test_syn_received_ack_too_high() {
+ let mut s = socket_syn_received();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 2), // wrong
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 2,
+ ack_number: None,
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.state, State::SynReceived);
+ }
+
+ #[test]
+ fn test_syn_received_fin() {
+ let mut s = socket_syn_received();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6 + 1),
+ window_len: 58,
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::CloseWait);
+
+ let mut s2 = socket_close_wait();
+ s2.remote_last_ack = Some(REMOTE_SEQ + 1 + 6 + 1);
+ s2.remote_last_win = 58;
+ sanity!(s, s2);
+ }
+
+ #[test]
+ fn test_syn_received_rst() {
+ let mut s = socket_syn_received();
+ s.listen_endpoint = LISTEN_END;
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Listen);
+ assert_eq!(s.listen_endpoint, LISTEN_END);
+ assert_eq!(s.tuple, None);
+ }
+
+ #[test]
+ fn test_syn_received_no_window_scaling() {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state(), State::SynReceived);
+ assert_eq!(s.tuple, Some(TUPLE));
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ window_scale: None,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_scale: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.remote_win_shift, 0);
+ assert_eq!(s.remote_win_scale, None);
+ }
+
+ #[test]
+ fn test_syn_received_window_scaling() {
+ for scale in 0..14 {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ window_scale: Some(scale),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state(), State::SynReceived);
+ assert_eq!(s.tuple, Some(TUPLE));
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_scale: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.remote_win_scale, Some(scale));
+ }
+ }
+
+ #[test]
+ fn test_syn_received_close() {
+ let mut s = socket_syn_received();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ }
+
+ // =========================================================================================//
+ // Tests for the SYN-SENT state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_connect_validation() {
+ let mut s = socket();
+ assert_eq!(
+ s.socket
+ .connect(&mut s.cx, REMOTE_END, (IpvXAddress::UNSPECIFIED, 0)),
+ Err(ConnectError::Unaddressable)
+ );
+ assert_eq!(
+ s.socket
+ .connect(&mut s.cx, REMOTE_END, (IpvXAddress::UNSPECIFIED, 1024)),
+ Err(ConnectError::Unaddressable)
+ );
+ assert_eq!(
+ s.socket
+ .connect(&mut s.cx, (IpvXAddress::UNSPECIFIED, 0), LOCAL_END),
+ Err(ConnectError::Unaddressable)
+ );
+ s.socket
+ .connect(&mut s.cx, REMOTE_END, LOCAL_END)
+ .expect("Connect failed with valid parameters");
+ assert_eq!(s.tuple, Some(TUPLE));
+ }
+
+ #[test]
+ fn test_connect() {
+ let mut s = socket();
+ s.local_seq_no = LOCAL_SEQ;
+ s.socket
+ .connect(&mut s.cx, REMOTE_END, LOCAL_END.port)
+ .unwrap();
+ assert_eq!(s.tuple, Some(TUPLE));
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ max_seg_size: Some(BASE_MSS - 80),
+ window_scale: Some(0),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.tuple, Some(TUPLE));
+ }
+
+ #[test]
+ fn test_connect_unspecified_local() {
+ let mut s = socket();
+ assert_eq!(s.socket.connect(&mut s.cx, REMOTE_END, 80), Ok(()));
+ }
+
+ #[test]
+ fn test_connect_specified_local() {
+ let mut s = socket();
+ assert_eq!(
+ s.socket.connect(&mut s.cx, REMOTE_END, (REMOTE_ADDR, 80)),
+ Ok(())
+ );
+ }
+
+ #[test]
+ fn test_connect_twice() {
+ let mut s = socket();
+ assert_eq!(s.socket.connect(&mut s.cx, REMOTE_END, 80), Ok(()));
+ assert_eq!(
+ s.socket.connect(&mut s.cx, REMOTE_END, 80),
+ Err(ConnectError::InvalidState)
+ );
+ }
+
+ #[test]
+ fn test_syn_sent_sanity() {
+ let mut s = socket();
+ s.local_seq_no = LOCAL_SEQ;
+ s.socket.connect(&mut s.cx, REMOTE_END, LOCAL_END).unwrap();
+ sanity!(s, socket_syn_sent());
+ }
+
+ #[test]
+ fn test_syn_sent_syn_ack() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ max_seg_size: Some(BASE_MSS - 80),
+ window_scale: Some(0),
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ recv_nothing!(s, time 1000);
+ assert_eq!(s.state, State::Established);
+ sanity!(s, socket_established());
+ }
+
+ #[test]
+ fn test_syn_sent_syn_ack_not_incremented() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ), // WRONG
+ max_seg_size: Some(BASE_MSS - 80),
+ window_scale: Some(0),
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_rst() {
+ let mut s = socket_syn_sent();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_syn_sent_rst_no_ack() {
+ let mut s = socket_syn_sent();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_rst_bad_ack() {
+ let mut s = socket_syn_sent();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(TcpSeqNumber(1234)),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_bad_ack() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::None, // Unexpected
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1), // Correct
+ ..SEND_TEMPL
+ }
+ );
+
+ // It should trigger no response and change no state
+ recv!(s, []);
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_bad_ack_seq_1() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::None,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ), // WRONG
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ, // matching the ack_number of the unexpected ack
+ ack_number: None,
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+
+ // It should trigger a RST, and change no state
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_bad_ack_seq_2() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::None,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 123456), // WRONG
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 123456, // matching the ack_number of the unexpected ack
+ ack_number: None,
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+
+ // It should trigger a RST, and change no state
+ assert_eq!(s.state, State::SynSent);
+ }
+
+ #[test]
+ fn test_syn_sent_close() {
+ let mut s = socket();
+ s.close();
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_syn_sent_win_scale_buffers() {
+ for (buffer_size, shift_amt) in &[
+ (64, 0),
+ (128, 0),
+ (1024, 0),
+ (65535, 0),
+ (65536, 1),
+ (65537, 1),
+ (131071, 1),
+ (131072, 2),
+ (524287, 3),
+ (524288, 4),
+ (655350, 4),
+ (1048576, 5),
+ ] {
+ let mut s = socket_with_buffer_sizes(64, *buffer_size);
+ s.local_seq_no = LOCAL_SEQ;
+ assert_eq!(s.remote_win_shift, *shift_amt);
+ s.socket.connect(&mut s.cx, REMOTE_END, LOCAL_END).unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(*shift_amt),
+ window_len: cmp::min(*buffer_size, 65535) as u16,
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+ }
+
+ #[test]
+ fn test_syn_sent_syn_ack_no_window_scaling() {
+ let mut s = socket_syn_sent_with_buffer_sizes(1048576, 1048576);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ // scaling does NOT apply to the window value in SYN packets
+ window_len: 65535,
+ window_scale: Some(5),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.remote_win_shift, 5);
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ max_seg_size: Some(BASE_MSS - 80),
+ window_scale: None,
+ window_len: 42,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Established);
+ assert_eq!(s.remote_win_shift, 0);
+ assert_eq!(s.remote_win_scale, None);
+ assert_eq!(s.remote_win_len, 42);
+ }
+
+ #[test]
+ fn test_syn_sent_syn_ack_window_scaling() {
+ let mut s = socket_syn_sent();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ max_seg_size: Some(BASE_MSS - 80),
+ window_scale: Some(7),
+ window_len: 42,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Established);
+ assert_eq!(s.remote_win_scale, Some(7));
+ // scaling does NOT apply to the window value in SYN packets
+ assert_eq!(s.remote_win_len, 42);
+ }
+
+ // =========================================================================================//
+ // Tests for the ESTABLISHED state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_established_recv() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.rx_buffer.dequeue_many(6), &b"abcdef"[..]);
+ }
+
+ #[test]
+ fn test_peek_slice() {
+ const BUF_SIZE: usize = 10;
+
+ let send_buf = b"0123456";
+
+ let mut s = socket_established_with_buffer_sizes(BUF_SIZE, BUF_SIZE);
+
+ // Populate the recv buffer
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &send_buf[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // Peek into the recv buffer
+ let mut peeked_buf = [0u8; BUF_SIZE];
+ let actually_peeked = s.peek_slice(&mut peeked_buf[..]).unwrap();
+ let mut recv_buf = [0u8; BUF_SIZE];
+ let actually_recvd = s.recv_slice(&mut recv_buf[..]).unwrap();
+ assert_eq!(
+ &mut peeked_buf[..actually_peeked],
+ &mut recv_buf[..actually_recvd]
+ );
+ }
+
+ #[test]
+ fn test_peek_slice_buffer_wrap() {
+ const BUF_SIZE: usize = 10;
+
+ let send_buf = b"0123456789";
+
+ let mut s = socket_established_with_buffer_sizes(BUF_SIZE, BUF_SIZE);
+
+ let _ = s.rx_buffer.enqueue_slice(&send_buf[..8]);
+ let _ = s.rx_buffer.dequeue_many(6);
+ let _ = s.rx_buffer.enqueue_slice(&send_buf[..5]);
+
+ let mut peeked_buf = [0u8; BUF_SIZE];
+ let actually_peeked = s.peek_slice(&mut peeked_buf[..]).unwrap();
+ let mut recv_buf = [0u8; BUF_SIZE];
+ let actually_recvd = s.recv_slice(&mut recv_buf[..]).unwrap();
+ assert_eq!(
+ &mut peeked_buf[..actually_peeked],
+ &mut recv_buf[..actually_recvd]
+ );
+ }
+
+ fn setup_rfc2018_cases() -> (TestSocket, Vec<u8>) {
+ // This is a utility function used by the tests for RFC 2018 cases. It configures a socket
+ // in a particular way suitable for those cases.
+ //
+ // RFC 2018: Assume the left window edge is 5000 and that the data transmitter sends [...]
+ // segments, each containing 500 data bytes.
+ let mut s = socket_established_with_buffer_sizes(4000, 4000);
+ s.remote_has_sack = true;
+
+ // create a segment that is 500 bytes long
+ let mut segment: Vec<u8> = Vec::with_capacity(500);
+
+ // move the last ack to 5000 by sending ten of them
+ for _ in 0..50 {
+ segment.extend_from_slice(b"abcdefghij")
+ }
+ for offset in (0..5000).step_by(500) {
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + offset,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &segment,
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + offset + 500),
+ window_len: 3500,
+ ..RECV_TEMPL
+ }]
+ );
+ s.recv(|data| {
+ assert_eq!(data.len(), 500);
+ assert_eq!(data, segment.as_slice());
+ (500, ())
+ })
+ .unwrap();
+ }
+ assert_eq!(s.remote_last_win, 3500);
+ (s, segment)
+ }
+
+ #[test]
+ fn test_established_rfc2018_cases() {
+ // This test case verifies the exact scenarios described on pages 8-9 of RFC 2018. Please
+ // ensure its behavior does not deviate from those scenarios.
+
+ let (mut s, segment) = setup_rfc2018_cases();
+ // RFC 2018:
+ //
+ // Case 2: The first segment is dropped but the remaining 7 are received.
+ //
+ // Upon receiving each of the last seven packets, the data receiver will return a TCP ACK
+ // segment that acknowledges sequence number 5000 and contains a SACK option specifying one
+ // block of queued data:
+ //
+ // Triggering ACK Left Edge Right Edge
+ // Segment
+ //
+ // 5000 (lost)
+ // 5500 5000 5500 6000
+ // 6000 5000 5500 6500
+ // 6500 5000 5500 7000
+ // 7000 5000 5500 7500
+ // 7500 5000 5500 8000
+ // 8000 5000 5500 8500
+ // 8500 5000 5500 9000
+ //
+ for offset in (500..3500).step_by(500) {
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + offset + 5000,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &segment,
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 5000),
+ window_len: 4000,
+ sack_ranges: [
+ Some((
+ REMOTE_SEQ.0 as u32 + 1 + 5500,
+ REMOTE_SEQ.0 as u32 + 1 + 5500 + offset as u32
+ )),
+ None,
+ None
+ ],
+ ..RECV_TEMPL
+ })
+ );
+ }
+ }
+
+ #[test]
+ fn test_established_sliding_window_recv() {
+ let mut s = socket_established();
+ // Update our scaling parameters for a TCP with a scaled buffer.
+ assert_eq!(s.rx_buffer.len(), 0);
+ s.rx_buffer = SocketBuffer::new(vec![0; 262143]);
+ s.assembler = Assembler::new();
+ s.remote_win_scale = Some(0);
+ s.remote_last_win = 65535;
+ s.remote_win_shift = 2;
+
+ // Create a TCP segment that will mostly fill an IP frame.
+ let mut segment: Vec<u8> = Vec::with_capacity(1400);
+ for _ in 0..100 {
+ segment.extend_from_slice(b"abcdefghijklmn")
+ }
+ assert_eq!(segment.len(), 1400);
+
+ // Send the frame
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &segment,
+ ..SEND_TEMPL
+ }
+ );
+
+ // Ensure that the received window size is shifted right by 2.
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1400),
+ window_len: 65185,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_send() {
+ let mut s = socket_established();
+ // First roundtrip after establishing.
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.tx_buffer.len(), 6);
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.tx_buffer.len(), 0);
+ // Second roundtrip.
+ s.send_slice(b"foobar").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"foobar"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.tx_buffer.len(), 0);
+ }
+
+ #[test]
+ fn test_established_send_no_ack_send() {
+ let mut s = socket_established();
+ s.set_nagle_enabled(false);
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ s.send_slice(b"foobar").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"foobar"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_send_buf_gt_win() {
+ let mut data = [0; 32];
+ for (i, elem) in data.iter_mut().enumerate() {
+ *elem = i as u8
+ }
+
+ let mut s = socket_established();
+ s.remote_win_len = 16;
+ s.send_slice(&data[..]).unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &data[0..16],
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_send_window_shrink() {
+ let mut s = socket_established();
+
+ // 6 octets fit on the remote side's window, so we send them.
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.tx_buffer.len(), 6);
+
+ println!(
+ "local_seq_no={} remote_win_len={} remote_last_seq={}",
+ s.local_seq_no, s.remote_win_len, s.remote_last_seq
+ );
+
+ // - Peer doesn't ack them yet
+ // - Sends data so we need to reply with an ACK
+ // - ...AND and sends a window announcement that SHRINKS the window, so data we've
+ // previously sent is now outside the window. Yes, this is allowed by TCP.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_len: 3,
+ payload: &b"xyzxyz"[..],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.tx_buffer.len(), 6);
+
+ println!(
+ "local_seq_no={} remote_win_len={} remote_last_seq={}",
+ s.local_seq_no, s.remote_win_len, s.remote_last_seq
+ );
+
+ // More data should not get sent since it doesn't fit in the window
+ s.send_slice(b"foobar").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 64 - 6,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_receive_partially_outside_window() {
+ let mut s = socket_established();
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+
+ // Peer decides to retransmit (perhaps because the ACK was lost)
+ // and also pushed data.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ s.recv(|data| {
+ assert_eq!(data, b"def");
+ (3, ())
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_established_send_wrap() {
+ let mut s = socket_established();
+ let local_seq_start = TcpSeqNumber(i32::MAX - 1);
+ s.local_seq_no = local_seq_start + 1;
+ s.remote_last_seq = local_seq_start + 1;
+ s.send_slice(b"abc").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: local_seq_start + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_established_no_ack() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ }
+
+ #[test]
+ fn test_established_bad_ack() {
+ let mut s = socket_established();
+ // Already acknowledged data.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(TcpSeqNumber(LOCAL_SEQ.0 - 1)),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.local_seq_no, LOCAL_SEQ + 1);
+ // Data not yet transmitted.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 10),
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.local_seq_no, LOCAL_SEQ + 1);
+ }
+
+ #[test]
+ fn test_established_bad_seq() {
+ let mut s = socket_established();
+ // Data outside of receive window.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 256,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1);
+
+ // Challenge ACKs are rate-limited, we don't get a second one immediately.
+ send!(
+ s,
+ time 100,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 256,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+
+ // If we wait a bit, we do get a new one.
+ send!(
+ s,
+ time 2000,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 256,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1);
+ }
+
+ #[test]
+ fn test_established_fin() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::CloseWait);
+ sanity!(s, socket_close_wait());
+ }
+
+ #[test]
+ fn test_established_fin_after_missing() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"123456"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.state, State::Established);
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6 + 6),
+ window_len: 52,
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.state, State::Established);
+ }
+
+ #[test]
+ fn test_established_send_fin() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::CloseWait);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_rst() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_established_rst_no_ack() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_established_close() {
+ let mut s = socket_established();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ sanity!(s, socket_fin_wait_1());
+ }
+
+ #[test]
+ fn test_established_abort() {
+ let mut s = socket_established();
+ s.abort();
+ assert_eq!(s.state, State::Closed);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_established_rst_bad_seq() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ, // Wrong seq
+ ack_number: None,
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+
+ assert_eq!(s.state, State::Established);
+
+ // Send something to advance seq by 1
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1, // correct seq
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"a"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // Send wrong rst again, check that the challenge ack is correctly updated
+ // The ack number must be updated even if we don't call dispatch on the socket
+ // See https://github.com/smoltcp-rs/smoltcp/issues/338
+ send!(
+ s,
+ time 2000,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ, // Wrong seq
+ ack_number: None,
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 2), // this has changed
+ window_len: 63,
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ // =========================================================================================//
+ // Tests for the FIN-WAIT-1 state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_fin_wait_1_fin_ack() {
+ let mut s = socket_fin_wait_1();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait2);
+ sanity!(s, socket_fin_wait_2());
+ }
+
+ #[test]
+ fn test_fin_wait_1_fin_fin() {
+ let mut s = socket_fin_wait_1();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closing);
+ sanity!(s, socket_closing());
+ }
+
+ #[test]
+ fn test_fin_wait_1_fin_with_data_queued() {
+ let mut s = socket_established();
+ s.remote_win_len = 6;
+ s.send_slice(b"abcdef123456").unwrap();
+ s.close();
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ })
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait1);
+ }
+
+ #[test]
+ fn test_fin_wait_1_recv() {
+ let mut s = socket_fin_wait_1();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait1);
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_fin_wait_1_close() {
+ let mut s = socket_fin_wait_1();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ }
+
+ // =========================================================================================//
+ // Tests for the FIN-WAIT-2 state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_fin_wait_2_fin() {
+ let mut s = socket_fin_wait_2();
+ send!(s, time 1_000, TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ });
+ assert_eq!(s.state, State::TimeWait);
+ sanity!(s, socket_time_wait(false));
+ }
+
+ #[test]
+ fn test_fin_wait_2_recv() {
+ let mut s = socket_fin_wait_2();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait2);
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_fin_wait_2_close() {
+ let mut s = socket_fin_wait_2();
+ s.close();
+ assert_eq!(s.state, State::FinWait2);
+ }
+
+ // =========================================================================================//
+ // Tests for the CLOSING state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_closing_ack_fin() {
+ let mut s = socket_closing();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(s, time 1_000, TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ });
+ assert_eq!(s.state, State::TimeWait);
+ sanity!(s, socket_time_wait(true));
+ }
+
+ #[test]
+ fn test_closing_close() {
+ let mut s = socket_closing();
+ s.close();
+ assert_eq!(s.state, State::Closing);
+ }
+
+ // =========================================================================================//
+ // Tests for the TIME-WAIT state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_time_wait_from_fin_wait_2_ack() {
+ let mut s = socket_time_wait(false);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_time_wait_from_closing_no_ack() {
+ let mut s = socket_time_wait(true);
+ recv!(s, []);
+ }
+
+ #[test]
+ fn test_time_wait_close() {
+ let mut s = socket_time_wait(false);
+ s.close();
+ assert_eq!(s.state, State::TimeWait);
+ }
+
+ #[test]
+ fn test_time_wait_retransmit() {
+ let mut s = socket_time_wait(false);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(s, time 5_000, TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }, Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }));
+ assert_eq!(
+ s.timer,
+ Timer::Close {
+ expires_at: Instant::from_secs(5) + CLOSE_DELAY
+ }
+ );
+ }
+
+ #[test]
+ fn test_time_wait_timeout() {
+ let mut s = socket_time_wait(false);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv_nothing!(s, time 60_000);
+ assert_eq!(s.state, State::Closed);
+ }
+
+ // =========================================================================================//
+ // Tests for the CLOSE-WAIT state.
+ // =========================================================================================//
+
+ #[test]
+ fn test_close_wait_ack() {
+ let mut s = socket_close_wait();
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ ..SEND_TEMPL
+ }
+ );
+ }
+
+ #[test]
+ fn test_close_wait_close() {
+ let mut s = socket_close_wait();
+ s.close();
+ assert_eq!(s.state, State::LastAck);
+ sanity!(s, socket_last_ack());
+ }
+
+ // =========================================================================================//
+ // Tests for the LAST-ACK state.
+ // =========================================================================================//
+ #[test]
+ fn test_last_ack_fin_ack() {
+ let mut s = socket_last_ack();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::LastAck);
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_last_ack_ack_not_of_fin() {
+ let mut s = socket_last_ack();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::LastAck);
+
+ // ACK received that doesn't ack the FIN: socket should stay in LastAck.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::LastAck);
+
+ // ACK received of fin: socket should change to Closed.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_last_ack_close() {
+ let mut s = socket_last_ack();
+ s.close();
+ assert_eq!(s.state, State::LastAck);
+ }
+
+ // =========================================================================================//
+ // Tests for transitioning through multiple states.
+ // =========================================================================================//
+
+ #[test]
+ fn test_listen() {
+ let mut s = socket();
+ s.listen(LISTEN_END).unwrap();
+ assert_eq!(s.state, State::Listen);
+ }
+
+ #[test]
+ fn test_three_way_handshake() {
+ let mut s = socket_listen();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state(), State::SynReceived);
+ assert_eq!(s.tuple, Some(TUPLE));
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state(), State::Established);
+ assert_eq!(s.local_seq_no, LOCAL_SEQ + 1);
+ assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1);
+ }
+
+ #[test]
+ fn test_remote_close() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::CloseWait);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ s.close();
+ assert_eq!(s.state, State::LastAck);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_local_close() {
+ let mut s = socket_established();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait2);
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_simultaneous_close() {
+ let mut s = socket_established();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ recv!(
+ s,
+ [TcpRepr {
+ // due to reordering, this is logically located...
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closing);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ // ... at this point
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv!(s, []);
+ }
+
+ #[test]
+ fn test_simultaneous_close_combined_fin_ack() {
+ let mut s = socket_established();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_simultaneous_close_raced() {
+ let mut s = socket_established();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+
+ // Socket receives FIN before it has a chance to send its own FIN
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closing);
+
+ // FIN + ack-of-FIN
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::Closing);
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv!(s, []);
+ }
+
+ #[test]
+ fn test_simultaneous_close_raced_with_data() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+
+ // Socket receives FIN before it has a chance to send its own data+FIN
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closing);
+
+ // data + FIN + ack-of-FIN
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::Closing);
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ recv!(s, []);
+ }
+
+ #[test]
+ fn test_fin_with_data() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ s.close();
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ )
+ }
+
+ #[test]
+ fn test_mutual_close_with_data_1() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ }
+
+ #[test]
+ fn test_mutual_close_with_data_2() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ s.close();
+ assert_eq!(s.state, State::FinWait1);
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::FinWait2);
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 1),
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.state, State::TimeWait);
+ }
+
+ // =========================================================================================//
+ // Tests for retransmission on packet loss.
+ // =========================================================================================//
+
+ #[test]
+ fn test_duplicate_seq_ack() {
+ let mut s = socket_recved();
+ // remote retransmission
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_data_retransmit() {
+ let mut s = socket_established();
+ s.send_slice(b"abcdef").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ recv_nothing!(s, time 1050);
+ recv!(s, time 2000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_data_retransmit_bursts() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef012345").unwrap();
+
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv_nothing!(s, time 0);
+
+ recv_nothing!(s, time 50);
+
+ recv!(s, time 1000, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 1500, Ok(TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv_nothing!(s, time 1550);
+ }
+
+ #[test]
+ fn test_data_retransmit_bursts_half_ack() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef012345").unwrap();
+
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+ // Acknowledge the first packet
+ send!(s, time 5, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ // The second packet should be re-sent.
+ recv!(s, time 1500, Ok(TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+
+ recv_nothing!(s, time 1550);
+ }
+
+ #[test]
+ fn test_data_retransmit_bursts_half_ack_close() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef012345").unwrap();
+ s.close();
+
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+ // Acknowledge the first packet
+ send!(s, time 5, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ // The second packet should be re-sent.
+ recv!(s, time 1500, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"012345"[..],
+ ..RECV_TEMPL
+ }), exact);
+
+ recv_nothing!(s, time 1550);
+ }
+
+ #[test]
+ fn test_send_data_after_syn_ack_retransmit() {
+ let mut s = socket_syn_received();
+ recv!(s, time 50, Ok(TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 750, Ok(TcpRepr { // retransmit
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state(), State::Established);
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ )
+ }
+
+ #[test]
+ fn test_established_retransmit_for_dup_ack() {
+ let mut s = socket_established();
+ // Duplicate ACKs do not replace the retransmission timer
+ s.send_slice(b"abc").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+ // Retransmit timer is on because all data was sent
+ assert_eq!(s.tx_buffer.len(), 3);
+ // ACK nothing new
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // Retransmit
+ recv!(s, time 4000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_established_retransmit_reset_after_ack() {
+ let mut s = socket_established();
+ s.remote_win_len = 6;
+ s.send_slice(b"abcdef").unwrap();
+ s.send_slice(b"123456").unwrap();
+ s.send_slice(b"ABCDEF").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1005, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1015, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1020, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"ABCDEF"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_established_queue_during_retransmission() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef123456ABCDEF").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ })); // this one is dropped
+ recv!(s, time 1005, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ })); // this one is received
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"ABCDEF"[..],
+ ..RECV_TEMPL
+ })); // also dropped
+ recv!(s, time 2000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ })); // retransmission
+ send!(s, time 2005, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6),
+ ..SEND_TEMPL
+ }); // acknowledgement of both segments
+ recv!(s, time 2010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"ABCDEF"[..],
+ ..RECV_TEMPL
+ })); // retransmission of only unacknowledged data
+ }
+
+ #[test]
+ fn test_close_wait_retransmit_reset_after_ack() {
+ let mut s = socket_close_wait();
+ s.remote_win_len = 6;
+ s.send_slice(b"abcdef").unwrap();
+ s.send_slice(b"123456").unwrap();
+ s.send_slice(b"ABCDEF").unwrap();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1005, TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1015, TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1020, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ payload: &b"ABCDEF"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_fin_wait_1_retransmit_reset_after_ack() {
+ let mut s = socket_established();
+ s.remote_win_len = 6;
+ s.send_slice(b"abcdef").unwrap();
+ s.send_slice(b"123456").unwrap();
+ s.send_slice(b"ABCDEF").unwrap();
+ s.close();
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1005, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ }));
+ send!(s, time 1015, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6),
+ window_len: 6,
+ ..SEND_TEMPL
+ });
+ recv!(s, time 1020, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"ABCDEF"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_fast_retransmit_after_triple_duplicate_ack() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+
+ // Normal ACK of previously received segment
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ // Send a long string of text divided into several packets
+ // because of previously received "window_len"
+ s.send_slice(b"xxxxxxyyyyyywwwwwwzzzzzz").unwrap();
+ // This packet is lost
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"xxxxxx"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1005, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"yyyyyy"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 2),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"wwwwww"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1015, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 3),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"zzzzzz"[..],
+ ..RECV_TEMPL
+ }));
+
+ // First duplicate ACK
+ send!(s, time 1050, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ // Second duplicate ACK
+ send!(s, time 1055, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ // Third duplicate ACK
+ // Should trigger a fast retransmit of dropped packet
+ send!(s, time 1060, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ // Fast retransmit packet
+ recv!(s, time 1100, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"xxxxxx"[..],
+ ..RECV_TEMPL
+ }));
+
+ recv!(s, time 1105, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"yyyyyy"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1110, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 2),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"wwwwww"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1115, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 3),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"zzzzzz"[..],
+ ..RECV_TEMPL
+ }));
+
+ // After all was send out, enter *normal* retransmission,
+ // don't stay in fast retransmission.
+ assert!(match s.timer {
+ Timer::Retransmit { expires_at, .. } => expires_at > Instant::from_millis(1115),
+ _ => false,
+ });
+
+ // ACK all received segments
+ send!(s, time 1120, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + (6 * 4)),
+ ..SEND_TEMPL
+ });
+ }
+
+ #[test]
+ fn test_fast_retransmit_duplicate_detection_with_data() {
+ let mut s = socket_established();
+
+ s.send_slice(b"abc").unwrap(); // This is lost
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+
+ // Normal ACK of previously received segment
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // First duplicate
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // Second duplicate
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+
+ assert_eq!(s.local_rx_dup_acks, 2, "duplicate ACK counter is not set");
+
+ // This packet has content, hence should not be detected
+ // as a duplicate ACK and should reset the duplicate ACK count
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"xxxxxx"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 3,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ }]
+ );
+
+ assert_eq!(
+ s.local_rx_dup_acks, 0,
+ "duplicate ACK counter is not reset when receiving data"
+ );
+ }
+
+ #[test]
+ fn test_fast_retransmit_duplicate_detection_with_window_update() {
+ let mut s = socket_established();
+
+ s.send_slice(b"abc").unwrap(); // This is lost
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+
+ // Normal ACK of previously received segment
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // First duplicate
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // Second duplicate
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+
+ assert_eq!(s.local_rx_dup_acks, 2, "duplicate ACK counter is not set");
+
+ // This packet has a window update, hence should not be detected
+ // as a duplicate ACK and should reset the duplicate ACK count
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_len: 400,
+ ..SEND_TEMPL
+ }
+ );
+
+ assert_eq!(
+ s.local_rx_dup_acks, 0,
+ "duplicate ACK counter is not reset when receiving a window update"
+ );
+ }
+
+ #[test]
+ fn test_fast_retransmit_duplicate_detection() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+
+ // Normal ACK of previously received segment
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ // First duplicate, should not be counted as there is nothing to resend
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ assert_eq!(
+ s.local_rx_dup_acks, 0,
+ "duplicate ACK counter is set but wound not transmit data"
+ );
+
+ // Send a long string of text divided into several packets
+ // because of small remote_mss
+ s.send_slice(b"xxxxxxyyyyyywwwwwwzzzzzz").unwrap();
+
+ // This packet is reordered in network
+ recv!(s, time 1000, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"xxxxxx"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1005, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"yyyyyy"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1010, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 2),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"wwwwww"[..],
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1015, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + (6 * 3),
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"zzzzzz"[..],
+ ..RECV_TEMPL
+ }));
+
+ // First duplicate ACK
+ send!(s, time 1050, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ // Second duplicate ACK
+ send!(s, time 1055, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ // Reordered packet arrives which should reset duplicate ACK count
+ send!(s, time 1060, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + (6 * 3)),
+ ..SEND_TEMPL
+ });
+
+ assert_eq!(
+ s.local_rx_dup_acks, 0,
+ "duplicate ACK counter is not reset when receiving ACK which updates send window"
+ );
+
+ // ACK all received segments
+ send!(s, time 1120, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + (6 * 4)),
+ ..SEND_TEMPL
+ });
+ }
+
+ #[test]
+ fn test_fast_retransmit_dup_acks_counter() {
+ let mut s = socket_established();
+
+ s.send_slice(b"abc").unwrap(); // This is lost
+ recv!(s, time 0, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ // A lot of retransmits happen here
+ s.local_rx_dup_acks = u8::max_value() - 1;
+
+ // Send 3 more ACKs, which could overflow local_rx_dup_acks,
+ // but intended behaviour is that we saturate the bounds
+ // of local_rx_dup_acks
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ send!(s, time 0, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ assert_eq!(
+ s.local_rx_dup_acks,
+ u8::max_value(),
+ "duplicate ACK count should not overflow but saturate"
+ );
+ }
+
+ #[test]
+ fn test_fast_retransmit_zero_window() {
+ let mut s = socket_established();
+
+ send!(s, time 1000, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+
+ s.send_slice(b"abc").unwrap();
+
+ recv!(s, time 0, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abc"[..],
+ ..RECV_TEMPL
+ }));
+
+ // 3 dup acks
+ send!(s, time 1050, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ send!(s, time 1050, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ send!(s, time 1050, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_len: 0, // boom
+ ..SEND_TEMPL
+ });
+
+ // even though we're in "fast retransmit", we shouldn't
+ // force-send anything because the remote's window is full.
+ recv_nothing!(s);
+ }
+
+ // =========================================================================================//
+ // Tests for window management.
+ // =========================================================================================//
+
+ #[test]
+ fn test_maximum_segment_size() {
+ let mut s = socket_listen();
+ s.tx_buffer = SocketBuffer::new(vec![0; 32767]);
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: REMOTE_SEQ,
+ ack_number: None,
+ max_seg_size: Some(1000),
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ max_seg_size: Some(BASE_MSS),
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ window_len: 32767,
+ ..SEND_TEMPL
+ }
+ );
+ s.send_slice(&[0; 1200][..]).unwrap();
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0; 1000][..],
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_close_wait_no_window_update() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &[1, 2, 3, 4],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::CloseWait);
+
+ // we ack the FIN, with the reduced window size.
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 6),
+ window_len: 60,
+ ..RECV_TEMPL
+ })
+ );
+
+ let rx_buf = &mut [0; 32];
+ assert_eq!(s.recv_slice(rx_buf), Ok(4));
+
+ // check that we do NOT send a window update even if it has changed.
+ recv_nothing!(s);
+ }
+
+ #[test]
+ fn test_time_wait_no_window_update() {
+ let mut s = socket_fin_wait_2();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 2),
+ payload: &[1, 2, 3, 4],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+
+ // we ack the FIN, with the reduced window size.
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 2,
+ ack_number: Some(REMOTE_SEQ + 6),
+ window_len: 60,
+ ..RECV_TEMPL
+ })
+ );
+
+ let rx_buf = &mut [0; 32];
+ assert_eq!(s.recv_slice(rx_buf), Ok(4));
+
+ // check that we do NOT send a window update even if it has changed.
+ recv_nothing!(s);
+ }
+
+ // =========================================================================================//
+ // Tests for flow control.
+ // =========================================================================================//
+
+ #[test]
+ fn test_psh_transmit() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef").unwrap();
+ s.send_slice(b"123456").unwrap();
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ }), exact);
+ }
+
+ #[test]
+ fn test_psh_receive() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Psh,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_zero_window_ack() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 0,
+ ..RECV_TEMPL
+ }]
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"123456"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_zero_window_fin() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+ s.ack_delay = None;
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 0,
+ ..RECV_TEMPL
+ }]
+ );
+
+ // Even though the sequence space for the FIN itself is outside the window,
+ // it is not data, so FIN must be accepted when window full.
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &[],
+ control: TcpControl::Fin,
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::CloseWait);
+
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 7),
+ window_len: 0,
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_zero_window_ack_on_window_growth() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 0,
+ ..RECV_TEMPL
+ }]
+ );
+ recv_nothing!(s, time 0);
+ s.recv(|buffer| {
+ assert_eq!(&buffer[..3], b"abc");
+ (3, ())
+ })
+ .unwrap();
+ recv!(s, time 0, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 3,
+ ..RECV_TEMPL
+ }));
+ recv_nothing!(s, time 0);
+ s.recv(|buffer| {
+ assert_eq!(buffer, b"def");
+ (buffer.len(), ())
+ })
+ .unwrap();
+ recv!(s, time 0, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 6,
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_fill_peer_window() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef123456!@#$%^").unwrap();
+ recv!(
+ s,
+ [
+ TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ },
+ TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"123456"[..],
+ ..RECV_TEMPL
+ },
+ TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"!@#$%^"[..],
+ ..RECV_TEMPL
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_announce_window_after_read() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 3,
+ ..RECV_TEMPL
+ }]
+ );
+ // Test that `dispatch` updates `remote_last_win`
+ assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16);
+ s.recv(|buffer| (buffer.len(), ())).unwrap();
+ assert!(s.window_to_update());
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 6,
+ ..RECV_TEMPL
+ }]
+ );
+ assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16);
+ // Provoke immediate ACK to test that `process` updates `remote_last_win`
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"def"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 6,
+ ..RECV_TEMPL
+ })
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 9),
+ window_len: 0,
+ ..RECV_TEMPL
+ })
+ );
+ assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16);
+ s.recv(|buffer| (buffer.len(), ())).unwrap();
+ assert!(s.window_to_update());
+ }
+
+ // =========================================================================================//
+ // Tests for timeouts.
+ // =========================================================================================//
+
+ #[test]
+ fn test_listen_timeout() {
+ let mut s = socket_listen();
+ s.set_timeout(Some(Duration::from_millis(100)));
+ assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Ingress);
+ }
+
+ #[test]
+ fn test_connect_timeout() {
+ let mut s = socket();
+ s.local_seq_no = LOCAL_SEQ;
+ s.socket
+ .connect(&mut s.cx, REMOTE_END, LOCAL_END.port)
+ .unwrap();
+ s.set_timeout(Some(Duration::from_millis(100)));
+ recv!(s, time 150, Ok(TcpRepr {
+ control: TcpControl::Syn,
+ seq_number: LOCAL_SEQ,
+ ack_number: None,
+ max_seg_size: Some(BASE_MSS),
+ window_scale: Some(0),
+ sack_permitted: true,
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.state, State::SynSent);
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(250))
+ );
+ recv!(s, time 250, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(TcpSeqNumber(0)),
+ window_scale: None,
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_established_timeout() {
+ let mut s = socket_established();
+ s.set_timeout(Some(Duration::from_millis(1000)));
+ recv_nothing!(s, time 250);
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(1250))
+ );
+ s.send_slice(b"abcdef").unwrap();
+ assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now);
+ recv!(s, time 255, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(955))
+ );
+ recv!(s, time 955, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }));
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(1255))
+ );
+ recv!(s, time 1255, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_established_keep_alive_timeout() {
+ let mut s = socket_established();
+ s.set_keep_alive(Some(Duration::from_millis(50)));
+ s.set_timeout(Some(Duration::from_millis(100)));
+ recv!(s, time 100, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0],
+ ..RECV_TEMPL
+ }));
+ recv_nothing!(s, time 100);
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(150))
+ );
+ send!(s, time 105, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(155))
+ );
+ recv!(s, time 155, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0],
+ ..RECV_TEMPL
+ }));
+ recv_nothing!(s, time 155);
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(205))
+ );
+ recv_nothing!(s, time 200);
+ recv!(s, time 205, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }));
+ recv_nothing!(s, time 205);
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_fin_wait_1_timeout() {
+ let mut s = socket_fin_wait_1();
+ s.set_timeout(Some(Duration::from_millis(1000)));
+ recv!(s, time 100, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1100, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_last_ack_timeout() {
+ let mut s = socket_last_ack();
+ s.set_timeout(Some(Duration::from_millis(1000)));
+ recv!(s, time 100, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }));
+ recv!(s, time 1100, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1 + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 1),
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.state, State::Closed);
+ }
+
+ #[test]
+ fn test_closed_timeout() {
+ let mut s = socket_established();
+ s.set_timeout(Some(Duration::from_millis(200)));
+ s.remote_last_ts = Some(Instant::from_millis(100));
+ s.abort();
+ assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now);
+ recv!(s, time 100, Ok(TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ }));
+ assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Ingress);
+ }
+
+ // =========================================================================================//
+ // Tests for keep-alive.
+ // =========================================================================================//
+
+ #[test]
+ fn test_responds_to_keep_alive() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_sends_keep_alive() {
+ let mut s = socket_established();
+ s.set_keep_alive(Some(Duration::from_millis(100)));
+
+ // drain the forced keep-alive packet
+ assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now);
+ recv!(s, time 0, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0],
+ ..RECV_TEMPL
+ }));
+
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(100))
+ );
+ recv_nothing!(s, time 95);
+ recv!(s, time 100, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0],
+ ..RECV_TEMPL
+ }));
+
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(200))
+ );
+ recv_nothing!(s, time 195);
+ recv!(s, time 200, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &[0],
+ ..RECV_TEMPL
+ }));
+
+ send!(s, time 250, TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ });
+ assert_eq!(
+ s.socket.poll_at(&mut s.cx),
+ PollAt::Time(Instant::from_millis(350))
+ );
+ recv_nothing!(s, time 345);
+ recv!(s, time 350, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"\x00"[..],
+ ..RECV_TEMPL
+ }));
+ }
+
+ // =========================================================================================//
+ // Tests for time-to-live configuration.
+ // =========================================================================================//
+
+ #[test]
+ fn test_set_hop_limit() {
+ let mut s = socket_syn_received();
+
+ s.set_hop_limit(Some(0x2a));
+ assert_eq!(
+ s.socket.dispatch(&mut s.cx, |_, (ip_repr, _)| {
+ assert_eq!(ip_repr.hop_limit(), 0x2a);
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+
+ // assert that user-configurable settings are kept,
+ // see https://github.com/smoltcp-rs/smoltcp/issues/601.
+ s.reset();
+ assert_eq!(s.hop_limit(), Some(0x2a));
+ }
+
+ #[test]
+ #[should_panic(expected = "the time-to-live value of a packet must not be zero")]
+ fn test_set_hop_limit_zero() {
+ let mut s = socket_syn_received();
+ s.set_hop_limit(Some(0));
+ }
+
+ // =========================================================================================//
+ // Tests for reassembly.
+ // =========================================================================================//
+
+ #[test]
+ fn test_out_of_order() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"def"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ ..RECV_TEMPL
+ })
+ );
+ s.recv(|buffer| {
+ assert_eq!(buffer, b"");
+ (buffer.len(), ())
+ })
+ .unwrap();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ })
+ );
+ s.recv(|buffer| {
+ assert_eq!(buffer, b"abcdef");
+ (buffer.len(), ())
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_buffer_wraparound_rx() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ s.recv(|buffer| {
+ assert_eq!(buffer, b"abc");
+ (buffer.len(), ())
+ })
+ .unwrap();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"defghi"[..],
+ ..SEND_TEMPL
+ }
+ );
+ let mut data = [0; 6];
+ assert_eq!(s.recv_slice(&mut data[..]), Ok(6));
+ assert_eq!(data, &b"defghi"[..]);
+ }
+
+ #[test]
+ fn test_buffer_wraparound_tx() {
+ let mut s = socket_established();
+ s.set_nagle_enabled(false);
+
+ s.tx_buffer = SocketBuffer::new(vec![b'.'; 9]);
+ assert_eq!(s.send_slice(b"xxxyyy"), Ok(6));
+ assert_eq!(s.tx_buffer.dequeue_many(3), &b"xxx"[..]);
+ assert_eq!(s.tx_buffer.len(), 3);
+
+ // "abcdef" not contiguous in tx buffer
+ assert_eq!(s.send_slice(b"abcdef"), Ok(6));
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"yyyabc"[..],
+ ..RECV_TEMPL
+ })
+ );
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"def"[..],
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ // =========================================================================================//
+ // Tests for graceful vs ungraceful rx close
+ // =========================================================================================//
+
+ #[test]
+ fn test_rx_close_fin() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished));
+ }
+
+ #[test]
+ fn test_rx_close_fin_in_fin_wait_1() {
+ let mut s = socket_fin_wait_1();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::Closing);
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished));
+ }
+
+ #[test]
+ fn test_rx_close_fin_in_fin_wait_2() {
+ let mut s = socket_fin_wait_2();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ assert_eq!(s.state, State::TimeWait);
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished));
+ }
+
+ #[test]
+ fn test_rx_close_fin_with_hole() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"ghi"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 61,
+ ..RECV_TEMPL
+ })
+ );
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ s.recv(|data| {
+ assert_eq!(data, b"");
+ (0, ())
+ })
+ .unwrap();
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1 + 9,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ // Error must be `Illegal` even if we've received a FIN,
+ // because we are missing data.
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState));
+ }
+
+ #[test]
+ fn test_rx_close_rst() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState));
+ }
+
+ #[test]
+ fn test_rx_close_rst_with_hole() {
+ let mut s = socket_established();
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"ghi"[..],
+ ..SEND_TEMPL
+ },
+ Some(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 61,
+ ..RECV_TEMPL
+ })
+ );
+ send!(
+ s,
+ TcpRepr {
+ control: TcpControl::Rst,
+ seq_number: REMOTE_SEQ + 1 + 9,
+ ack_number: Some(LOCAL_SEQ + 1),
+ ..SEND_TEMPL
+ }
+ );
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+ assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState));
+ }
+
+ // =========================================================================================//
+ // Tests for delayed ACK
+ // =========================================================================================//
+
+ #[test]
+ fn test_delayed_ack() {
+ let mut s = socket_established();
+ s.set_ack_delay(Some(ACK_DELAY_DEFAULT));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // No ACK is immediately sent.
+ recv_nothing!(s);
+
+ // After 10ms, it is sent.
+ recv!(s, time 11, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ window_len: 61,
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_delayed_ack_win() {
+ let mut s = socket_established();
+ s.set_ack_delay(Some(ACK_DELAY_DEFAULT));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // Reading the data off the buffer should cause a window update.
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+
+ // However, no ACK or window update is immediately sent.
+ recv_nothing!(s);
+
+ // After 10ms, it is sent.
+ recv!(s, time 11, Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ ..RECV_TEMPL
+ }));
+ }
+
+ #[test]
+ fn test_delayed_ack_reply() {
+ let mut s = socket_established();
+ s.set_ack_delay(Some(ACK_DELAY_DEFAULT));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ s.recv(|data| {
+ assert_eq!(data, b"abc");
+ (3, ())
+ })
+ .unwrap();
+
+ s.send_slice(&b"xyz"[..]).unwrap();
+
+ // Writing data to the socket causes ACK to not be delayed,
+ // because it is immediately sent with the data.
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 3),
+ payload: &b"xyz"[..],
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_delayed_ack_every_second_packet() {
+ let mut s = socket_established();
+ s.set_ack_delay(Some(ACK_DELAY_DEFAULT));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // No ACK is immediately sent.
+ recv_nothing!(s);
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"def"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // Every 2nd packet, ACK is sent without delay.
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 6),
+ window_len: 58,
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ #[test]
+ fn test_delayed_ack_three_packets() {
+ let mut s = socket_established();
+ s.set_ack_delay(Some(ACK_DELAY_DEFAULT));
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abc"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // No ACK is immediately sent.
+ recv_nothing!(s);
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 3,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"def"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1 + 6,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"ghi"[..],
+ ..SEND_TEMPL
+ }
+ );
+
+ // Every 2nd (or more) packet, ACK is sent without delay.
+ recv!(
+ s,
+ Ok(TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1 + 9),
+ window_len: 55,
+ ..RECV_TEMPL
+ })
+ );
+ }
+
+ // =========================================================================================//
+ // Tests for Nagle's Algorithm
+ // =========================================================================================//
+
+ #[test]
+ fn test_nagle() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+
+ s.send_slice(b"abcdef").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }]
+ );
+
+ // If there's data in flight, full segments get sent.
+ s.send_slice(b"foobar").unwrap();
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"foobar"[..],
+ ..RECV_TEMPL
+ }]
+ );
+
+ s.send_slice(b"aaabbbccc").unwrap();
+ // If there's data in flight, not-full segments don't get sent.
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"aaabbb"[..],
+ ..RECV_TEMPL
+ }]
+ );
+
+ // Data gets ACKd, so there's no longer data in flight
+ send!(
+ s,
+ TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1 + 6 + 6 + 6),
+ ..SEND_TEMPL
+ }
+ );
+
+ // Now non-full segment gets sent.
+ recv!(
+ s,
+ [TcpRepr {
+ seq_number: LOCAL_SEQ + 1 + 6 + 6 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"ccc"[..],
+ ..RECV_TEMPL
+ }]
+ );
+ }
+
+ #[test]
+ fn test_final_packet_in_stream_doesnt_wait_for_nagle() {
+ let mut s = socket_established();
+ s.remote_mss = 6;
+ s.send_slice(b"abcdef0").unwrap();
+ s.socket.close();
+
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::None,
+ seq_number: LOCAL_SEQ + 1,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..RECV_TEMPL
+ }), exact);
+ recv!(s, time 0, Ok(TcpRepr {
+ control: TcpControl::Fin,
+ seq_number: LOCAL_SEQ + 1 + 6,
+ ack_number: Some(REMOTE_SEQ + 1),
+ payload: &b"0"[..],
+ ..RECV_TEMPL
+ }), exact);
+ }
+
+ // =========================================================================================//
+ // Tests for packet filtering.
+ // =========================================================================================//
+
+ #[test]
+ fn test_doesnt_accept_wrong_port() {
+ let mut s = socket_established();
+ s.rx_buffer = SocketBuffer::new(vec![0; 6]);
+ s.assembler = Assembler::new();
+
+ let tcp_repr = TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ dst_port: LOCAL_PORT + 1,
+ ..SEND_TEMPL
+ };
+ assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr));
+
+ let tcp_repr = TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ src_port: REMOTE_PORT + 1,
+ ..SEND_TEMPL
+ };
+ assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr));
+ }
+
+ #[test]
+ fn test_doesnt_accept_wrong_ip() {
+ let mut s = socket_established();
+
+ let tcp_repr = TcpRepr {
+ seq_number: REMOTE_SEQ + 1,
+ ack_number: Some(LOCAL_SEQ + 1),
+ payload: &b"abcdef"[..],
+ ..SEND_TEMPL
+ };
+
+ let ip_repr = IpReprIpvX(IpvXRepr {
+ src_addr: REMOTE_ADDR,
+ dst_addr: LOCAL_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp_repr.buffer_len(),
+ hop_limit: 64,
+ });
+ assert!(s.socket.accepts(&mut s.cx, &ip_repr, &tcp_repr));
+
+ let ip_repr_wrong_src = IpReprIpvX(IpvXRepr {
+ src_addr: OTHER_ADDR,
+ dst_addr: LOCAL_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp_repr.buffer_len(),
+ hop_limit: 64,
+ });
+ assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_src, &tcp_repr));
+
+ let ip_repr_wrong_dst = IpReprIpvX(IpvXRepr {
+ src_addr: REMOTE_ADDR,
+ dst_addr: OTHER_ADDR,
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp_repr.buffer_len(),
+ hop_limit: 64,
+ });
+ assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_dst, &tcp_repr));
+ }
+
+ // =========================================================================================//
+ // Timer tests
+ // =========================================================================================//
+
+ #[test]
+ fn test_timer_retransmit() {
+ const RTO: Duration = Duration::from_millis(100);
+ let mut r = Timer::new();
+ assert_eq!(r.should_retransmit(Instant::from_secs(1)), None);
+ r.set_for_retransmit(Instant::from_millis(1000), RTO);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1000)), None);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1050)), None);
+ assert_eq!(
+ r.should_retransmit(Instant::from_millis(1101)),
+ Some(Duration::from_millis(101))
+ );
+ r.set_for_retransmit(Instant::from_millis(1101), RTO);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1101)), None);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1150)), None);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1200)), None);
+ assert_eq!(
+ r.should_retransmit(Instant::from_millis(1301)),
+ Some(Duration::from_millis(300))
+ );
+ r.set_for_idle(Instant::from_millis(1301), None);
+ assert_eq!(r.should_retransmit(Instant::from_millis(1350)), None);
+ }
+
+ #[test]
+ fn test_rtt_estimator() {
+ let mut r = RttEstimator::default();
+
+ let rtos = &[
+ 751, 766, 755, 731, 697, 656, 613, 567, 523, 484, 445, 411, 378, 350, 322, 299, 280,
+ 261, 243, 229, 215, 206, 197, 188,
+ ];
+
+ for &rto in rtos {
+ r.sample(100);
+ assert_eq!(r.retransmission_timeout(), Duration::from_millis(rto));
+ }
+ }
+}
diff --git a/src/socket/udp.rs b/src/socket/udp.rs
new file mode 100644
index 0000000..82eebf2
--- /dev/null
+++ b/src/socket/udp.rs
@@ -0,0 +1,1030 @@
+use core::cmp::min;
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+use crate::iface::Context;
+use crate::phy::PacketMeta;
+use crate::socket::PollAt;
+#[cfg(feature = "async")]
+use crate::socket::WakerRegistration;
+use crate::storage::Empty;
+use crate::wire::{IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, UdpRepr};
+
+/// Metadata for a sent or received UDP packet.
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct UdpMetadata {
+ pub endpoint: IpEndpoint,
+ pub meta: PacketMeta,
+}
+
+impl<T: Into<IpEndpoint>> From<T> for UdpMetadata {
+ fn from(value: T) -> Self {
+ Self {
+ endpoint: value.into(),
+ meta: PacketMeta::default(),
+ }
+ }
+}
+
+impl core::fmt::Display for UdpMetadata {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ #[cfg(feature = "packetmeta-id")]
+ return write!(f, "{}, PacketID: {:?}", self.endpoint, self.meta);
+
+ #[cfg(not(feature = "packetmeta-id"))]
+ write!(f, "{}", self.endpoint)
+ }
+}
+
+/// A UDP packet metadata.
+pub type PacketMetadata = crate::storage::PacketMetadata<UdpMetadata>;
+
+/// A UDP packet ring buffer.
+pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, UdpMetadata>;
+
+/// Error returned by [`Socket::bind`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum BindError {
+ InvalidState,
+ Unaddressable,
+}
+
+impl core::fmt::Display for BindError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ BindError::InvalidState => write!(f, "invalid state"),
+ BindError::Unaddressable => write!(f, "unaddressable"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BindError {}
+
+/// Error returned by [`Socket::send`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SendError {
+ Unaddressable,
+ BufferFull,
+}
+
+impl core::fmt::Display for SendError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ SendError::Unaddressable => write!(f, "unaddressable"),
+ SendError::BufferFull => write!(f, "buffer full"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for SendError {}
+
+/// Error returned by [`Socket::recv`]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecvError {
+ Exhausted,
+ Truncated,
+}
+
+impl core::fmt::Display for RecvError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ RecvError::Exhausted => write!(f, "exhausted"),
+ RecvError::Truncated => write!(f, "truncated"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RecvError {}
+
+/// A User Datagram Protocol socket.
+///
+/// A UDP socket is bound to a specific endpoint, and owns transmit and receive
+/// packet buffers.
+#[derive(Debug)]
+pub struct Socket<'a> {
+ endpoint: IpListenEndpoint,
+ rx_buffer: PacketBuffer<'a>,
+ tx_buffer: PacketBuffer<'a>,
+ /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ hop_limit: Option<u8>,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration,
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration,
+}
+
+impl<'a> Socket<'a> {
+ /// Create an UDP socket with the given buffers.
+ pub fn new(rx_buffer: PacketBuffer<'a>, tx_buffer: PacketBuffer<'a>) -> Socket<'a> {
+ Socket {
+ endpoint: IpListenEndpoint::default(),
+ rx_buffer,
+ tx_buffer,
+ hop_limit: None,
+ #[cfg(feature = "async")]
+ rx_waker: WakerRegistration::new(),
+ #[cfg(feature = "async")]
+ tx_waker: WakerRegistration::new(),
+ }
+ }
+
+ /// Register a waker for receive operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `recv` method calls, such as receiving data, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_recv_waker(&mut self, waker: &Waker) {
+ self.rx_waker.register(waker)
+ }
+
+ /// Register a waker for send operations.
+ ///
+ /// The waker is woken on state changes that might affect the return value
+ /// of `send` method calls, such as space becoming available in the transmit
+ /// buffer, or the socket closing.
+ ///
+ /// Notes:
+ ///
+ /// - Only one waker can be registered at a time. If another waker was previously registered,
+ /// it is overwritten and will no longer be woken.
+ /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
+ /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
+ /// necessarily changed.
+ #[cfg(feature = "async")]
+ pub fn register_send_waker(&mut self, waker: &Waker) {
+ self.tx_waker.register(waker)
+ }
+
+ /// Return the bound endpoint.
+ #[inline]
+ pub fn endpoint(&self) -> IpListenEndpoint {
+ self.endpoint
+ }
+
+ /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// See also the [set_hop_limit](#method.set_hop_limit) method
+ pub fn hop_limit(&self) -> Option<u8> {
+ self.hop_limit
+ }
+
+ /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+ ///
+ /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
+ /// value (64).
+ ///
+ /// # Panics
+ ///
+ /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
+ ///
+ /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
+ /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
+ pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
+ // A host MUST NOT send a datagram with a hop limit value of 0
+ if let Some(0) = hop_limit {
+ panic!("the time-to-live value of a packet must not be zero")
+ }
+
+ self.hop_limit = hop_limit
+ }
+
+ /// Bind the socket to the given endpoint.
+ ///
+ /// This function returns `Err(Error::Illegal)` if the socket was open
+ /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)`
+ /// if the port in the given endpoint is zero.
+ pub fn bind<T: Into<IpListenEndpoint>>(&mut self, endpoint: T) -> Result<(), BindError> {
+ let endpoint = endpoint.into();
+ if endpoint.port == 0 {
+ return Err(BindError::Unaddressable);
+ }
+
+ if self.is_open() {
+ return Err(BindError::InvalidState);
+ }
+
+ self.endpoint = endpoint;
+
+ #[cfg(feature = "async")]
+ {
+ self.rx_waker.wake();
+ self.tx_waker.wake();
+ }
+
+ Ok(())
+ }
+
+ /// Close the socket.
+ pub fn close(&mut self) {
+ // Clear the bound endpoint of the socket.
+ self.endpoint = IpListenEndpoint::default();
+
+ // Reset the RX and TX buffers of the socket.
+ self.tx_buffer.reset();
+ self.rx_buffer.reset();
+
+ #[cfg(feature = "async")]
+ {
+ self.rx_waker.wake();
+ self.tx_waker.wake();
+ }
+ }
+
+ /// Check whether the socket is open.
+ #[inline]
+ pub fn is_open(&self) -> bool {
+ self.endpoint.port != 0
+ }
+
+ /// Check whether the transmit buffer is full.
+ #[inline]
+ pub fn can_send(&self) -> bool {
+ !self.tx_buffer.is_full()
+ }
+
+ /// Check whether the receive buffer is not empty.
+ #[inline]
+ pub fn can_recv(&self) -> bool {
+ !self.rx_buffer.is_empty()
+ }
+
+ /// Return the maximum number packets the socket can receive.
+ #[inline]
+ pub fn packet_recv_capacity(&self) -> usize {
+ self.rx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number packets the socket can transmit.
+ #[inline]
+ pub fn packet_send_capacity(&self) -> usize {
+ self.tx_buffer.packet_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the recv buffer.
+ #[inline]
+ pub fn payload_recv_capacity(&self) -> usize {
+ self.rx_buffer.payload_capacity()
+ }
+
+ /// Return the maximum number of bytes inside the transmit buffer.
+ #[inline]
+ pub fn payload_send_capacity(&self) -> usize {
+ self.tx_buffer.payload_capacity()
+ }
+
+ /// Enqueue a packet to be sent to a given remote endpoint, and return a pointer
+ /// to its payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full,
+ /// `Err(Error::Unaddressable)` if local or remote port, or remote address are unspecified,
+ /// and `Err(Error::Truncated)` if there is not enough transmit buffer capacity
+ /// to ever send this packet.
+ pub fn send(
+ &mut self,
+ size: usize,
+ meta: impl Into<UdpMetadata>,
+ ) -> Result<&mut [u8], SendError> {
+ let meta = meta.into();
+ if self.endpoint.port == 0 {
+ return Err(SendError::Unaddressable);
+ }
+ if meta.endpoint.addr.is_unspecified() {
+ return Err(SendError::Unaddressable);
+ }
+ if meta.endpoint.port == 0 {
+ return Err(SendError::Unaddressable);
+ }
+
+ let payload_buf = self
+ .tx_buffer
+ .enqueue(size, meta)
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!(
+ "udp:{}:{}: buffer to send {} octets",
+ self.endpoint,
+ meta.endpoint,
+ size
+ );
+ Ok(payload_buf)
+ }
+
+ /// Enqueue a packet to be send to a given remote endpoint and pass the buffer
+ /// to the provided closure. The closure then returns the size of the data written
+ /// into the buffer.
+ ///
+ /// Also see [send](#method.send).
+ pub fn send_with<F>(
+ &mut self,
+ max_size: usize,
+ meta: impl Into<UdpMetadata>,
+ f: F,
+ ) -> Result<usize, SendError>
+ where
+ F: FnOnce(&mut [u8]) -> usize,
+ {
+ let meta = meta.into();
+ if self.endpoint.port == 0 {
+ return Err(SendError::Unaddressable);
+ }
+ if meta.endpoint.addr.is_unspecified() {
+ return Err(SendError::Unaddressable);
+ }
+ if meta.endpoint.port == 0 {
+ return Err(SendError::Unaddressable);
+ }
+
+ let size = self
+ .tx_buffer
+ .enqueue_with_infallible(max_size, meta, f)
+ .map_err(|_| SendError::BufferFull)?;
+
+ net_trace!(
+ "udp:{}:{}: buffer to send {} octets",
+ self.endpoint,
+ meta.endpoint,
+ size
+ );
+ Ok(size)
+ }
+
+ /// Enqueue a packet to be sent to a given remote endpoint, and fill it from a slice.
+ ///
+ /// See also [send](#method.send).
+ pub fn send_slice(
+ &mut self,
+ data: &[u8],
+ meta: impl Into<UdpMetadata>,
+ ) -> Result<(), SendError> {
+ self.send(data.len(), meta)?.copy_from_slice(data);
+ Ok(())
+ }
+
+ /// Dequeue a packet received from a remote endpoint, and return the endpoint as well
+ /// as a pointer to the payload.
+ ///
+ /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty.
+ pub fn recv(&mut self) -> Result<(&[u8], UdpMetadata), RecvError> {
+ let (remote_endpoint, payload_buf) =
+ self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?;
+
+ net_trace!(
+ "udp:{}:{}: receive {} buffered octets",
+ self.endpoint,
+ remote_endpoint.endpoint,
+ payload_buf.len()
+ );
+ Ok((payload_buf, remote_endpoint))
+ }
+
+ /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice,
+ /// and return the amount of octets copied as well as the endpoint.
+ ///
+ /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
+ /// the packet is dropped and a `RecvError::Truncated` error is returned.
+ ///
+ /// See also [recv](#method.recv).
+ pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<(usize, UdpMetadata), RecvError> {
+ let (buffer, endpoint) = self.recv().map_err(|_| RecvError::Exhausted)?;
+
+ if data.len() < buffer.len() {
+ return Err(RecvError::Truncated);
+ }
+
+ let length = min(data.len(), buffer.len());
+ data[..length].copy_from_slice(&buffer[..length]);
+ Ok((length, endpoint))
+ }
+
+ /// Peek at a packet received from a remote endpoint, and return the endpoint as well
+ /// as a pointer to the payload without removing the packet from the receive buffer.
+ /// This function otherwise behaves identically to [recv](#method.recv).
+ ///
+ /// It returns `Err(Error::Exhausted)` if the receive buffer is empty.
+ pub fn peek(&mut self) -> Result<(&[u8], &UdpMetadata), RecvError> {
+ let endpoint = self.endpoint;
+ self.rx_buffer.peek().map_err(|_| RecvError::Exhausted).map(
+ |(remote_endpoint, payload_buf)| {
+ net_trace!(
+ "udp:{}:{}: peek {} buffered octets",
+ endpoint,
+ remote_endpoint.endpoint,
+ payload_buf.len()
+ );
+ (payload_buf, remote_endpoint)
+ },
+ )
+ }
+
+ /// Peek at a packet received from a remote endpoint, copy the payload into the given slice,
+ /// and return the amount of octets copied as well as the endpoint without removing the
+ /// packet from the receive buffer.
+ /// This function otherwise behaves identically to [recv_slice](#method.recv_slice).
+ ///
+ /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
+ /// no data is copied into the provided buffer and a `RecvError::Truncated` error is returned.
+ ///
+ /// See also [peek](#method.peek).
+ pub fn peek_slice(&mut self, data: &mut [u8]) -> Result<(usize, &UdpMetadata), RecvError> {
+ let (buffer, endpoint) = self.peek()?;
+
+ if data.len() < buffer.len() {
+ return Err(RecvError::Truncated);
+ }
+
+ let length = min(data.len(), buffer.len());
+ data[..length].copy_from_slice(&buffer[..length]);
+ Ok((length, endpoint))
+ }
+
+ pub(crate) fn accepts(&self, cx: &mut Context, ip_repr: &IpRepr, repr: &UdpRepr) -> bool {
+ if self.endpoint.port != repr.dst_port {
+ return false;
+ }
+ if self.endpoint.addr.is_some()
+ && self.endpoint.addr != Some(ip_repr.dst_addr())
+ && !cx.is_broadcast(&ip_repr.dst_addr())
+ && !ip_repr.dst_addr().is_multicast()
+ {
+ return false;
+ }
+
+ true
+ }
+
+ pub(crate) fn process(
+ &mut self,
+ cx: &mut Context,
+ meta: PacketMeta,
+ ip_repr: &IpRepr,
+ repr: &UdpRepr,
+ payload: &[u8],
+ ) {
+ debug_assert!(self.accepts(cx, ip_repr, repr));
+
+ let size = payload.len();
+
+ let remote_endpoint = IpEndpoint {
+ addr: ip_repr.src_addr(),
+ port: repr.src_port,
+ };
+
+ net_trace!(
+ "udp:{}:{}: receiving {} octets",
+ self.endpoint,
+ remote_endpoint,
+ size
+ );
+
+ let metadata = UdpMetadata {
+ endpoint: remote_endpoint,
+ meta,
+ };
+
+ match self.rx_buffer.enqueue(size, metadata) {
+ Ok(buf) => buf.copy_from_slice(payload),
+ Err(_) => net_trace!(
+ "udp:{}:{}: buffer full, dropped incoming packet",
+ self.endpoint,
+ remote_endpoint
+ ),
+ }
+
+ #[cfg(feature = "async")]
+ self.rx_waker.wake();
+ }
+
+ pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
+ where
+ F: FnOnce(&mut Context, PacketMeta, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>,
+ {
+ let endpoint = self.endpoint;
+ let hop_limit = self.hop_limit.unwrap_or(64);
+
+ let res = self.tx_buffer.dequeue_with(|packet_meta, payload_buf| {
+ let src_addr = match endpoint.addr {
+ Some(addr) => addr,
+ None => match cx.get_source_address(&packet_meta.endpoint.addr) {
+ Some(addr) => addr,
+ None => {
+ net_trace!(
+ "udp:{}:{}: cannot find suitable source address, dropping.",
+ endpoint,
+ packet_meta.endpoint
+ );
+ return Ok(());
+ }
+ },
+ };
+
+ net_trace!(
+ "udp:{}:{}: sending {} octets",
+ endpoint,
+ packet_meta.endpoint,
+ payload_buf.len()
+ );
+
+ let repr = UdpRepr {
+ src_port: endpoint.port,
+ dst_port: packet_meta.endpoint.port,
+ };
+ let ip_repr = IpRepr::new(
+ src_addr,
+ packet_meta.endpoint.addr,
+ IpProtocol::Udp,
+ repr.header_len() + payload_buf.len(),
+ hop_limit,
+ );
+
+ emit(cx, packet_meta.meta, (ip_repr, repr, payload_buf))
+ });
+ match res {
+ Err(Empty) => Ok(()),
+ Ok(Err(e)) => Err(e),
+ Ok(Ok(())) => {
+ #[cfg(feature = "async")]
+ self.tx_waker.wake();
+ Ok(())
+ }
+ }
+ }
+
+ pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
+ if self.tx_buffer.is_empty() {
+ PollAt::Ingress
+ } else {
+ PollAt::Now
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::wire::{IpRepr, UdpRepr};
+
+ use crate::phy::Medium;
+ use crate::tests::setup;
+ use rstest::*;
+
+ fn buffer(packets: usize) -> PacketBuffer<'static> {
+ PacketBuffer::new(
+ (0..packets)
+ .map(|_| PacketMetadata::EMPTY)
+ .collect::<Vec<_>>(),
+ vec![0; 16 * packets],
+ )
+ }
+
+ fn socket(
+ rx_buffer: PacketBuffer<'static>,
+ tx_buffer: PacketBuffer<'static>,
+ ) -> Socket<'static> {
+ Socket::new(rx_buffer, tx_buffer)
+ }
+
+ const LOCAL_PORT: u16 = 53;
+ const REMOTE_PORT: u16 = 49500;
+
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "proto-ipv4")] {
+ use crate::wire::Ipv4Address as IpvXAddress;
+ use crate::wire::Ipv4Repr as IpvXRepr;
+ use IpRepr::Ipv4 as IpReprIpvX;
+
+ const LOCAL_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 1]);
+ const REMOTE_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 2]);
+ const OTHER_ADDR: IpvXAddress = IpvXAddress([192, 168, 1, 3]);
+ } else {
+ use crate::wire::Ipv6Address as IpvXAddress;
+ use crate::wire::Ipv6Repr as IpvXRepr;
+ use IpRepr::Ipv6 as IpReprIpvX;
+
+ const LOCAL_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ ]);
+ const REMOTE_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ ]);
+ const OTHER_ADDR: IpvXAddress = IpvXAddress([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ ]);
+ }
+ }
+
+ pub const LOCAL_END: IpEndpoint = IpEndpoint {
+ addr: LOCAL_ADDR.into_address(),
+ port: LOCAL_PORT,
+ };
+ pub const REMOTE_END: IpEndpoint = IpEndpoint {
+ addr: REMOTE_ADDR.into_address(),
+ port: REMOTE_PORT,
+ };
+
+ pub const LOCAL_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr {
+ src_addr: LOCAL_ADDR,
+ dst_addr: REMOTE_ADDR,
+ next_header: IpProtocol::Udp,
+ payload_len: 8 + 6,
+ hop_limit: 64,
+ });
+
+ pub const REMOTE_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr {
+ src_addr: REMOTE_ADDR,
+ dst_addr: LOCAL_ADDR,
+ next_header: IpProtocol::Udp,
+ payload_len: 8 + 6,
+ hop_limit: 64,
+ });
+
+ pub const BAD_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr {
+ src_addr: REMOTE_ADDR,
+ dst_addr: OTHER_ADDR,
+ next_header: IpProtocol::Udp,
+ payload_len: 8 + 6,
+ hop_limit: 64,
+ });
+
+ const LOCAL_UDP_REPR: UdpRepr = UdpRepr {
+ src_port: LOCAL_PORT,
+ dst_port: REMOTE_PORT,
+ };
+
+ const REMOTE_UDP_REPR: UdpRepr = UdpRepr {
+ src_port: REMOTE_PORT,
+ dst_port: LOCAL_PORT,
+ };
+
+ const PAYLOAD: &[u8] = b"abcdef";
+
+ #[test]
+ fn test_bind_unaddressable() {
+ let mut socket = socket(buffer(0), buffer(0));
+ assert_eq!(socket.bind(0), Err(BindError::Unaddressable));
+ }
+
+ #[test]
+ fn test_bind_twice() {
+ let mut socket = socket(buffer(0), buffer(0));
+ assert_eq!(socket.bind(1), Ok(()));
+ assert_eq!(socket.bind(2), Err(BindError::InvalidState));
+ }
+
+ #[test]
+ #[should_panic(expected = "the time-to-live value of a packet must not be zero")]
+ fn test_set_hop_limit_zero() {
+ let mut s = socket(buffer(0), buffer(1));
+ s.set_hop_limit(Some(0));
+ }
+
+ #[test]
+ fn test_send_unaddressable() {
+ let mut socket = socket(buffer(0), buffer(1));
+
+ assert_eq!(
+ socket.send_slice(b"abcdef", REMOTE_END),
+ Err(SendError::Unaddressable)
+ );
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+ assert_eq!(
+ socket.send_slice(
+ b"abcdef",
+ IpEndpoint {
+ addr: IpvXAddress::UNSPECIFIED.into(),
+ ..REMOTE_END
+ }
+ ),
+ Err(SendError::Unaddressable)
+ );
+ assert_eq!(
+ socket.send_slice(
+ b"abcdef",
+ IpEndpoint {
+ port: 0,
+ ..REMOTE_END
+ }
+ ),
+ Err(SendError::Unaddressable)
+ );
+ assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(()));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_send_dispatch(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+ let mut socket = socket(buffer(0), buffer(1));
+
+ assert_eq!(socket.bind(LOCAL_END), Ok(()));
+
+ assert!(socket.can_send());
+ assert_eq!(
+ socket.dispatch(cx, |_, _, _| unreachable!()),
+ Ok::<_, ()>(())
+ );
+
+ assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(()));
+ assert_eq!(
+ socket.send_slice(b"123456", REMOTE_END),
+ Err(SendError::BufferFull)
+ );
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, _, (ip_repr, udp_repr, payload)| {
+ assert_eq!(ip_repr, LOCAL_IP_REPR);
+ assert_eq!(udp_repr, LOCAL_UDP_REPR);
+ assert_eq!(payload, PAYLOAD);
+ Err(())
+ }),
+ Err(())
+ );
+ assert!(!socket.can_send());
+
+ assert_eq!(
+ socket.dispatch(cx, |_, _, (ip_repr, udp_repr, payload)| {
+ assert_eq!(ip_repr, LOCAL_IP_REPR);
+ assert_eq!(udp_repr, LOCAL_UDP_REPR);
+ assert_eq!(payload, PAYLOAD);
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ assert!(socket.can_send());
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_recv_process(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ assert!(!socket.can_recv());
+ assert_eq!(socket.recv(), Err(RecvError::Exhausted));
+
+ assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+ socket.process(
+ cx,
+ PacketMeta::default(),
+ &REMOTE_IP_REPR,
+ &REMOTE_UDP_REPR,
+ PAYLOAD,
+ );
+ assert!(socket.can_recv());
+
+ assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+ socket.process(
+ cx,
+ PacketMeta::default(),
+ &REMOTE_IP_REPR,
+ &REMOTE_UDP_REPR,
+ PAYLOAD,
+ );
+
+ assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END.into())));
+ assert!(!socket.can_recv());
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_peek_process(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+
+ socket.process(
+ cx,
+ PacketMeta::default(),
+ &REMOTE_IP_REPR,
+ &REMOTE_UDP_REPR,
+ PAYLOAD,
+ );
+ assert_eq!(socket.peek(), Ok((&b"abcdef"[..], &REMOTE_END.into(),)));
+ assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END.into(),)));
+ assert_eq!(socket.peek(), Err(RecvError::Exhausted));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_recv_truncated_slice(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+ socket.process(
+ cx,
+ PacketMeta::default(),
+ &REMOTE_IP_REPR,
+ &REMOTE_UDP_REPR,
+ PAYLOAD,
+ );
+
+ let mut slice = [0; 4];
+ assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_peek_truncated_slice(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ socket.process(
+ cx,
+ PacketMeta::default(),
+ &REMOTE_IP_REPR,
+ &REMOTE_UDP_REPR,
+ PAYLOAD,
+ );
+
+ let mut slice = [0; 4];
+ assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Truncated));
+ assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated));
+ assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Exhausted));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_set_hop_limit(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut s = socket(buffer(0), buffer(1));
+
+ assert_eq!(s.bind(LOCAL_END), Ok(()));
+
+ s.set_hop_limit(Some(0x2a));
+ assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(()));
+ assert_eq!(
+ s.dispatch(cx, |_, _, (ip_repr, _, _)| {
+ assert_eq!(
+ ip_repr,
+ IpReprIpvX(IpvXRepr {
+ src_addr: LOCAL_ADDR,
+ dst_addr: REMOTE_ADDR,
+ next_header: IpProtocol::Udp,
+ payload_len: 8 + 6,
+ hop_limit: 0x2a,
+ })
+ );
+ Ok::<_, ()>(())
+ }),
+ Ok(())
+ );
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_doesnt_accept_wrong_port(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut socket = socket(buffer(1), buffer(0));
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ let mut udp_repr = REMOTE_UDP_REPR;
+ assert!(socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr));
+ udp_repr.dst_port += 1;
+ assert!(!socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_doesnt_accept_wrong_ip(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ let mut port_bound_socket = socket(buffer(1), buffer(0));
+ assert_eq!(port_bound_socket.bind(LOCAL_PORT), Ok(()));
+ assert!(port_bound_socket.accepts(cx, &BAD_IP_REPR, &REMOTE_UDP_REPR));
+
+ let mut ip_bound_socket = socket(buffer(1), buffer(0));
+ assert_eq!(ip_bound_socket.bind(LOCAL_END), Ok(()));
+ assert!(!ip_bound_socket.accepts(cx, &BAD_IP_REPR, &REMOTE_UDP_REPR));
+ }
+
+ #[test]
+ fn test_send_large_packet() {
+ // buffer(4) creates a payload buffer of size 16*4
+ let mut socket = socket(buffer(0), buffer(4));
+ assert_eq!(socket.bind(LOCAL_END), Ok(()));
+
+ let too_large = b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefx";
+ assert_eq!(
+ socket.send_slice(too_large, REMOTE_END),
+ Err(SendError::BufferFull)
+ );
+ assert_eq!(socket.send_slice(&too_large[..16 * 4], REMOTE_END), Ok(()));
+ }
+
+ #[rstest]
+ #[case::ip(Medium::Ip)]
+ #[cfg(feature = "medium-ip")]
+ #[case::ethernet(Medium::Ethernet)]
+ #[cfg(feature = "medium-ethernet")]
+ #[case::ieee802154(Medium::Ieee802154)]
+ #[cfg(feature = "medium-ieee802154")]
+ fn test_process_empty_payload(#[case] medium: Medium) {
+ let meta = Box::leak(Box::new([PacketMetadata::EMPTY]));
+ let recv_buffer = PacketBuffer::new(&mut meta[..], vec![]);
+ let mut socket = socket(recv_buffer, buffer(0));
+
+ let (mut iface, _, _) = setup(medium);
+ let cx = iface.context();
+
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ let repr = UdpRepr {
+ src_port: REMOTE_PORT,
+ dst_port: LOCAL_PORT,
+ };
+ socket.process(cx, PacketMeta::default(), &REMOTE_IP_REPR, &repr, &[]);
+ assert_eq!(socket.recv(), Ok((&[][..], REMOTE_END.into())));
+ }
+
+ #[test]
+ fn test_closing() {
+ let meta = Box::leak(Box::new([PacketMetadata::EMPTY]));
+ let recv_buffer = PacketBuffer::new(&mut meta[..], vec![]);
+ let mut socket = socket(recv_buffer, buffer(0));
+ assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
+
+ assert!(socket.is_open());
+ socket.close();
+ assert!(!socket.is_open());
+ }
+}
diff --git a/src/socket/waker.rs b/src/socket/waker.rs
new file mode 100644
index 0000000..4f42197
--- /dev/null
+++ b/src/socket/waker.rs
@@ -0,0 +1,33 @@
+use core::task::Waker;
+
+/// Utility struct to register and wake a waker.
+#[derive(Debug)]
+pub struct WakerRegistration {
+ waker: Option<Waker>,
+}
+
+impl WakerRegistration {
+ pub const fn new() -> Self {
+ Self { waker: None }
+ }
+
+ /// Register a waker. Overwrites the previous waker, if any.
+ pub fn register(&mut self, w: &Waker) {
+ match self.waker {
+ // Optimization: If both the old and new Wakers wake the same task, we can simply
+ // keep the old waker, skipping the clone. (In most executor implementations,
+ // cloning a waker is somewhat expensive, comparable to cloning an Arc).
+ Some(ref w2) if (w2.will_wake(w)) => {}
+ // In all other cases
+ // - we have no waker registered
+ // - we have a waker registered but it's for a different task.
+ // then clone the new waker and store it
+ _ => self.waker = Some(w.clone()),
+ }
+ }
+
+ /// Wake the registered waker, if any.
+ pub fn wake(&mut self) {
+ self.waker.take().map(|w| w.wake());
+ }
+}
diff --git a/src/storage/assembler.rs b/src/storage/assembler.rs
new file mode 100644
index 0000000..365a1e0
--- /dev/null
+++ b/src/storage/assembler.rs
@@ -0,0 +1,750 @@
+use core::fmt;
+
+use crate::config::ASSEMBLER_MAX_SEGMENT_COUNT;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct TooManyHolesError;
+
+impl fmt::Display for TooManyHolesError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "too many holes")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for TooManyHolesError {}
+
+/// A contiguous chunk of absent data, followed by a contiguous chunk of present data.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Contig {
+ hole_size: usize,
+ data_size: usize,
+}
+
+impl fmt::Display for Contig {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.has_hole() {
+ write!(f, "({})", self.hole_size)?;
+ }
+ if self.has_hole() && self.has_data() {
+ write!(f, " ")?;
+ }
+ if self.has_data() {
+ write!(f, "{}", self.data_size)?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Contig {
+ fn format(&self, fmt: defmt::Formatter) {
+ if self.has_hole() {
+ defmt::write!(fmt, "({})", self.hole_size);
+ }
+ if self.has_hole() && self.has_data() {
+ defmt::write!(fmt, " ");
+ }
+ if self.has_data() {
+ defmt::write!(fmt, "{}", self.data_size);
+ }
+ }
+}
+
+impl Contig {
+ const fn empty() -> Contig {
+ Contig {
+ hole_size: 0,
+ data_size: 0,
+ }
+ }
+
+ fn hole_and_data(hole_size: usize, data_size: usize) -> Contig {
+ Contig {
+ hole_size,
+ data_size,
+ }
+ }
+
+ fn has_hole(&self) -> bool {
+ self.hole_size != 0
+ }
+
+ fn has_data(&self) -> bool {
+ self.data_size != 0
+ }
+
+ fn total_size(&self) -> usize {
+ self.hole_size + self.data_size
+ }
+
+ fn shrink_hole_by(&mut self, size: usize) {
+ self.hole_size -= size;
+ }
+
+ fn shrink_hole_to(&mut self, size: usize) {
+ debug_assert!(self.hole_size >= size);
+
+ let total_size = self.total_size();
+ self.hole_size = size;
+ self.data_size = total_size - size;
+ }
+}
+
+/// A buffer (re)assembler.
+///
+/// Currently, up to a hardcoded limit of 4 or 32 holes can be tracked in the buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Assembler {
+ contigs: [Contig; ASSEMBLER_MAX_SEGMENT_COUNT],
+}
+
+impl fmt::Display for Assembler {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "[ ")?;
+ for contig in self.contigs.iter() {
+ if !contig.has_data() {
+ break;
+ }
+ write!(f, "{contig} ")?;
+ }
+ write!(f, "]")?;
+ Ok(())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Assembler {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(fmt, "[ ");
+ for contig in self.contigs.iter() {
+ if !contig.has_data() {
+ break;
+ }
+ defmt::write!(fmt, "{} ", contig);
+ }
+ defmt::write!(fmt, "]");
+ }
+}
+
+// Invariant on Assembler::contigs:
+// - There's an index `i` where all contigs before have data, and all contigs after don't (are unused).
+// - All contigs with data must have hole_size != 0, except the first.
+
+impl Assembler {
+ /// Create a new buffer assembler.
+ pub const fn new() -> Assembler {
+ const EMPTY: Contig = Contig::empty();
+ Assembler {
+ contigs: [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT],
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.contigs.fill(Contig::empty());
+ }
+
+ fn front(&self) -> Contig {
+ self.contigs[0]
+ }
+
+ /// Return length of the front contiguous range without removing it from the assembler
+ pub fn peek_front(&self) -> usize {
+ let front = self.front();
+ if front.has_hole() {
+ 0
+ } else {
+ front.data_size
+ }
+ }
+
+ fn back(&self) -> Contig {
+ self.contigs[self.contigs.len() - 1]
+ }
+
+ /// Return whether the assembler contains no data.
+ pub fn is_empty(&self) -> bool {
+ !self.front().has_data()
+ }
+
+ /// Remove a contig at the given index.
+ fn remove_contig_at(&mut self, at: usize) {
+ debug_assert!(self.contigs[at].has_data());
+
+ for i in at..self.contigs.len() - 1 {
+ if !self.contigs[i].has_data() {
+ return;
+ }
+ self.contigs[i] = self.contigs[i + 1];
+ }
+
+ // Removing the last one.
+ self.contigs[self.contigs.len() - 1] = Contig::empty();
+ }
+
+ /// Add a contig at the given index, and return a pointer to it.
+ fn add_contig_at(&mut self, at: usize) -> Result<&mut Contig, TooManyHolesError> {
+ if self.back().has_data() {
+ return Err(TooManyHolesError);
+ }
+
+ for i in (at + 1..self.contigs.len()).rev() {
+ self.contigs[i] = self.contigs[i - 1];
+ }
+
+ self.contigs[at] = Contig::empty();
+ Ok(&mut self.contigs[at])
+ }
+
+ /// Add a new contiguous range to the assembler,
+ /// or return `Err(TooManyHolesError)` if too many discontinuities are already recorded.
+ pub fn add(&mut self, mut offset: usize, size: usize) -> Result<(), TooManyHolesError> {
+ if size == 0 {
+ return Ok(());
+ }
+
+ let mut i = 0;
+
+ // Find index of the contig containing the start of the range.
+ loop {
+ if i == self.contigs.len() {
+ // The new range is after all the previous ranges, but there/s no space to add it.
+ return Err(TooManyHolesError);
+ }
+ let contig = &mut self.contigs[i];
+ if !contig.has_data() {
+ // The new range is after all the previous ranges. Add it.
+ *contig = Contig::hole_and_data(offset, size);
+ return Ok(());
+ }
+ if offset <= contig.total_size() {
+ break;
+ }
+ offset -= contig.total_size();
+ i += 1;
+ }
+
+ let contig = &mut self.contigs[i];
+ if offset < contig.hole_size {
+ // Range starts within the hole.
+
+ if offset + size < contig.hole_size {
+ // Range also ends within the hole.
+ let new_contig = self.add_contig_at(i)?;
+ new_contig.hole_size = offset;
+ new_contig.data_size = size;
+
+ // Previous contigs[index] got moved to contigs[index+1]
+ self.contigs[i + 1].shrink_hole_by(offset + size);
+ return Ok(());
+ }
+
+ // The range being added covers both a part of the hole and a part of the data
+ // in this contig, shrink the hole in this contig.
+ contig.shrink_hole_to(offset);
+ }
+
+ // coalesce contigs to the right.
+ let mut j = i + 1;
+ while j < self.contigs.len()
+ && self.contigs[j].has_data()
+ && offset + size >= self.contigs[i].total_size() + self.contigs[j].hole_size
+ {
+ self.contigs[i].data_size += self.contigs[j].total_size();
+ j += 1;
+ }
+ let shift = j - i - 1;
+ if shift != 0 {
+ for x in i + 1..self.contigs.len() {
+ if !self.contigs[x].has_data() {
+ break;
+ }
+
+ self.contigs[x] = self
+ .contigs
+ .get(x + shift)
+ .copied()
+ .unwrap_or_else(Contig::empty);
+ }
+ }
+
+ if offset + size > self.contigs[i].total_size() {
+ // The added range still extends beyond the current contig. Increase data size.
+ let left = offset + size - self.contigs[i].total_size();
+ self.contigs[i].data_size += left;
+
+ // Decrease hole size of the next, if any.
+ if i + 1 < self.contigs.len() && self.contigs[i + 1].has_data() {
+ self.contigs[i + 1].hole_size -= left;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Remove a contiguous range from the front of the assembler.
+ /// If no such range, return 0.
+ pub fn remove_front(&mut self) -> usize {
+ let front = self.front();
+ if front.has_hole() || !front.has_data() {
+ 0
+ } else {
+ self.remove_contig_at(0);
+ debug_assert!(front.data_size > 0);
+ front.data_size
+ }
+ }
+
+ /// Add a segment, then remove_front.
+ ///
+ /// This is equivalent to calling `add` then `remove_front` individually,
+ /// except it's guaranteed to not fail when offset = 0.
+ /// This is required for TCP: we must never drop the next expected segment, or
+ /// the protocol might get stuck.
+ pub fn add_then_remove_front(
+ &mut self,
+ offset: usize,
+ size: usize,
+ ) -> Result<usize, TooManyHolesError> {
+ // This is the only case where a segment at offset=0 would cause the
+ // total amount of contigs to rise (and therefore can potentially cause
+ // a TooManyHolesError). Handle it in a way that is guaranteed to succeed.
+ if offset == 0 && size < self.contigs[0].hole_size {
+ self.contigs[0].hole_size -= size;
+ return Ok(size);
+ }
+
+ self.add(offset, size)?;
+ Ok(self.remove_front())
+ }
+
+ /// Iterate over all of the contiguous data ranges.
+ ///
+ /// This is used in calculating what data ranges have been received. The offset indicates the
+ /// number of bytes of contiguous data received before the beginnings of this Assembler.
+ ///
+ /// Data Hole Data
+ /// |--- 100 ---|--- 200 ---|--- 100 ---|
+ ///
+ /// An offset of 1500 would return the ranges: ``(1500, 1600), (1800, 1900)``
+ pub fn iter_data(&self, first_offset: usize) -> AssemblerIter {
+ AssemblerIter::new(self, first_offset)
+ }
+}
+
+pub struct AssemblerIter<'a> {
+ assembler: &'a Assembler,
+ offset: usize,
+ index: usize,
+ left: usize,
+ right: usize,
+}
+
+impl<'a> AssemblerIter<'a> {
+ fn new(assembler: &'a Assembler, offset: usize) -> AssemblerIter<'a> {
+ AssemblerIter {
+ assembler,
+ offset,
+ index: 0,
+ left: 0,
+ right: 0,
+ }
+ }
+}
+
+impl<'a> Iterator for AssemblerIter<'a> {
+ type Item = (usize, usize);
+
+ fn next(&mut self) -> Option<(usize, usize)> {
+ let mut data_range = None;
+ while data_range.is_none() && self.index < self.assembler.contigs.len() {
+ let contig = self.assembler.contigs[self.index];
+ self.left += contig.hole_size;
+ self.right = self.left + contig.data_size;
+ data_range = if self.left < self.right {
+ let data_range = (self.left + self.offset, self.right + self.offset);
+ self.left = self.right;
+ Some(data_range)
+ } else {
+ None
+ };
+ self.index += 1;
+ }
+ data_range
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::vec::Vec;
+
+ impl From<Vec<(usize, usize)>> for Assembler {
+ fn from(vec: Vec<(usize, usize)>) -> Assembler {
+ const EMPTY: Contig = Contig::empty();
+
+ let mut contigs = [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT];
+ for (i, &(hole_size, data_size)) in vec.iter().enumerate() {
+ contigs[i] = Contig {
+ hole_size,
+ data_size,
+ };
+ }
+ Assembler { contigs }
+ }
+ }
+
+ macro_rules! contigs {
+ [$( $x:expr ),*] => ({
+ Assembler::from(vec![$( $x ),*])
+ })
+ }
+
+ #[test]
+ fn test_new() {
+ let assr = Assembler::new();
+ assert_eq!(assr, contigs![]);
+ }
+
+ #[test]
+ fn test_empty_add_full() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 16), Ok(()));
+ assert_eq!(assr, contigs![(0, 16)]);
+ }
+
+ #[test]
+ fn test_empty_add_front() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 4), Ok(()));
+ assert_eq!(assr, contigs![(0, 4)]);
+ }
+
+ #[test]
+ fn test_empty_add_back() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(12, 4), Ok(()));
+ assert_eq!(assr, contigs![(12, 4)]);
+ }
+
+ #[test]
+ fn test_empty_add_mid() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(4, 8), Ok(()));
+ assert_eq!(assr, contigs![(4, 8)]);
+ }
+
+ #[test]
+ fn test_partial_add_front() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(0, 4), Ok(()));
+ assert_eq!(assr, contigs![(0, 12)]);
+ }
+
+ #[test]
+ fn test_partial_add_back() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(12, 4), Ok(()));
+ assert_eq!(assr, contigs![(4, 12)]);
+ }
+
+ #[test]
+ fn test_partial_add_front_overlap() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(0, 8), Ok(()));
+ assert_eq!(assr, contigs![(0, 12)]);
+ }
+
+ #[test]
+ fn test_partial_add_front_overlap_split() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(2, 6), Ok(()));
+ assert_eq!(assr, contigs![(2, 10)]);
+ }
+
+ #[test]
+ fn test_partial_add_back_overlap() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(8, 8), Ok(()));
+ assert_eq!(assr, contigs![(4, 12)]);
+ }
+
+ #[test]
+ fn test_partial_add_back_overlap_split() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(10, 4), Ok(()));
+ assert_eq!(assr, contigs![(4, 10)]);
+ }
+
+ #[test]
+ fn test_partial_add_both_overlap() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(0, 16), Ok(()));
+ assert_eq!(assr, contigs![(0, 16)]);
+ }
+
+ #[test]
+ fn test_partial_add_both_overlap_split() {
+ let mut assr = contigs![(4, 8)];
+ assert_eq!(assr.add(2, 12), Ok(()));
+ assert_eq!(assr, contigs![(2, 12)]);
+ }
+
+ #[test]
+ fn test_rejected_add_keeps_state() {
+ let mut assr = Assembler::new();
+ for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT {
+ assert_eq!(assr.add(c * 10, 3), Ok(()));
+ }
+ // Maximum of allowed holes is reached
+ let assr_before = assr.clone();
+ assert_eq!(assr.add(1, 3), Err(TooManyHolesError));
+ assert_eq!(assr_before, assr);
+ }
+
+ #[test]
+ fn test_empty_remove_front() {
+ let mut assr = contigs![];
+ assert_eq!(assr.remove_front(), 0);
+ }
+
+ #[test]
+ fn test_trailing_hole_remove_front() {
+ let mut assr = contigs![(0, 4)];
+ assert_eq!(assr.remove_front(), 4);
+ assert_eq!(assr, contigs![]);
+ }
+
+ #[test]
+ fn test_trailing_data_remove_front() {
+ let mut assr = contigs![(0, 4), (4, 4)];
+ assert_eq!(assr.remove_front(), 4);
+ assert_eq!(assr, contigs![(4, 4)]);
+ }
+
+ #[test]
+ fn test_boundary_case_remove_front() {
+ let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT];
+ vec[0] = (0, 2);
+ let mut assr = Assembler::from(vec);
+ assert_eq!(assr.remove_front(), 2);
+ let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT];
+ vec[ASSEMBLER_MAX_SEGMENT_COUNT - 1] = (0, 0);
+ let exp_assr = Assembler::from(vec);
+ assert_eq!(assr, exp_assr);
+ }
+
+ #[test]
+ fn test_shrink_next_hole() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(100, 10), Ok(()));
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add(40, 30), Ok(()));
+ assert_eq!(assr, contigs![(40, 30), (30, 10)]);
+ }
+
+ #[test]
+ fn test_join_two() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(10, 10), Ok(()));
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add(15, 40), Ok(()));
+ assert_eq!(assr, contigs![(10, 50)]);
+ }
+
+ #[test]
+ fn test_join_two_reversed() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add(10, 10), Ok(()));
+ assert_eq!(assr.add(15, 40), Ok(()));
+ assert_eq!(assr, contigs![(10, 50)]);
+ }
+
+ #[test]
+ fn test_join_two_overlong() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add(10, 10), Ok(()));
+ assert_eq!(assr.add(15, 60), Ok(()));
+ assert_eq!(assr, contigs![(10, 65)]);
+ }
+
+ #[test]
+ fn test_iter_empty() {
+ let assr = Assembler::new();
+ let segments: Vec<_> = assr.iter_data(10).collect();
+ assert_eq!(segments, vec![]);
+ }
+
+ #[test]
+ fn test_iter_full() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 16), Ok(()));
+ let segments: Vec<_> = assr.iter_data(10).collect();
+ assert_eq!(segments, vec![(10, 26)]);
+ }
+
+ #[test]
+ fn test_iter_offset() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 16), Ok(()));
+ let segments: Vec<_> = assr.iter_data(100).collect();
+ assert_eq!(segments, vec![(100, 116)]);
+ }
+
+ #[test]
+ fn test_iter_one_front() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 4), Ok(()));
+ let segments: Vec<_> = assr.iter_data(10).collect();
+ assert_eq!(segments, vec![(10, 14)]);
+ }
+
+ #[test]
+ fn test_iter_one_back() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(12, 4), Ok(()));
+ let segments: Vec<_> = assr.iter_data(10).collect();
+ assert_eq!(segments, vec![(22, 26)]);
+ }
+
+ #[test]
+ fn test_iter_one_mid() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(4, 8), Ok(()));
+ let segments: Vec<_> = assr.iter_data(10).collect();
+ assert_eq!(segments, vec![(14, 22)]);
+ }
+
+ #[test]
+ fn test_iter_one_trailing_gap() {
+ let assr = contigs![(4, 8)];
+ let segments: Vec<_> = assr.iter_data(100).collect();
+ assert_eq!(segments, vec![(104, 112)]);
+ }
+
+ #[test]
+ fn test_iter_two_split() {
+ let assr = contigs![(2, 6), (4, 1)];
+ let segments: Vec<_> = assr.iter_data(100).collect();
+ assert_eq!(segments, vec![(102, 108), (112, 113)]);
+ }
+
+ #[test]
+ fn test_iter_three_split() {
+ let assr = contigs![(2, 6), (2, 1), (2, 2)];
+ let segments: Vec<_> = assr.iter_data(100).collect();
+ assert_eq!(segments, vec![(102, 108), (110, 111), (113, 115)]);
+ }
+
+ #[test]
+ fn test_issue_694() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(0, 1), Ok(()));
+ assert_eq!(assr.add(2, 1), Ok(()));
+ assert_eq!(assr.add(1, 1), Ok(()));
+ }
+
+ #[test]
+ fn test_add_then_remove_front() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add_then_remove_front(10, 10), Ok(0));
+ assert_eq!(assr, contigs![(10, 10), (30, 10)]);
+ }
+
+ #[test]
+ fn test_add_then_remove_front_at_front() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add_then_remove_front(0, 10), Ok(10));
+ assert_eq!(assr, contigs![(40, 10)]);
+ }
+
+ #[test]
+ fn test_add_then_remove_front_at_front_touch() {
+ let mut assr = Assembler::new();
+ assert_eq!(assr.add(50, 10), Ok(()));
+ assert_eq!(assr.add_then_remove_front(0, 50), Ok(60));
+ assert_eq!(assr, contigs![]);
+ }
+
+ #[test]
+ fn test_add_then_remove_front_at_front_full() {
+ let mut assr = Assembler::new();
+ for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT {
+ assert_eq!(assr.add(c * 10, 3), Ok(()));
+ }
+ // Maximum of allowed holes is reached
+ let assr_before = assr.clone();
+ assert_eq!(assr.add_then_remove_front(1, 3), Err(TooManyHolesError));
+ assert_eq!(assr_before, assr);
+ }
+
+ #[test]
+ fn test_add_then_remove_front_at_front_full_offset_0() {
+ let mut assr = Assembler::new();
+ for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT {
+ assert_eq!(assr.add(c * 10, 3), Ok(()));
+ }
+ assert_eq!(assr.add_then_remove_front(0, 3), Ok(3));
+ }
+
+ // Test against an obviously-correct but inefficient bitmap impl.
+ #[test]
+ fn test_random() {
+ use rand::Rng;
+
+ const MAX_INDEX: usize = 256;
+
+ for max_size in [2, 5, 10, 100] {
+ for _ in 0..300 {
+ //println!("===");
+ let mut assr = Assembler::new();
+ let mut map = [false; MAX_INDEX];
+
+ for _ in 0..60 {
+ let offset = rand::thread_rng().gen_range(0..MAX_INDEX - max_size - 1);
+ let size = rand::thread_rng().gen_range(1..=max_size);
+
+ //println!("add {}..{} {}", offset, offset + size, size);
+ // Real impl
+ let res = assr.add(offset, size);
+
+ // Bitmap impl
+ let mut map2 = map;
+ map2[offset..][..size].fill(true);
+
+ let mut contigs = vec![];
+ let mut hole: usize = 0;
+ let mut data: usize = 0;
+ for b in map2 {
+ if b {
+ data += 1;
+ } else {
+ if data != 0 {
+ contigs.push((hole, data));
+ hole = 0;
+ data = 0;
+ }
+ hole += 1;
+ }
+ }
+
+ // Compare.
+ let wanted_res = if contigs.len() > ASSEMBLER_MAX_SEGMENT_COUNT {
+ Err(TooManyHolesError)
+ } else {
+ Ok(())
+ };
+ assert_eq!(res, wanted_res);
+ if res.is_ok() {
+ map = map2;
+ assert_eq!(assr, Assembler::from(contigs));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/storage/mod.rs b/src/storage/mod.rs
new file mode 100644
index 0000000..b03de71
--- /dev/null
+++ b/src/storage/mod.rs
@@ -0,0 +1,31 @@
+/*! Specialized containers.
+
+The `storage` module provides containers for use in other modules.
+The containers support both pre-allocated memory, without the `std`
+or `alloc` crates being available, and heap-allocated memory.
+*/
+
+mod assembler;
+mod packet_buffer;
+mod ring_buffer;
+
+pub use self::assembler::Assembler;
+pub use self::packet_buffer::{PacketBuffer, PacketMetadata};
+pub use self::ring_buffer::RingBuffer;
+
+/// A trait for setting a value to a known state.
+///
+/// In-place analog of Default.
+pub trait Resettable {
+ fn reset(&mut self);
+}
+
+/// Error returned when enqueuing into a full buffer.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Full;
+
+/// Error returned when dequeuing from an empty buffer.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Empty;
diff --git a/src/storage/packet_buffer.rs b/src/storage/packet_buffer.rs
new file mode 100644
index 0000000..28119fa
--- /dev/null
+++ b/src/storage/packet_buffer.rs
@@ -0,0 +1,402 @@
+use managed::ManagedSlice;
+
+use crate::storage::{Full, RingBuffer};
+
+use super::Empty;
+
+/// Size and header of a packet.
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PacketMetadata<H> {
+ size: usize,
+ header: Option<H>,
+}
+
+impl<H> PacketMetadata<H> {
+ /// Empty packet description.
+ pub const EMPTY: PacketMetadata<H> = PacketMetadata {
+ size: 0,
+ header: None,
+ };
+
+ fn padding(size: usize) -> PacketMetadata<H> {
+ PacketMetadata {
+ size: size,
+ header: None,
+ }
+ }
+
+ fn packet(size: usize, header: H) -> PacketMetadata<H> {
+ PacketMetadata {
+ size: size,
+ header: Some(header),
+ }
+ }
+
+ fn is_padding(&self) -> bool {
+ self.header.is_none()
+ }
+}
+
+/// An UDP packet ring buffer.
+#[derive(Debug)]
+pub struct PacketBuffer<'a, H: 'a> {
+ metadata_ring: RingBuffer<'a, PacketMetadata<H>>,
+ payload_ring: RingBuffer<'a, u8>,
+}
+
+impl<'a, H> PacketBuffer<'a, H> {
+ /// Create a new packet buffer with the provided metadata and payload storage.
+ ///
+ /// Metadata storage limits the maximum _number_ of packets in the buffer and payload
+ /// storage limits the maximum _total size_ of packets.
+ pub fn new<MS, PS>(metadata_storage: MS, payload_storage: PS) -> PacketBuffer<'a, H>
+ where
+ MS: Into<ManagedSlice<'a, PacketMetadata<H>>>,
+ PS: Into<ManagedSlice<'a, u8>>,
+ {
+ PacketBuffer {
+ metadata_ring: RingBuffer::new(metadata_storage),
+ payload_ring: RingBuffer::new(payload_storage),
+ }
+ }
+
+ /// Query whether the buffer is empty.
+ pub fn is_empty(&self) -> bool {
+ self.metadata_ring.is_empty()
+ }
+
+ /// Query whether the buffer is full.
+ pub fn is_full(&self) -> bool {
+ self.metadata_ring.is_full()
+ }
+
+ // There is currently no enqueue_with() because of the complexity of managing padding
+ // in case of failure.
+
+ /// Enqueue a single packet with the given header into the buffer, and
+ /// return a reference to its payload, or return `Err(Full)`
+ /// if the buffer is full.
+ pub fn enqueue(&mut self, size: usize, header: H) -> Result<&mut [u8], Full> {
+ if self.payload_ring.capacity() < size || self.metadata_ring.is_full() {
+ return Err(Full);
+ }
+
+ // Ring is currently empty. Clear it (resetting `read_at`) to maximize
+ // for contiguous space.
+ if self.payload_ring.is_empty() {
+ self.payload_ring.clear();
+ }
+
+ let window = self.payload_ring.window();
+ let contig_window = self.payload_ring.contiguous_window();
+
+ if window < size {
+ return Err(Full);
+ } else if contig_window < size {
+ if window - contig_window < size {
+ // The buffer length is larger than the current contiguous window
+ // and is larger than the contiguous window will be after adding
+ // the padding necessary to circle around to the beginning of the
+ // ring buffer.
+ return Err(Full);
+ } else {
+ // Add padding to the end of the ring buffer so that the
+ // contiguous window is at the beginning of the ring buffer.
+ *self.metadata_ring.enqueue_one()? = PacketMetadata::padding(contig_window);
+ // note(discard): function does not write to the result
+ // enqueued padding buffer location
+ let _buf_enqueued = self.payload_ring.enqueue_many(contig_window);
+ }
+ }
+
+ *self.metadata_ring.enqueue_one()? = PacketMetadata::packet(size, header);
+
+ let payload_buf = self.payload_ring.enqueue_many(size);
+ debug_assert!(payload_buf.len() == size);
+ Ok(payload_buf)
+ }
+
+ /// Call `f` with a packet from the buffer large enough to fit `max_size` bytes. The packet
+ /// is shrunk to the size returned from `f` and enqueued into the buffer.
+ pub fn enqueue_with_infallible<'b, F>(
+ &'b mut self,
+ max_size: usize,
+ header: H,
+ f: F,
+ ) -> Result<usize, Full>
+ where
+ F: FnOnce(&'b mut [u8]) -> usize,
+ {
+ if self.payload_ring.capacity() < max_size || self.metadata_ring.is_full() {
+ return Err(Full);
+ }
+
+ let window = self.payload_ring.window();
+ let contig_window = self.payload_ring.contiguous_window();
+
+ if window < max_size {
+ return Err(Full);
+ } else if contig_window < max_size {
+ if window - contig_window < max_size {
+ // The buffer length is larger than the current contiguous window
+ // and is larger than the contiguous window will be after adding
+ // the padding necessary to circle around to the beginning of the
+ // ring buffer.
+ return Err(Full);
+ } else {
+ // Add padding to the end of the ring buffer so that the
+ // contiguous window is at the beginning of the ring buffer.
+ *self.metadata_ring.enqueue_one()? = PacketMetadata::padding(contig_window);
+ // note(discard): function does not write to the result
+ // enqueued padding buffer location
+ let _buf_enqueued = self.payload_ring.enqueue_many(contig_window);
+ }
+ }
+
+ let (size, _) = self
+ .payload_ring
+ .enqueue_many_with(|data| (f(&mut data[..max_size]), ()));
+
+ *self.metadata_ring.enqueue_one()? = PacketMetadata::packet(size, header);
+
+ Ok(size)
+ }
+
+ fn dequeue_padding(&mut self) {
+ let _ = self.metadata_ring.dequeue_one_with(|metadata| {
+ if metadata.is_padding() {
+ // note(discard): function does not use value of dequeued padding bytes
+ let _buf_dequeued = self.payload_ring.dequeue_many(metadata.size);
+ Ok(()) // dequeue metadata
+ } else {
+ Err(()) // don't dequeue metadata
+ }
+ });
+ }
+
+ /// Call `f` with a single packet from the buffer, and dequeue the packet if `f`
+ /// returns successfully, or return `Err(EmptyError)` if the buffer is empty.
+ pub fn dequeue_with<'c, R, E, F>(&'c mut self, f: F) -> Result<Result<R, E>, Empty>
+ where
+ F: FnOnce(&mut H, &'c mut [u8]) -> Result<R, E>,
+ {
+ self.dequeue_padding();
+
+ self.metadata_ring.dequeue_one_with(|metadata| {
+ self.payload_ring
+ .dequeue_many_with(|payload_buf| {
+ debug_assert!(payload_buf.len() >= metadata.size);
+
+ match f(
+ metadata.header.as_mut().unwrap(),
+ &mut payload_buf[..metadata.size],
+ ) {
+ Ok(val) => (metadata.size, Ok(val)),
+ Err(err) => (0, Err(err)),
+ }
+ })
+ .1
+ })
+ }
+
+ /// Dequeue a single packet from the buffer, and return a reference to its payload
+ /// as well as its header, or return `Err(Error::Exhausted)` if the buffer is empty.
+ pub fn dequeue(&mut self) -> Result<(H, &mut [u8]), Empty> {
+ self.dequeue_padding();
+
+ let meta = self.metadata_ring.dequeue_one()?;
+
+ let payload_buf = self.payload_ring.dequeue_many(meta.size);
+ debug_assert!(payload_buf.len() == meta.size);
+ Ok((meta.header.take().unwrap(), payload_buf))
+ }
+
+ /// Peek at a single packet from the buffer without removing it, and return a reference to
+ /// its payload as well as its header, or return `Err(Error:Exhausted)` if the buffer is empty.
+ ///
+ /// This function otherwise behaves identically to [dequeue](#method.dequeue).
+ pub fn peek(&mut self) -> Result<(&H, &[u8]), Empty> {
+ self.dequeue_padding();
+
+ if let Some(metadata) = self.metadata_ring.get_allocated(0, 1).first() {
+ Ok((
+ metadata.header.as_ref().unwrap(),
+ self.payload_ring.get_allocated(0, metadata.size),
+ ))
+ } else {
+ Err(Empty)
+ }
+ }
+
+ /// Return the maximum number packets that can be stored.
+ pub fn packet_capacity(&self) -> usize {
+ self.metadata_ring.capacity()
+ }
+
+ /// Return the maximum number of bytes in the payload ring buffer.
+ pub fn payload_capacity(&self) -> usize {
+ self.payload_ring.capacity()
+ }
+
+ /// Reset the packet buffer and clear any staged.
+ #[allow(unused)]
+ pub(crate) fn reset(&mut self) {
+ self.payload_ring.clear();
+ self.metadata_ring.clear();
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn buffer() -> PacketBuffer<'static, ()> {
+ PacketBuffer::new(vec![PacketMetadata::EMPTY; 4], vec![0u8; 16])
+ }
+
+ #[test]
+ fn test_simple() {
+ let mut buffer = buffer();
+ buffer.enqueue(6, ()).unwrap().copy_from_slice(b"abcdef");
+ assert_eq!(buffer.enqueue(16, ()), Err(Full));
+ assert_eq!(buffer.metadata_ring.len(), 1);
+ assert_eq!(buffer.dequeue().unwrap().1, &b"abcdef"[..]);
+ assert_eq!(buffer.dequeue(), Err(Empty));
+ }
+
+ #[test]
+ fn test_peek() {
+ let mut buffer = buffer();
+ assert_eq!(buffer.peek(), Err(Empty));
+ buffer.enqueue(6, ()).unwrap().copy_from_slice(b"abcdef");
+ assert_eq!(buffer.metadata_ring.len(), 1);
+ assert_eq!(buffer.peek().unwrap().1, &b"abcdef"[..]);
+ assert_eq!(buffer.dequeue().unwrap().1, &b"abcdef"[..]);
+ assert_eq!(buffer.peek(), Err(Empty));
+ }
+
+ #[test]
+ fn test_padding() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(6, ()).is_ok());
+ assert!(buffer.enqueue(8, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ buffer.enqueue(4, ()).unwrap().copy_from_slice(b"abcd");
+ assert_eq!(buffer.metadata_ring.len(), 3);
+ assert!(buffer.dequeue().is_ok());
+
+ assert_eq!(buffer.dequeue().unwrap().1, &b"abcd"[..]);
+ assert_eq!(buffer.metadata_ring.len(), 0);
+ }
+
+ #[test]
+ fn test_padding_with_large_payload() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(12, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ buffer
+ .enqueue(12, ())
+ .unwrap()
+ .copy_from_slice(b"abcdefghijkl");
+ }
+
+ #[test]
+ fn test_dequeue_with() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(6, ()).is_ok());
+ assert!(buffer.enqueue(8, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ buffer.enqueue(4, ()).unwrap().copy_from_slice(b"abcd");
+ assert_eq!(buffer.metadata_ring.len(), 3);
+ assert!(buffer.dequeue().is_ok());
+
+ assert!(matches!(
+ buffer.dequeue_with(|_, _| Result::<(), u32>::Err(123)),
+ Ok(Err(_))
+ ));
+ assert_eq!(buffer.metadata_ring.len(), 1);
+
+ assert!(buffer
+ .dequeue_with(|&mut (), payload| {
+ assert_eq!(payload, &b"abcd"[..]);
+ Result::<(), ()>::Ok(())
+ })
+ .is_ok());
+ assert_eq!(buffer.metadata_ring.len(), 0);
+ }
+
+ #[test]
+ fn test_metadata_full_empty() {
+ let mut buffer = buffer();
+ assert!(buffer.is_empty());
+ assert!(!buffer.is_full());
+ assert!(buffer.enqueue(1, ()).is_ok());
+ assert!(!buffer.is_empty());
+ assert!(buffer.enqueue(1, ()).is_ok());
+ assert!(buffer.enqueue(1, ()).is_ok());
+ assert!(!buffer.is_full());
+ assert!(!buffer.is_empty());
+ assert!(buffer.enqueue(1, ()).is_ok());
+ assert!(buffer.is_full());
+ assert!(!buffer.is_empty());
+ assert_eq!(buffer.metadata_ring.len(), 4);
+ assert_eq!(buffer.enqueue(1, ()), Err(Full));
+ }
+
+ #[test]
+ fn test_window_too_small() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(4, ()).is_ok());
+ assert!(buffer.enqueue(8, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ assert_eq!(buffer.enqueue(16, ()), Err(Full));
+ assert_eq!(buffer.metadata_ring.len(), 1);
+ }
+
+ #[test]
+ fn test_contiguous_window_too_small() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(4, ()).is_ok());
+ assert!(buffer.enqueue(8, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ assert_eq!(buffer.enqueue(8, ()), Err(Full));
+ assert_eq!(buffer.metadata_ring.len(), 1);
+ }
+
+ #[test]
+ fn test_contiguous_window_wrap() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(15, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ assert!(buffer.enqueue(16, ()).is_ok());
+ }
+
+ #[test]
+ fn test_capacity_too_small() {
+ let mut buffer = buffer();
+ assert_eq!(buffer.enqueue(32, ()), Err(Full));
+ }
+
+ #[test]
+ fn test_contig_window_prioritized() {
+ let mut buffer = buffer();
+ assert!(buffer.enqueue(4, ()).is_ok());
+ assert!(buffer.dequeue().is_ok());
+ assert!(buffer.enqueue(5, ()).is_ok());
+ }
+
+ #[test]
+ fn clear() {
+ let mut buffer = buffer();
+
+ // Ensure enqueuing data in the buffer fills it somewhat.
+ assert!(buffer.is_empty());
+ assert!(buffer.enqueue(6, ()).is_ok());
+
+ // Ensure that resetting the buffer causes it to be empty.
+ assert!(!buffer.is_empty());
+ buffer.reset();
+ assert!(buffer.is_empty());
+ }
+}
diff --git a/src/storage/ring_buffer.rs b/src/storage/ring_buffer.rs
new file mode 100644
index 0000000..7d461b6
--- /dev/null
+++ b/src/storage/ring_buffer.rs
@@ -0,0 +1,803 @@
+// Some of the functions in ring buffer is marked as #[must_use]. It notes that
+// these functions may have side effects, and it's implemented by [RFC 1940].
+// [RFC 1940]: https://github.com/rust-lang/rust/issues/43302
+
+use core::cmp;
+use managed::ManagedSlice;
+
+use crate::storage::Resettable;
+
+use super::{Empty, Full};
+
+/// A ring buffer.
+///
+/// This ring buffer implementation provides many ways to interact with it:
+///
+/// * Enqueueing or dequeueing one element from corresponding side of the buffer;
+/// * Enqueueing or dequeueing a slice of elements from corresponding side of the buffer;
+/// * Accessing allocated and unallocated areas directly.
+///
+/// It is also zero-copy; all methods provide references into the buffer's storage.
+/// Note that all references are mutable; it is considered more important to allow
+/// in-place processing than to protect from accidental mutation.
+///
+/// This implementation is suitable for both simple uses such as a FIFO queue
+/// of UDP packets, and advanced ones such as a TCP reassembly buffer.
+#[derive(Debug)]
+pub struct RingBuffer<'a, T: 'a> {
+ storage: ManagedSlice<'a, T>,
+ read_at: usize,
+ length: usize,
+}
+
+impl<'a, T: 'a> RingBuffer<'a, T> {
+ /// Create a ring buffer with the given storage.
+ ///
+ /// During creation, every element in `storage` is reset.
+ pub fn new<S>(storage: S) -> RingBuffer<'a, T>
+ where
+ S: Into<ManagedSlice<'a, T>>,
+ {
+ RingBuffer {
+ storage: storage.into(),
+ read_at: 0,
+ length: 0,
+ }
+ }
+
+ /// Clear the ring buffer.
+ pub fn clear(&mut self) {
+ self.read_at = 0;
+ self.length = 0;
+ }
+
+ /// Return the maximum number of elements in the ring buffer.
+ pub fn capacity(&self) -> usize {
+ self.storage.len()
+ }
+
+ /// Clear the ring buffer, and reset every element.
+ pub fn reset(&mut self)
+ where
+ T: Resettable,
+ {
+ self.clear();
+ for elem in self.storage.iter_mut() {
+ elem.reset();
+ }
+ }
+
+ /// Return the current number of elements in the ring buffer.
+ pub fn len(&self) -> usize {
+ self.length
+ }
+
+ /// Return the number of elements that can be added to the ring buffer.
+ pub fn window(&self) -> usize {
+ self.capacity() - self.len()
+ }
+
+ /// Return the largest number of elements that can be added to the buffer
+ /// without wrapping around (i.e. in a single `enqueue_many` call).
+ pub fn contiguous_window(&self) -> usize {
+ cmp::min(self.window(), self.capacity() - self.get_idx(self.length))
+ }
+
+ /// Query whether the buffer is empty.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Query whether the buffer is full.
+ pub fn is_full(&self) -> bool {
+ self.window() == 0
+ }
+
+ /// Shorthand for `(self.read + idx) % self.capacity()` with an
+ /// additional check to ensure that the capacity is not zero.
+ fn get_idx(&self, idx: usize) -> usize {
+ let len = self.capacity();
+ if len > 0 {
+ (self.read_at + idx) % len
+ } else {
+ 0
+ }
+ }
+
+ /// Shorthand for `(self.read + idx) % self.capacity()` with no
+ /// additional checks to ensure the capacity is not zero.
+ fn get_idx_unchecked(&self, idx: usize) -> usize {
+ (self.read_at + idx) % self.capacity()
+ }
+}
+
+/// This is the "discrete" ring buffer interface: it operates with single elements,
+/// and boundary conditions (empty/full) are errors.
+impl<'a, T: 'a> RingBuffer<'a, T> {
+ /// Call `f` with a single buffer element, and enqueue the element if `f`
+ /// returns successfully, or return `Err(Full)` if the buffer is full.
+ pub fn enqueue_one_with<'b, R, E, F>(&'b mut self, f: F) -> Result<Result<R, E>, Full>
+ where
+ F: FnOnce(&'b mut T) -> Result<R, E>,
+ {
+ if self.is_full() {
+ return Err(Full);
+ }
+
+ let index = self.get_idx_unchecked(self.length);
+ let res = f(&mut self.storage[index]);
+ if res.is_ok() {
+ self.length += 1;
+ }
+ Ok(res)
+ }
+
+ /// Enqueue a single element into the buffer, and return a reference to it,
+ /// or return `Err(Full)` if the buffer is full.
+ ///
+ /// This function is a shortcut for `ring_buf.enqueue_one_with(Ok)`.
+ pub fn enqueue_one(&mut self) -> Result<&mut T, Full> {
+ self.enqueue_one_with(Ok)?
+ }
+
+ /// Call `f` with a single buffer element, and dequeue the element if `f`
+ /// returns successfully, or return `Err(Empty)` if the buffer is empty.
+ pub fn dequeue_one_with<'b, R, E, F>(&'b mut self, f: F) -> Result<Result<R, E>, Empty>
+ where
+ F: FnOnce(&'b mut T) -> Result<R, E>,
+ {
+ if self.is_empty() {
+ return Err(Empty);
+ }
+
+ let next_at = self.get_idx_unchecked(1);
+ let res = f(&mut self.storage[self.read_at]);
+
+ if res.is_ok() {
+ self.length -= 1;
+ self.read_at = next_at;
+ }
+ Ok(res)
+ }
+
+ /// Dequeue an element from the buffer, and return a reference to it,
+ /// or return `Err(Empty)` if the buffer is empty.
+ ///
+ /// This function is a shortcut for `ring_buf.dequeue_one_with(Ok)`.
+ pub fn dequeue_one(&mut self) -> Result<&mut T, Empty> {
+ self.dequeue_one_with(Ok)?
+ }
+}
+
+/// This is the "continuous" ring buffer interface: it operates with element slices,
+/// and boundary conditions (empty/full) simply result in empty slices.
+impl<'a, T: 'a> RingBuffer<'a, T> {
+ /// Call `f` with the largest contiguous slice of unallocated buffer elements,
+ /// and enqueue the amount of elements returned by `f`.
+ ///
+ /// # Panics
+ /// This function panics if the amount of elements returned by `f` is larger
+ /// than the size of the slice passed into it.
+ pub fn enqueue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R)
+ where
+ F: FnOnce(&'b mut [T]) -> (usize, R),
+ {
+ if self.length == 0 {
+ // Ring is currently empty. Reset `read_at` to optimize
+ // for contiguous space.
+ self.read_at = 0;
+ }
+
+ let write_at = self.get_idx(self.length);
+ let max_size = self.contiguous_window();
+ let (size, result) = f(&mut self.storage[write_at..write_at + max_size]);
+ assert!(size <= max_size);
+ self.length += size;
+ (size, result)
+ }
+
+ /// Enqueue a slice of elements up to the given size into the buffer,
+ /// and return a reference to them.
+ ///
+ /// This function may return a slice smaller than the given size
+ /// if the free space in the buffer is not contiguous.
+ #[must_use]
+ pub fn enqueue_many(&mut self, size: usize) -> &mut [T] {
+ self.enqueue_many_with(|buf| {
+ let size = cmp::min(size, buf.len());
+ (size, &mut buf[..size])
+ })
+ .1
+ }
+
+ /// Enqueue as many elements from the given slice into the buffer as possible,
+ /// and return the amount of elements that could fit.
+ #[must_use]
+ pub fn enqueue_slice(&mut self, data: &[T]) -> usize
+ where
+ T: Copy,
+ {
+ let (size_1, data) = self.enqueue_many_with(|buf| {
+ let size = cmp::min(buf.len(), data.len());
+ buf[..size].copy_from_slice(&data[..size]);
+ (size, &data[size..])
+ });
+ let (size_2, ()) = self.enqueue_many_with(|buf| {
+ let size = cmp::min(buf.len(), data.len());
+ buf[..size].copy_from_slice(&data[..size]);
+ (size, ())
+ });
+ size_1 + size_2
+ }
+
+ /// Call `f` with the largest contiguous slice of allocated buffer elements,
+ /// and dequeue the amount of elements returned by `f`.
+ ///
+ /// # Panics
+ /// This function panics if the amount of elements returned by `f` is larger
+ /// than the size of the slice passed into it.
+ pub fn dequeue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R)
+ where
+ F: FnOnce(&'b mut [T]) -> (usize, R),
+ {
+ let capacity = self.capacity();
+ let max_size = cmp::min(self.len(), capacity - self.read_at);
+ let (size, result) = f(&mut self.storage[self.read_at..self.read_at + max_size]);
+ assert!(size <= max_size);
+ self.read_at = if capacity > 0 {
+ (self.read_at + size) % capacity
+ } else {
+ 0
+ };
+ self.length -= size;
+ (size, result)
+ }
+
+ /// Dequeue a slice of elements up to the given size from the buffer,
+ /// and return a reference to them.
+ ///
+ /// This function may return a slice smaller than the given size
+ /// if the allocated space in the buffer is not contiguous.
+ #[must_use]
+ pub fn dequeue_many(&mut self, size: usize) -> &mut [T] {
+ self.dequeue_many_with(|buf| {
+ let size = cmp::min(size, buf.len());
+ (size, &mut buf[..size])
+ })
+ .1
+ }
+
+ /// Dequeue as many elements from the buffer into the given slice as possible,
+ /// and return the amount of elements that could fit.
+ #[must_use]
+ pub fn dequeue_slice(&mut self, data: &mut [T]) -> usize
+ where
+ T: Copy,
+ {
+ let (size_1, data) = self.dequeue_many_with(|buf| {
+ let size = cmp::min(buf.len(), data.len());
+ data[..size].copy_from_slice(&buf[..size]);
+ (size, &mut data[size..])
+ });
+ let (size_2, ()) = self.dequeue_many_with(|buf| {
+ let size = cmp::min(buf.len(), data.len());
+ data[..size].copy_from_slice(&buf[..size]);
+ (size, ())
+ });
+ size_1 + size_2
+ }
+}
+
+/// This is the "random access" ring buffer interface: it operates with element slices,
+/// and allows to access elements of the buffer that are not adjacent to its head or tail.
+impl<'a, T: 'a> RingBuffer<'a, T> {
+ /// Return the largest contiguous slice of unallocated buffer elements starting
+ /// at the given offset past the last allocated element, and up to the given size.
+ #[must_use]
+ pub fn get_unallocated(&mut self, offset: usize, mut size: usize) -> &mut [T] {
+ let start_at = self.get_idx(self.length + offset);
+ // We can't access past the end of unallocated data.
+ if offset > self.window() {
+ return &mut [];
+ }
+ // We can't enqueue more than there is free space.
+ let clamped_window = self.window() - offset;
+ if size > clamped_window {
+ size = clamped_window
+ }
+ // We can't contiguously enqueue past the end of the storage.
+ let until_end = self.capacity() - start_at;
+ if size > until_end {
+ size = until_end
+ }
+
+ &mut self.storage[start_at..start_at + size]
+ }
+
+ /// Write as many elements from the given slice into unallocated buffer elements
+ /// starting at the given offset past the last allocated element, and return
+ /// the amount written.
+ #[must_use]
+ pub fn write_unallocated(&mut self, offset: usize, data: &[T]) -> usize
+ where
+ T: Copy,
+ {
+ let (size_1, offset, data) = {
+ let slice = self.get_unallocated(offset, data.len());
+ let slice_len = slice.len();
+ slice.copy_from_slice(&data[..slice_len]);
+ (slice_len, offset + slice_len, &data[slice_len..])
+ };
+ let size_2 = {
+ let slice = self.get_unallocated(offset, data.len());
+ let slice_len = slice.len();
+ slice.copy_from_slice(&data[..slice_len]);
+ slice_len
+ };
+ size_1 + size_2
+ }
+
+ /// Enqueue the given number of unallocated buffer elements.
+ ///
+ /// # Panics
+ /// Panics if the number of elements given exceeds the number of unallocated elements.
+ pub fn enqueue_unallocated(&mut self, count: usize) {
+ assert!(count <= self.window());
+ self.length += count;
+ }
+
+ /// Return the largest contiguous slice of allocated buffer elements starting
+ /// at the given offset past the first allocated element, and up to the given size.
+ #[must_use]
+ pub fn get_allocated(&self, offset: usize, mut size: usize) -> &[T] {
+ let start_at = self.get_idx(offset);
+ // We can't read past the end of the allocated data.
+ if offset > self.length {
+ return &mut [];
+ }
+ // We can't read more than we have allocated.
+ let clamped_length = self.length - offset;
+ if size > clamped_length {
+ size = clamped_length
+ }
+ // We can't contiguously dequeue past the end of the storage.
+ let until_end = self.capacity() - start_at;
+ if size > until_end {
+ size = until_end
+ }
+
+ &self.storage[start_at..start_at + size]
+ }
+
+ /// Read as many elements from allocated buffer elements into the given slice
+ /// starting at the given offset past the first allocated element, and return
+ /// the amount read.
+ #[must_use]
+ pub fn read_allocated(&mut self, offset: usize, data: &mut [T]) -> usize
+ where
+ T: Copy,
+ {
+ let (size_1, offset, data) = {
+ let slice = self.get_allocated(offset, data.len());
+ data[..slice.len()].copy_from_slice(slice);
+ (slice.len(), offset + slice.len(), &mut data[slice.len()..])
+ };
+ let size_2 = {
+ let slice = self.get_allocated(offset, data.len());
+ data[..slice.len()].copy_from_slice(slice);
+ slice.len()
+ };
+ size_1 + size_2
+ }
+
+ /// Dequeue the given number of allocated buffer elements.
+ ///
+ /// # Panics
+ /// Panics if the number of elements given exceeds the number of allocated elements.
+ pub fn dequeue_allocated(&mut self, count: usize) {
+ assert!(count <= self.len());
+ self.length -= count;
+ self.read_at = self.get_idx(count);
+ }
+}
+
+impl<'a, T: 'a> From<ManagedSlice<'a, T>> for RingBuffer<'a, T> {
+ fn from(slice: ManagedSlice<'a, T>) -> RingBuffer<'a, T> {
+ RingBuffer::new(slice)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_buffer_length_changes() {
+ let mut ring = RingBuffer::new(vec![0; 2]);
+ assert!(ring.is_empty());
+ assert!(!ring.is_full());
+ assert_eq!(ring.len(), 0);
+ assert_eq!(ring.capacity(), 2);
+ assert_eq!(ring.window(), 2);
+
+ ring.length = 1;
+ assert!(!ring.is_empty());
+ assert!(!ring.is_full());
+ assert_eq!(ring.len(), 1);
+ assert_eq!(ring.capacity(), 2);
+ assert_eq!(ring.window(), 1);
+
+ ring.length = 2;
+ assert!(!ring.is_empty());
+ assert!(ring.is_full());
+ assert_eq!(ring.len(), 2);
+ assert_eq!(ring.capacity(), 2);
+ assert_eq!(ring.window(), 0);
+ }
+
+ #[test]
+ fn test_buffer_enqueue_dequeue_one_with() {
+ let mut ring = RingBuffer::new(vec![0; 5]);
+ assert_eq!(
+ ring.dequeue_one_with(|_| -> Result::<(), ()> { unreachable!() }),
+ Err(Empty)
+ );
+
+ ring.enqueue_one_with(Ok::<_, ()>).unwrap().unwrap();
+ assert!(!ring.is_empty());
+ assert!(!ring.is_full());
+
+ for i in 1..5 {
+ ring.enqueue_one_with(|e| Ok::<_, ()>(*e = i))
+ .unwrap()
+ .unwrap();
+ assert!(!ring.is_empty());
+ }
+ assert!(ring.is_full());
+ assert_eq!(
+ ring.enqueue_one_with(|_| -> Result::<(), ()> { unreachable!() }),
+ Err(Full)
+ );
+
+ for i in 0..5 {
+ assert_eq!(
+ ring.dequeue_one_with(|e| Ok::<_, ()>(*e)).unwrap().unwrap(),
+ i
+ );
+ assert!(!ring.is_full());
+ }
+ assert_eq!(
+ ring.dequeue_one_with(|_| -> Result::<(), ()> { unreachable!() }),
+ Err(Empty)
+ );
+ assert!(ring.is_empty());
+ }
+
+ #[test]
+ fn test_buffer_enqueue_dequeue_one() {
+ let mut ring = RingBuffer::new(vec![0; 5]);
+ assert_eq!(ring.dequeue_one(), Err(Empty));
+
+ ring.enqueue_one().unwrap();
+ assert!(!ring.is_empty());
+ assert!(!ring.is_full());
+
+ for i in 1..5 {
+ *ring.enqueue_one().unwrap() = i;
+ assert!(!ring.is_empty());
+ }
+ assert!(ring.is_full());
+ assert_eq!(ring.enqueue_one(), Err(Full));
+
+ for i in 0..5 {
+ assert_eq!(*ring.dequeue_one().unwrap(), i);
+ assert!(!ring.is_full());
+ }
+ assert_eq!(ring.dequeue_one(), Err(Empty));
+ assert!(ring.is_empty());
+ }
+
+ #[test]
+ fn test_buffer_enqueue_many_with() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(
+ ring.enqueue_many_with(|buf| {
+ assert_eq!(buf.len(), 12);
+ buf[0..2].copy_from_slice(b"ab");
+ (2, true)
+ }),
+ (2, true)
+ );
+ assert_eq!(ring.len(), 2);
+ assert_eq!(&ring.storage[..], b"ab..........");
+
+ ring.enqueue_many_with(|buf| {
+ assert_eq!(buf.len(), 12 - 2);
+ buf[0..4].copy_from_slice(b"cdXX");
+ (2, ())
+ });
+ assert_eq!(ring.len(), 4);
+ assert_eq!(&ring.storage[..], b"abcdXX......");
+
+ ring.enqueue_many_with(|buf| {
+ assert_eq!(buf.len(), 12 - 4);
+ buf[0..4].copy_from_slice(b"efgh");
+ (4, ())
+ });
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"abcdefgh....");
+
+ for _ in 0..4 {
+ *ring.dequeue_one().unwrap() = b'.';
+ }
+ assert_eq!(ring.len(), 4);
+ assert_eq!(&ring.storage[..], b"....efgh....");
+
+ ring.enqueue_many_with(|buf| {
+ assert_eq!(buf.len(), 12 - 8);
+ buf[0..4].copy_from_slice(b"ijkl");
+ (4, ())
+ });
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"....efghijkl");
+
+ ring.enqueue_many_with(|buf| {
+ assert_eq!(buf.len(), 4);
+ buf[0..4].copy_from_slice(b"abcd");
+ (4, ())
+ });
+ assert_eq!(ring.len(), 12);
+ assert_eq!(&ring.storage[..], b"abcdefghijkl");
+
+ for _ in 0..4 {
+ *ring.dequeue_one().unwrap() = b'.';
+ }
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"abcd....ijkl");
+ }
+
+ #[test]
+ fn test_buffer_enqueue_many() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ ring.enqueue_many(8).copy_from_slice(b"abcdefgh");
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"abcdefgh....");
+
+ ring.enqueue_many(8).copy_from_slice(b"ijkl");
+ assert_eq!(ring.len(), 12);
+ assert_eq!(&ring.storage[..], b"abcdefghijkl");
+ }
+
+ #[test]
+ fn test_buffer_enqueue_slice() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.enqueue_slice(b"abcdefgh"), 8);
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"abcdefgh....");
+
+ for _ in 0..4 {
+ *ring.dequeue_one().unwrap() = b'.';
+ }
+ assert_eq!(ring.len(), 4);
+ assert_eq!(&ring.storage[..], b"....efgh....");
+
+ assert_eq!(ring.enqueue_slice(b"ijklabcd"), 8);
+ assert_eq!(ring.len(), 12);
+ assert_eq!(&ring.storage[..], b"abcdefghijkl");
+ }
+
+ #[test]
+ fn test_buffer_dequeue_many_with() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12);
+
+ assert_eq!(
+ ring.dequeue_many_with(|buf| {
+ assert_eq!(buf.len(), 12);
+ assert_eq!(buf, b"abcdefghijkl");
+ buf[..4].copy_from_slice(b"....");
+ (4, true)
+ }),
+ (4, true)
+ );
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"....efghijkl");
+
+ ring.dequeue_many_with(|buf| {
+ assert_eq!(buf, b"efghijkl");
+ buf[..4].copy_from_slice(b"....");
+ (4, ())
+ });
+ assert_eq!(ring.len(), 4);
+ assert_eq!(&ring.storage[..], b"........ijkl");
+
+ assert_eq!(ring.enqueue_slice(b"abcd"), 4);
+ assert_eq!(ring.len(), 8);
+
+ ring.dequeue_many_with(|buf| {
+ assert_eq!(buf, b"ijkl");
+ buf[..4].copy_from_slice(b"....");
+ (4, ())
+ });
+ ring.dequeue_many_with(|buf| {
+ assert_eq!(buf, b"abcd");
+ buf[..4].copy_from_slice(b"....");
+ (4, ())
+ });
+ assert_eq!(ring.len(), 0);
+ assert_eq!(&ring.storage[..], b"............");
+ }
+
+ #[test]
+ fn test_buffer_dequeue_many() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12);
+
+ {
+ let buf = ring.dequeue_many(8);
+ assert_eq!(buf, b"abcdefgh");
+ buf.copy_from_slice(b"........");
+ }
+ assert_eq!(ring.len(), 4);
+ assert_eq!(&ring.storage[..], b"........ijkl");
+
+ {
+ let buf = ring.dequeue_many(8);
+ assert_eq!(buf, b"ijkl");
+ buf.copy_from_slice(b"....");
+ }
+ assert_eq!(ring.len(), 0);
+ assert_eq!(&ring.storage[..], b"............");
+ }
+
+ #[test]
+ fn test_buffer_dequeue_slice() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12);
+
+ {
+ let mut buf = [0; 8];
+ assert_eq!(ring.dequeue_slice(&mut buf[..]), 8);
+ assert_eq!(&buf[..], b"abcdefgh");
+ assert_eq!(ring.len(), 4);
+ }
+
+ assert_eq!(ring.enqueue_slice(b"abcd"), 4);
+
+ {
+ let mut buf = [0; 8];
+ assert_eq!(ring.dequeue_slice(&mut buf[..]), 8);
+ assert_eq!(&buf[..], b"ijklabcd");
+ assert_eq!(ring.len(), 0);
+ }
+ }
+
+ #[test]
+ fn test_buffer_get_unallocated() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.get_unallocated(16, 4), b"");
+
+ {
+ let buf = ring.get_unallocated(0, 4);
+ buf.copy_from_slice(b"abcd");
+ }
+ assert_eq!(&ring.storage[..], b"abcd........");
+
+ let buf_enqueued = ring.enqueue_many(4);
+ assert_eq!(buf_enqueued.len(), 4);
+ assert_eq!(ring.len(), 4);
+
+ {
+ let buf = ring.get_unallocated(4, 8);
+ buf.copy_from_slice(b"ijkl");
+ }
+ assert_eq!(&ring.storage[..], b"abcd....ijkl");
+
+ ring.enqueue_many(8).copy_from_slice(b"EFGHIJKL");
+ ring.dequeue_many(4).copy_from_slice(b"abcd");
+ assert_eq!(ring.len(), 8);
+ assert_eq!(&ring.storage[..], b"abcdEFGHIJKL");
+
+ {
+ let buf = ring.get_unallocated(0, 8);
+ buf.copy_from_slice(b"ABCD");
+ }
+ assert_eq!(&ring.storage[..], b"ABCDEFGHIJKL");
+ }
+
+ #[test]
+ fn test_buffer_write_unallocated() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+ ring.enqueue_many(6).copy_from_slice(b"abcdef");
+ ring.dequeue_many(6).copy_from_slice(b"ABCDEF");
+
+ assert_eq!(ring.write_unallocated(0, b"ghi"), 3);
+ assert_eq!(ring.get_unallocated(0, 3), b"ghi");
+
+ assert_eq!(ring.write_unallocated(3, b"jklmno"), 6);
+ assert_eq!(ring.get_unallocated(3, 3), b"jkl");
+
+ assert_eq!(ring.write_unallocated(9, b"pqrstu"), 3);
+ assert_eq!(ring.get_unallocated(9, 3), b"pqr");
+ }
+
+ #[test]
+ fn test_buffer_get_allocated() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+
+ assert_eq!(ring.get_allocated(16, 4), b"");
+ assert_eq!(ring.get_allocated(0, 4), b"");
+
+ let len_enqueued = ring.enqueue_slice(b"abcd");
+ assert_eq!(ring.get_allocated(0, 8), b"abcd");
+ assert_eq!(len_enqueued, 4);
+
+ let len_enqueued = ring.enqueue_slice(b"efghijkl");
+ ring.dequeue_many(4).copy_from_slice(b"....");
+ assert_eq!(ring.get_allocated(4, 8), b"ijkl");
+ assert_eq!(len_enqueued, 8);
+
+ let len_enqueued = ring.enqueue_slice(b"abcd");
+ assert_eq!(ring.get_allocated(4, 8), b"ijkl");
+ assert_eq!(len_enqueued, 4);
+ }
+
+ #[test]
+ fn test_buffer_read_allocated() {
+ let mut ring = RingBuffer::new(vec![b'.'; 12]);
+ ring.enqueue_many(12).copy_from_slice(b"abcdefghijkl");
+
+ let mut data = [0; 6];
+ assert_eq!(ring.read_allocated(0, &mut data[..]), 6);
+ assert_eq!(&data[..], b"abcdef");
+
+ ring.dequeue_many(6).copy_from_slice(b"ABCDEF");
+ ring.enqueue_many(3).copy_from_slice(b"mno");
+
+ let mut data = [0; 6];
+ assert_eq!(ring.read_allocated(3, &mut data[..]), 6);
+ assert_eq!(&data[..], b"jklmno");
+
+ let mut data = [0; 6];
+ assert_eq!(ring.read_allocated(6, &mut data[..]), 3);
+ assert_eq!(&data[..], b"mno\x00\x00\x00");
+ }
+
+ #[test]
+ fn test_buffer_with_no_capacity() {
+ let mut no_capacity: RingBuffer<u8> = RingBuffer::new(vec![]);
+
+ // Call all functions that calculate the remainder against rx_buffer.capacity()
+ // with a backing storage with a length of 0.
+ assert_eq!(no_capacity.get_unallocated(0, 0), &[]);
+ assert_eq!(no_capacity.get_allocated(0, 0), &[]);
+ no_capacity.dequeue_allocated(0);
+ assert_eq!(no_capacity.enqueue_many(0), &[]);
+ assert_eq!(no_capacity.enqueue_one(), Err(Full));
+ assert_eq!(no_capacity.contiguous_window(), 0);
+ }
+
+ /// Use the buffer a bit. Then empty it and put in an item of
+ /// maximum size. By detecting a length of 0, the implementation
+ /// can reset the current buffer position.
+ #[test]
+ fn test_buffer_write_wholly() {
+ let mut ring = RingBuffer::new(vec![b'.'; 8]);
+ ring.enqueue_many(2).copy_from_slice(b"ab");
+ ring.enqueue_many(2).copy_from_slice(b"cd");
+ assert_eq!(ring.len(), 4);
+ let buf_dequeued = ring.dequeue_many(4);
+ assert_eq!(buf_dequeued, b"abcd");
+ assert_eq!(ring.len(), 0);
+
+ let large = ring.enqueue_many(8);
+ assert_eq!(large.len(), 8);
+ }
+}
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644
index 0000000..ec026ab
--- /dev/null
+++ b/src/tests.rs
@@ -0,0 +1,148 @@
+use crate::iface::*;
+use crate::wire::*;
+
+pub(crate) fn setup<'a>(medium: Medium) -> (Interface, SocketSet<'a>, TestingDevice) {
+ let mut device = TestingDevice::new(medium);
+
+ let config = Config::new(match medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ HardwareAddress::Ethernet(EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]))
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => HardwareAddress::Ip,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::Extended([
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ ])),
+ });
+
+ let mut iface = Interface::new(config, &mut device, Instant::ZERO);
+
+ #[cfg(feature = "proto-ipv4")]
+ {
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(192, 168, 1, 1), 24))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
+ .unwrap();
+ });
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ {
+ iface.update_ip_addrs(|ip_addrs| {
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128))
+ .unwrap();
+ ip_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64))
+ .unwrap();
+ });
+ }
+
+ (iface, SocketSet::new(vec![]), device)
+}
+
+use heapless::Deque;
+use heapless::Vec;
+
+use crate::phy::{self, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A testing device.
+#[derive(Debug)]
+pub struct TestingDevice {
+ pub(crate) queue: Deque<Vec<u8, 1514>, 4>,
+ max_transmission_unit: usize,
+ medium: Medium,
+}
+
+#[allow(clippy::new_without_default)]
+impl TestingDevice {
+ /// Creates a testing device.
+ ///
+ /// Every packet transmitted through this device will be received through it
+ /// in FIFO order.
+ pub fn new(medium: Medium) -> Self {
+ TestingDevice {
+ queue: Deque::new(),
+ max_transmission_unit: match medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => 1514,
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => 1500,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => 1500,
+ },
+ medium,
+ }
+ }
+}
+
+impl Device for TestingDevice {
+ type RxToken<'a> = RxToken;
+ type TxToken<'a> = TxToken<'a>;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ medium: self.medium,
+ max_transmission_unit: self.max_transmission_unit,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ self.queue.pop_front().map(move |buffer| {
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ queue: &mut self.queue,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ queue: &mut self.queue,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8, 1514>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer)
+ }
+}
+
+#[doc(hidden)]
+#[derive(Debug)]
+pub struct TxToken<'a> {
+ queue: &'a mut Deque<Vec<u8, 1514>, 4>,
+}
+
+impl<'a> phy::TxToken for TxToken<'a> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut buffer = Vec::new();
+ buffer.resize(len, 0).unwrap();
+ let result = f(&mut buffer);
+ self.queue.push_back(buffer).unwrap();
+ result
+ }
+}
diff --git a/src/time.rs b/src/time.rs
new file mode 100644
index 0000000..e6904af
--- /dev/null
+++ b/src/time.rs
@@ -0,0 +1,460 @@
+/*! Time structures.
+
+The `time` module contains structures used to represent both
+absolute and relative time.
+
+ - [Instant] is used to represent absolute time.
+ - [Duration] is used to represent relative time.
+
+[Instant]: struct.Instant.html
+[Duration]: struct.Duration.html
+*/
+
+use core::{fmt, ops};
+
+/// A representation of an absolute time value.
+///
+/// The `Instant` type is a wrapper around a `i64` value that
+/// represents a number of microseconds, monotonically increasing
+/// since an arbitrary moment in time, such as system startup.
+///
+/// * A value of `0` is inherently arbitrary.
+/// * A value less than `0` indicates a time before the starting
+/// point.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Instant {
+ micros: i64,
+}
+
+impl Instant {
+ pub const ZERO: Instant = Instant::from_micros_const(0);
+
+ /// Create a new `Instant` from a number of microseconds.
+ pub fn from_micros<T: Into<i64>>(micros: T) -> Instant {
+ Instant {
+ micros: micros.into(),
+ }
+ }
+
+ pub const fn from_micros_const(micros: i64) -> Instant {
+ Instant { micros }
+ }
+
+ /// Create a new `Instant` from a number of milliseconds.
+ pub fn from_millis<T: Into<i64>>(millis: T) -> Instant {
+ Instant {
+ micros: millis.into() * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of milliseconds.
+ pub const fn from_millis_const(millis: i64) -> Instant {
+ Instant {
+ micros: millis * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of seconds.
+ pub fn from_secs<T: Into<i64>>(secs: T) -> Instant {
+ Instant {
+ micros: secs.into() * 1000000,
+ }
+ }
+
+ /// Create a new `Instant` from the current [std::time::SystemTime].
+ ///
+ /// See [std::time::SystemTime::now]
+ ///
+ /// [std::time::SystemTime]: https://doc.rust-lang.org/std/time/struct.SystemTime.html
+ /// [std::time::SystemTime::now]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#method.now
+ #[cfg(feature = "std")]
+ pub fn now() -> Instant {
+ Self::from(::std::time::SystemTime::now())
+ }
+
+ /// The fractional number of milliseconds that have passed
+ /// since the beginning of time.
+ pub const fn millis(&self) -> i64 {
+ self.micros % 1000000 / 1000
+ }
+
+ /// The fractional number of microseconds that have passed
+ /// since the beginning of time.
+ pub const fn micros(&self) -> i64 {
+ self.micros % 1000000
+ }
+
+ /// The number of whole seconds that have passed since the
+ /// beginning of time.
+ pub const fn secs(&self) -> i64 {
+ self.micros / 1000000
+ }
+
+ /// The total number of milliseconds that have passed since
+ /// the beginning of time.
+ pub const fn total_millis(&self) -> i64 {
+ self.micros / 1000
+ }
+ /// The total number of milliseconds that have passed since
+ /// the beginning of time.
+ pub const fn total_micros(&self) -> i64 {
+ self.micros
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::time::Instant> for Instant {
+ fn from(other: ::std::time::Instant) -> Instant {
+ let elapsed = other.elapsed();
+ Instant::from_micros((elapsed.as_secs() * 1_000000) as i64 + elapsed.subsec_micros() as i64)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::time::SystemTime> for Instant {
+ fn from(other: ::std::time::SystemTime) -> Instant {
+ let n = other
+ .duration_since(::std::time::UNIX_EPOCH)
+ .expect("start time must not be before the unix epoch");
+ Self::from_micros(n.as_secs() as i64 * 1000000 + n.subsec_micros() as i64)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Instant> for ::std::time::SystemTime {
+ fn from(val: Instant) -> Self {
+ ::std::time::UNIX_EPOCH + ::std::time::Duration::from_micros(val.micros as u64)
+ }
+}
+
+impl fmt::Display for Instant {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{:0>3}s", self.secs(), self.millis())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Instant {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}.{:03}s", self.secs(), self.millis());
+ }
+}
+
+impl ops::Add<Duration> for Instant {
+ type Output = Instant;
+
+ fn add(self, rhs: Duration) -> Instant {
+ Instant::from_micros(self.micros + rhs.total_micros() as i64)
+ }
+}
+
+impl ops::AddAssign<Duration> for Instant {
+ fn add_assign(&mut self, rhs: Duration) {
+ self.micros += rhs.total_micros() as i64;
+ }
+}
+
+impl ops::Sub<Duration> for Instant {
+ type Output = Instant;
+
+ fn sub(self, rhs: Duration) -> Instant {
+ Instant::from_micros(self.micros - rhs.total_micros() as i64)
+ }
+}
+
+impl ops::SubAssign<Duration> for Instant {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.micros -= rhs.total_micros() as i64;
+ }
+}
+
+impl ops::Sub<Instant> for Instant {
+ type Output = Duration;
+
+ fn sub(self, rhs: Instant) -> Duration {
+ Duration::from_micros((self.micros - rhs.micros).unsigned_abs())
+ }
+}
+
+/// A relative amount of time.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Duration {
+ micros: u64,
+}
+
+impl Duration {
+ pub const ZERO: Duration = Duration::from_micros(0);
+ /// The longest possible duration we can encode.
+ pub const MAX: Duration = Duration::from_micros(u64::MAX);
+ /// Create a new `Duration` from a number of microseconds.
+ pub const fn from_micros(micros: u64) -> Duration {
+ Duration { micros }
+ }
+
+ /// Create a new `Duration` from a number of milliseconds.
+ pub const fn from_millis(millis: u64) -> Duration {
+ Duration {
+ micros: millis * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of seconds.
+ pub const fn from_secs(secs: u64) -> Duration {
+ Duration {
+ micros: secs * 1000000,
+ }
+ }
+
+ /// The fractional number of milliseconds in this `Duration`.
+ pub const fn millis(&self) -> u64 {
+ self.micros / 1000 % 1000
+ }
+
+ /// The fractional number of milliseconds in this `Duration`.
+ pub const fn micros(&self) -> u64 {
+ self.micros % 1000000
+ }
+
+ /// The number of whole seconds in this `Duration`.
+ pub const fn secs(&self) -> u64 {
+ self.micros / 1000000
+ }
+
+ /// The total number of milliseconds in this `Duration`.
+ pub const fn total_millis(&self) -> u64 {
+ self.micros / 1000
+ }
+
+ /// The total number of microseconds in this `Duration`.
+ pub const fn total_micros(&self) -> u64 {
+ self.micros
+ }
+}
+
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{:03}s", self.secs(), self.millis())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Duration {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}.{:03}s", self.secs(), self.millis());
+ }
+}
+
+impl ops::Add<Duration> for Duration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Duration {
+ Duration::from_micros(self.micros + rhs.total_micros())
+ }
+}
+
+impl ops::AddAssign<Duration> for Duration {
+ fn add_assign(&mut self, rhs: Duration) {
+ self.micros += rhs.total_micros();
+ }
+}
+
+impl ops::Sub<Duration> for Duration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Duration {
+ Duration::from_micros(
+ self.micros
+ .checked_sub(rhs.total_micros())
+ .expect("overflow when subtracting durations"),
+ )
+ }
+}
+
+impl ops::SubAssign<Duration> for Duration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.micros = self
+ .micros
+ .checked_sub(rhs.total_micros())
+ .expect("overflow when subtracting durations");
+ }
+}
+
+impl ops::Mul<u32> for Duration {
+ type Output = Duration;
+
+ fn mul(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros * rhs as u64)
+ }
+}
+
+impl ops::MulAssign<u32> for Duration {
+ fn mul_assign(&mut self, rhs: u32) {
+ self.micros *= rhs as u64;
+ }
+}
+
+impl ops::Div<u32> for Duration {
+ type Output = Duration;
+
+ fn div(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros / rhs as u64)
+ }
+}
+
+impl ops::DivAssign<u32> for Duration {
+ fn div_assign(&mut self, rhs: u32) {
+ self.micros /= rhs as u64;
+ }
+}
+
+impl ops::Shl<u32> for Duration {
+ type Output = Duration;
+
+ fn shl(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros << rhs)
+ }
+}
+
+impl ops::ShlAssign<u32> for Duration {
+ fn shl_assign(&mut self, rhs: u32) {
+ self.micros <<= rhs;
+ }
+}
+
+impl ops::Shr<u32> for Duration {
+ type Output = Duration;
+
+ fn shr(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros >> rhs)
+ }
+}
+
+impl ops::ShrAssign<u32> for Duration {
+ fn shr_assign(&mut self, rhs: u32) {
+ self.micros >>= rhs;
+ }
+}
+
+impl From<::core::time::Duration> for Duration {
+ fn from(other: ::core::time::Duration) -> Duration {
+ Duration::from_micros(other.as_secs() * 1000000 + other.subsec_micros() as u64)
+ }
+}
+
+impl From<Duration> for ::core::time::Duration {
+ fn from(val: Duration) -> Self {
+ ::core::time::Duration::from_micros(val.total_micros())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_instant_ops() {
+ // std::ops::Add
+ assert_eq!(
+ Instant::from_millis(4) + Duration::from_millis(6),
+ Instant::from_millis(10)
+ );
+ // std::ops::Sub
+ assert_eq!(
+ Instant::from_millis(7) - Duration::from_millis(5),
+ Instant::from_millis(2)
+ );
+ }
+
+ #[test]
+ fn test_instant_getters() {
+ let instant = Instant::from_millis(5674);
+ assert_eq!(instant.secs(), 5);
+ assert_eq!(instant.millis(), 674);
+ assert_eq!(instant.total_millis(), 5674);
+ }
+
+ #[test]
+ fn test_instant_display() {
+ assert_eq!(format!("{}", Instant::from_millis(74)), "0.074s");
+ assert_eq!(format!("{}", Instant::from_millis(5674)), "5.674s");
+ assert_eq!(format!("{}", Instant::from_millis(5000)), "5.000s");
+ }
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_instant_conversions() {
+ let mut epoc: ::std::time::SystemTime = Instant::from_millis(0).into();
+ assert_eq!(
+ Instant::from(::std::time::UNIX_EPOCH),
+ Instant::from_millis(0)
+ );
+ assert_eq!(epoc, ::std::time::UNIX_EPOCH);
+ epoc = Instant::from_millis(2085955200i64 * 1000).into();
+ assert_eq!(
+ epoc,
+ ::std::time::UNIX_EPOCH + ::std::time::Duration::from_secs(2085955200)
+ );
+ }
+
+ #[test]
+ fn test_duration_ops() {
+ // std::ops::Add
+ assert_eq!(
+ Duration::from_millis(40) + Duration::from_millis(2),
+ Duration::from_millis(42)
+ );
+ // std::ops::Sub
+ assert_eq!(
+ Duration::from_millis(555) - Duration::from_millis(42),
+ Duration::from_millis(513)
+ );
+ // std::ops::Mul
+ assert_eq!(Duration::from_millis(13) * 22, Duration::from_millis(286));
+ // std::ops::Div
+ assert_eq!(Duration::from_millis(53) / 4, Duration::from_micros(13250));
+ }
+
+ #[test]
+ fn test_duration_assign_ops() {
+ let mut duration = Duration::from_millis(4735);
+ duration += Duration::from_millis(1733);
+ assert_eq!(duration, Duration::from_millis(6468));
+ duration -= Duration::from_millis(1234);
+ assert_eq!(duration, Duration::from_millis(5234));
+ duration *= 4;
+ assert_eq!(duration, Duration::from_millis(20936));
+ duration /= 5;
+ assert_eq!(duration, Duration::from_micros(4187200));
+ }
+
+ #[test]
+ #[should_panic(expected = "overflow when subtracting durations")]
+ fn test_sub_from_zero_overflow() {
+ let _ = Duration::from_millis(0) - Duration::from_millis(1);
+ }
+
+ #[test]
+ #[should_panic(expected = "attempt to divide by zero")]
+ fn test_div_by_zero() {
+ let _ = Duration::from_millis(4) / 0;
+ }
+
+ #[test]
+ fn test_duration_getters() {
+ let instant = Duration::from_millis(4934);
+ assert_eq!(instant.secs(), 4);
+ assert_eq!(instant.millis(), 934);
+ assert_eq!(instant.total_millis(), 4934);
+ }
+
+ #[test]
+ fn test_duration_conversions() {
+ let mut std_duration = ::core::time::Duration::from_millis(4934);
+ let duration: Duration = std_duration.into();
+ assert_eq!(duration, Duration::from_millis(4934));
+ assert_eq!(Duration::from(std_duration), Duration::from_millis(4934));
+
+ std_duration = duration.into();
+ assert_eq!(std_duration, ::core::time::Duration::from_millis(4934));
+ }
+}
diff --git a/src/wire/arp.rs b/src/wire/arp.rs
new file mode 100644
index 0000000..bb0df3a
--- /dev/null
+++ b/src/wire/arp.rs
@@ -0,0 +1,458 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+
+pub use super::EthernetProtocol as Protocol;
+
+enum_with_unknown! {
+ /// ARP hardware type.
+ pub enum Hardware(u16) {
+ Ethernet = 1
+ }
+}
+
+enum_with_unknown! {
+ /// ARP operation type.
+ pub enum Operation(u16) {
+ Request = 1,
+ Reply = 2
+ }
+}
+
+/// A read/write wrapper around an Address Resolution Protocol packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ pub const HTYPE: Field = 0..2;
+ pub const PTYPE: Field = 2..4;
+ pub const HLEN: usize = 4;
+ pub const PLEN: usize = 5;
+ pub const OPER: Field = 6..8;
+
+ #[inline]
+ pub const fn SHA(hardware_len: u8, _protocol_len: u8) -> Field {
+ let start = OPER.end;
+ start..(start + hardware_len as usize)
+ }
+
+ #[inline]
+ pub const fn SPA(hardware_len: u8, protocol_len: u8) -> Field {
+ let start = SHA(hardware_len, protocol_len).end;
+ start..(start + protocol_len as usize)
+ }
+
+ #[inline]
+ pub const fn THA(hardware_len: u8, protocol_len: u8) -> Field {
+ let start = SPA(hardware_len, protocol_len).end;
+ start..(start + hardware_len as usize)
+ }
+
+ #[inline]
+ pub const fn TPA(hardware_len: u8, protocol_len: u8) -> Field {
+ let start = THA(hardware_len, protocol_len).end;
+ start..(start + protocol_len as usize)
+ }
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with ARP packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_hardware_len] or
+ /// [set_protocol_len].
+ ///
+ /// [set_hardware_len]: #method.set_hardware_len
+ /// [set_protocol_len]: #method.set_protocol_len
+ #[allow(clippy::if_same_then_else)]
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::OPER.end {
+ Err(Error)
+ } else if len < field::TPA(self.hardware_len(), self.protocol_len()).end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the hardware type field.
+ #[inline]
+ pub fn hardware_type(&self) -> Hardware {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::HTYPE]);
+ Hardware::from(raw)
+ }
+
+ /// Return the protocol type field.
+ #[inline]
+ pub fn protocol_type(&self) -> Protocol {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::PTYPE]);
+ Protocol::from(raw)
+ }
+
+ /// Return the hardware length field.
+ #[inline]
+ pub fn hardware_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::HLEN]
+ }
+
+ /// Return the protocol length field.
+ #[inline]
+ pub fn protocol_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::PLEN]
+ }
+
+ /// Return the operation field.
+ #[inline]
+ pub fn operation(&self) -> Operation {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::OPER]);
+ Operation::from(raw)
+ }
+
+ /// Return the source hardware address field.
+ pub fn source_hardware_addr(&self) -> &[u8] {
+ let data = self.buffer.as_ref();
+ &data[field::SHA(self.hardware_len(), self.protocol_len())]
+ }
+
+ /// Return the source protocol address field.
+ pub fn source_protocol_addr(&self) -> &[u8] {
+ let data = self.buffer.as_ref();
+ &data[field::SPA(self.hardware_len(), self.protocol_len())]
+ }
+
+ /// Return the target hardware address field.
+ pub fn target_hardware_addr(&self) -> &[u8] {
+ let data = self.buffer.as_ref();
+ &data[field::THA(self.hardware_len(), self.protocol_len())]
+ }
+
+ /// Return the target protocol address field.
+ pub fn target_protocol_addr(&self) -> &[u8] {
+ let data = self.buffer.as_ref();
+ &data[field::TPA(self.hardware_len(), self.protocol_len())]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the hardware type field.
+ #[inline]
+ pub fn set_hardware_type(&mut self, value: Hardware) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::HTYPE], value.into())
+ }
+
+ /// Set the protocol type field.
+ #[inline]
+ pub fn set_protocol_type(&mut self, value: Protocol) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::PTYPE], value.into())
+ }
+
+ /// Set the hardware length field.
+ #[inline]
+ pub fn set_hardware_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::HLEN] = value
+ }
+
+ /// Set the protocol length field.
+ #[inline]
+ pub fn set_protocol_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::PLEN] = value
+ }
+
+ /// Set the operation field.
+ #[inline]
+ pub fn set_operation(&mut self, value: Operation) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::OPER], value.into())
+ }
+
+ /// Set the source hardware address field.
+ ///
+ /// # Panics
+ /// The function panics if `value` is not `self.hardware_len()` long.
+ pub fn set_source_hardware_addr(&mut self, value: &[u8]) {
+ let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len());
+ let data = self.buffer.as_mut();
+ data[field::SHA(hardware_len, protocol_len)].copy_from_slice(value)
+ }
+
+ /// Set the source protocol address field.
+ ///
+ /// # Panics
+ /// The function panics if `value` is not `self.protocol_len()` long.
+ pub fn set_source_protocol_addr(&mut self, value: &[u8]) {
+ let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len());
+ let data = self.buffer.as_mut();
+ data[field::SPA(hardware_len, protocol_len)].copy_from_slice(value)
+ }
+
+ /// Set the target hardware address field.
+ ///
+ /// # Panics
+ /// The function panics if `value` is not `self.hardware_len()` long.
+ pub fn set_target_hardware_addr(&mut self, value: &[u8]) {
+ let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len());
+ let data = self.buffer.as_mut();
+ data[field::THA(hardware_len, protocol_len)].copy_from_slice(value)
+ }
+
+ /// Set the target protocol address field.
+ ///
+ /// # Panics
+ /// The function panics if `value` is not `self.protocol_len()` long.
+ pub fn set_target_protocol_addr(&mut self, value: &[u8]) {
+ let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len());
+ let data = self.buffer.as_mut();
+ data[field::TPA(hardware_len, protocol_len)].copy_from_slice(value)
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+use crate::wire::{EthernetAddress, Ipv4Address};
+
+/// A high-level representation of an Address Resolution Protocol packet.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Repr {
+ /// An Ethernet and IPv4 Address Resolution Protocol packet.
+ EthernetIpv4 {
+ operation: Operation,
+ source_hardware_addr: EthernetAddress,
+ source_protocol_addr: Ipv4Address,
+ target_hardware_addr: EthernetAddress,
+ target_protocol_addr: Ipv4Address,
+ },
+}
+
+impl Repr {
+ /// Parse an Address Resolution Protocol packet and return a high-level representation,
+ /// or return `Err(Error)` if the packet is not recognized.
+ pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> {
+ match (
+ packet.hardware_type(),
+ packet.protocol_type(),
+ packet.hardware_len(),
+ packet.protocol_len(),
+ ) {
+ (Hardware::Ethernet, Protocol::Ipv4, 6, 4) => Ok(Repr::EthernetIpv4 {
+ operation: packet.operation(),
+ source_hardware_addr: EthernetAddress::from_bytes(packet.source_hardware_addr()),
+ source_protocol_addr: Ipv4Address::from_bytes(packet.source_protocol_addr()),
+ target_hardware_addr: EthernetAddress::from_bytes(packet.target_hardware_addr()),
+ target_protocol_addr: Ipv4Address::from_bytes(packet.target_protocol_addr()),
+ }),
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ match *self {
+ Repr::EthernetIpv4 { .. } => field::TPA(6, 4).end,
+ }
+ }
+
+ /// Emit a high-level representation into an Address Resolution Protocol packet.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+ match *self {
+ Repr::EthernetIpv4 {
+ operation,
+ source_hardware_addr,
+ source_protocol_addr,
+ target_hardware_addr,
+ target_protocol_addr,
+ } => {
+ packet.set_hardware_type(Hardware::Ethernet);
+ packet.set_protocol_type(Protocol::Ipv4);
+ packet.set_hardware_len(6);
+ packet.set_protocol_len(4);
+ packet.set_operation(operation);
+ packet.set_source_hardware_addr(source_hardware_addr.as_bytes());
+ packet.set_source_protocol_addr(source_protocol_addr.as_bytes());
+ packet.set_target_hardware_addr(target_hardware_addr.as_bytes());
+ packet.set_target_protocol_addr(target_protocol_addr.as_bytes());
+ }
+ }
+ }
+}
+
+impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ _ => {
+ write!(f, "ARP (unrecognized)")?;
+ write!(
+ f,
+ " htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}",
+ self.hardware_type(),
+ self.protocol_type(),
+ self.hardware_len(),
+ self.protocol_len(),
+ self.operation()
+ )?;
+ write!(
+ f,
+ " sha={:?} spa={:?} tha={:?} tpa={:?}",
+ self.source_hardware_addr(),
+ self.source_protocol_addr(),
+ self.target_hardware_addr(),
+ self.target_protocol_addr()
+ )?;
+ Ok(())
+ }
+ }
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Repr::EthernetIpv4 {
+ operation,
+ source_hardware_addr,
+ source_protocol_addr,
+ target_hardware_addr,
+ target_protocol_addr,
+ } => {
+ write!(
+ f,
+ "ARP type=Ethernet+IPv4 src={source_hardware_addr}/{source_protocol_addr} tgt={target_hardware_addr}/{target_protocol_addr} op={operation:?}"
+ )
+ }
+ }
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ match Packet::new_checked(buffer) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(packet) => write!(f, "{indent}{packet}"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static PACKET_BYTES: [u8; 28] = [
+ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x21,
+ 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x41, 0x42, 0x43, 0x44,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(packet.hardware_type(), Hardware::Ethernet);
+ assert_eq!(packet.protocol_type(), Protocol::Ipv4);
+ assert_eq!(packet.hardware_len(), 6);
+ assert_eq!(packet.protocol_len(), 4);
+ assert_eq!(packet.operation(), Operation::Request);
+ assert_eq!(
+ packet.source_hardware_addr(),
+ &[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]
+ );
+ assert_eq!(packet.source_protocol_addr(), &[0x21, 0x22, 0x23, 0x24]);
+ assert_eq!(
+ packet.target_hardware_addr(),
+ &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]
+ );
+ assert_eq!(packet.target_protocol_addr(), &[0x41, 0x42, 0x43, 0x44]);
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 28];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_hardware_type(Hardware::Ethernet);
+ packet.set_protocol_type(Protocol::Ipv4);
+ packet.set_hardware_len(6);
+ packet.set_protocol_len(4);
+ packet.set_operation(Operation::Request);
+ packet.set_source_hardware_addr(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]);
+ packet.set_source_protocol_addr(&[0x21, 0x22, 0x23, 0x24]);
+ packet.set_target_hardware_addr(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]);
+ packet.set_target_protocol_addr(&[0x41, 0x42, 0x43, 0x44]);
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+
+ fn packet_repr() -> Repr {
+ Repr::EthernetIpv4 {
+ operation: Operation::Request,
+ source_hardware_addr: EthernetAddress::from_bytes(&[
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ ]),
+ source_protocol_addr: Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
+ target_hardware_addr: EthernetAddress::from_bytes(&[
+ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ ]),
+ target_protocol_addr: Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44]),
+ }
+ }
+
+ #[test]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ let repr = Repr::parse(&packet).unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+
+ #[test]
+ fn test_emit() {
+ let mut bytes = vec![0xa5; 28];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet_repr().emit(&mut packet);
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+}
diff --git a/src/wire/dhcpv4.rs b/src/wire/dhcpv4.rs
new file mode 100644
index 0000000..cae9129
--- /dev/null
+++ b/src/wire/dhcpv4.rs
@@ -0,0 +1,1315 @@
+// See https://tools.ietf.org/html/rfc2131 for the DHCP specification.
+
+use bitflags::bitflags;
+use byteorder::{ByteOrder, NetworkEndian};
+use core::iter;
+use heapless::Vec;
+
+use super::{Error, Result};
+use crate::wire::arp::Hardware;
+use crate::wire::{EthernetAddress, Ipv4Address};
+
+pub const SERVER_PORT: u16 = 67;
+pub const CLIENT_PORT: u16 = 68;
+pub const MAX_DNS_SERVER_COUNT: usize = 3;
+
+const DHCP_MAGIC_NUMBER: u32 = 0x63825363;
+
+enum_with_unknown! {
+ /// The possible opcodes of a DHCP packet.
+ pub enum OpCode(u8) {
+ Request = 1,
+ Reply = 2,
+ }
+}
+
+enum_with_unknown! {
+ /// The possible message types of a DHCP packet.
+ pub enum MessageType(u8) {
+ Discover = 1,
+ Offer = 2,
+ Request = 3,
+ Decline = 4,
+ Ack = 5,
+ Nak = 6,
+ Release = 7,
+ Inform = 8,
+ }
+}
+
+bitflags! {
+ pub struct Flags: u16 {
+ const BROADCAST = 0b1000_0000_0000_0000;
+ }
+}
+
+impl MessageType {
+ const fn opcode(&self) -> OpCode {
+ match *self {
+ MessageType::Discover
+ | MessageType::Inform
+ | MessageType::Request
+ | MessageType::Decline
+ | MessageType::Release => OpCode::Request,
+ MessageType::Offer | MessageType::Ack | MessageType::Nak => OpCode::Reply,
+ MessageType::Unknown(_) => OpCode::Unknown(0),
+ }
+ }
+}
+
+/// A buffer for DHCP options.
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DhcpOptionWriter<'a> {
+ /// The underlying buffer, directly from the DHCP packet representation.
+ buffer: &'a mut [u8],
+}
+
+impl<'a> DhcpOptionWriter<'a> {
+ pub fn new(buffer: &'a mut [u8]) -> Self {
+ Self { buffer }
+ }
+
+ /// Emit a [`DhcpOption`] into a [`DhcpOptionWriter`].
+ pub fn emit(&mut self, option: DhcpOption<'_>) -> Result<()> {
+ if option.data.len() > u8::MAX as _ {
+ return Err(Error);
+ }
+
+ let total_len = 2 + option.data.len();
+ if self.buffer.len() < total_len {
+ return Err(Error);
+ }
+
+ let (buf, rest) = core::mem::take(&mut self.buffer).split_at_mut(total_len);
+ self.buffer = rest;
+
+ buf[0] = option.kind;
+ buf[1] = option.data.len() as _;
+ buf[2..].copy_from_slice(option.data);
+
+ Ok(())
+ }
+
+ pub fn end(&mut self) -> Result<()> {
+ if self.buffer.is_empty() {
+ return Err(Error);
+ }
+
+ self.buffer[0] = field::OPT_END;
+ self.buffer = &mut [];
+ Ok(())
+ }
+}
+
+/// A representation of a single DHCP option.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DhcpOption<'a> {
+ pub kind: u8,
+ pub data: &'a [u8],
+}
+
+/// A read/write wrapper around a Dynamic Host Configuration Protocol packet buffer.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+pub(crate) mod field {
+ #![allow(non_snake_case)]
+ #![allow(unused)]
+
+ use crate::wire::field::*;
+
+ pub const OP: usize = 0;
+ pub const HTYPE: usize = 1;
+ pub const HLEN: usize = 2;
+ pub const HOPS: usize = 3;
+ pub const XID: Field = 4..8;
+ pub const SECS: Field = 8..10;
+ pub const FLAGS: Field = 10..12;
+ pub const CIADDR: Field = 12..16;
+ pub const YIADDR: Field = 16..20;
+ pub const SIADDR: Field = 20..24;
+ pub const GIADDR: Field = 24..28;
+ pub const CHADDR: Field = 28..34;
+ pub const SNAME: Field = 34..108;
+ pub const FILE: Field = 108..236;
+ pub const MAGIC_NUMBER: Field = 236..240;
+ pub const OPTIONS: Rest = 240..;
+
+ // Vendor Extensions
+ pub const OPT_END: u8 = 255;
+ pub const OPT_PAD: u8 = 0;
+ pub const OPT_SUBNET_MASK: u8 = 1;
+ pub const OPT_TIME_OFFSET: u8 = 2;
+ pub const OPT_ROUTER: u8 = 3;
+ pub const OPT_TIME_SERVER: u8 = 4;
+ pub const OPT_NAME_SERVER: u8 = 5;
+ pub const OPT_DOMAIN_NAME_SERVER: u8 = 6;
+ pub const OPT_LOG_SERVER: u8 = 7;
+ pub const OPT_COOKIE_SERVER: u8 = 8;
+ pub const OPT_LPR_SERVER: u8 = 9;
+ pub const OPT_IMPRESS_SERVER: u8 = 10;
+ pub const OPT_RESOURCE_LOCATION_SERVER: u8 = 11;
+ pub const OPT_HOST_NAME: u8 = 12;
+ pub const OPT_BOOT_FILE_SIZE: u8 = 13;
+ pub const OPT_MERIT_DUMP: u8 = 14;
+ pub const OPT_DOMAIN_NAME: u8 = 15;
+ pub const OPT_SWAP_SERVER: u8 = 16;
+ pub const OPT_ROOT_PATH: u8 = 17;
+ pub const OPT_EXTENSIONS_PATH: u8 = 18;
+
+ // IP Layer Parameters per Host
+ pub const OPT_IP_FORWARDING: u8 = 19;
+ pub const OPT_NON_LOCAL_SOURCE_ROUTING: u8 = 20;
+ pub const OPT_POLICY_FILTER: u8 = 21;
+ pub const OPT_MAX_DATAGRAM_REASSEMBLY_SIZE: u8 = 22;
+ pub const OPT_DEFAULT_TTL: u8 = 23;
+ pub const OPT_PATH_MTU_AGING_TIMEOUT: u8 = 24;
+ pub const OPT_PATH_MTU_PLATEAU_TABLE: u8 = 25;
+
+ // IP Layer Parameters per Interface
+ pub const OPT_INTERFACE_MTU: u8 = 26;
+ pub const OPT_ALL_SUBNETS_ARE_LOCAL: u8 = 27;
+ pub const OPT_BROADCAST_ADDRESS: u8 = 28;
+ pub const OPT_PERFORM_MASK_DISCOVERY: u8 = 29;
+ pub const OPT_MASK_SUPPLIER: u8 = 30;
+ pub const OPT_PERFORM_ROUTER_DISCOVERY: u8 = 31;
+ pub const OPT_ROUTER_SOLICITATION_ADDRESS: u8 = 32;
+ pub const OPT_STATIC_ROUTE: u8 = 33;
+
+ // Link Layer Parameters per Interface
+ pub const OPT_TRAILER_ENCAPSULATION: u8 = 34;
+ pub const OPT_ARP_CACHE_TIMEOUT: u8 = 35;
+ pub const OPT_ETHERNET_ENCAPSULATION: u8 = 36;
+
+ // TCP Parameters
+ pub const OPT_TCP_DEFAULT_TTL: u8 = 37;
+ pub const OPT_TCP_KEEPALIVE_INTERVAL: u8 = 38;
+ pub const OPT_TCP_KEEPALIVE_GARBAGE: u8 = 39;
+
+ // Application and Service Parameters
+ pub const OPT_NIS_DOMAIN: u8 = 40;
+ pub const OPT_NIS_SERVERS: u8 = 41;
+ pub const OPT_NTP_SERVERS: u8 = 42;
+ pub const OPT_VENDOR_SPECIFIC_INFO: u8 = 43;
+ pub const OPT_NETBIOS_NAME_SERVER: u8 = 44;
+ pub const OPT_NETBIOS_DISTRIBUTION_SERVER: u8 = 45;
+ pub const OPT_NETBIOS_NODE_TYPE: u8 = 46;
+ pub const OPT_NETBIOS_SCOPE: u8 = 47;
+ pub const OPT_X_WINDOW_FONT_SERVER: u8 = 48;
+ pub const OPT_X_WINDOW_DISPLAY_MANAGER: u8 = 49;
+ pub const OPT_NIS_PLUS_DOMAIN: u8 = 64;
+ pub const OPT_NIS_PLUS_SERVERS: u8 = 65;
+ pub const OPT_MOBILE_IP_HOME_AGENT: u8 = 68;
+ pub const OPT_SMTP_SERVER: u8 = 69;
+ pub const OPT_POP3_SERVER: u8 = 70;
+ pub const OPT_NNTP_SERVER: u8 = 71;
+ pub const OPT_WWW_SERVER: u8 = 72;
+ pub const OPT_FINGER_SERVER: u8 = 73;
+ pub const OPT_IRC_SERVER: u8 = 74;
+ pub const OPT_STREETTALK_SERVER: u8 = 75;
+ pub const OPT_STDA_SERVER: u8 = 76;
+
+ // DHCP Extensions
+ pub const OPT_REQUESTED_IP: u8 = 50;
+ pub const OPT_IP_LEASE_TIME: u8 = 51;
+ pub const OPT_OPTION_OVERLOAD: u8 = 52;
+ pub const OPT_TFTP_SERVER_NAME: u8 = 66;
+ pub const OPT_BOOTFILE_NAME: u8 = 67;
+ pub const OPT_DHCP_MESSAGE_TYPE: u8 = 53;
+ pub const OPT_SERVER_IDENTIFIER: u8 = 54;
+ pub const OPT_PARAMETER_REQUEST_LIST: u8 = 55;
+ pub const OPT_MESSAGE: u8 = 56;
+ pub const OPT_MAX_DHCP_MESSAGE_SIZE: u8 = 57;
+ pub const OPT_RENEWAL_TIME_VALUE: u8 = 58;
+ pub const OPT_REBINDING_TIME_VALUE: u8 = 59;
+ pub const OPT_VENDOR_CLASS_ID: u8 = 60;
+ pub const OPT_CLIENT_ID: u8 = 61;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with DHCP packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::MAGIC_NUMBER.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Returns the operation code of this packet.
+ pub fn opcode(&self) -> OpCode {
+ let data = self.buffer.as_ref();
+ OpCode::from(data[field::OP])
+ }
+
+ /// Returns the hardware protocol type (e.g. ethernet).
+ pub fn hardware_type(&self) -> Hardware {
+ let data = self.buffer.as_ref();
+ Hardware::from(u16::from(data[field::HTYPE]))
+ }
+
+ /// Returns the length of a hardware address in bytes (e.g. 6 for ethernet).
+ pub fn hardware_len(&self) -> u8 {
+ self.buffer.as_ref()[field::HLEN]
+ }
+
+ /// Returns the transaction ID.
+ ///
+ /// The transaction ID (called `xid` in the specification) is a random number used to
+ /// associate messages and responses between client and server. The number is chosen by
+ /// the client.
+ pub fn transaction_id(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::XID];
+ NetworkEndian::read_u32(field)
+ }
+
+ /// Returns the hardware address of the client (called `chaddr` in the specification).
+ ///
+ /// Only ethernet is supported by `smoltcp`, so this functions returns
+ /// an `EthernetAddress`.
+ pub fn client_hardware_address(&self) -> EthernetAddress {
+ let field = &self.buffer.as_ref()[field::CHADDR];
+ EthernetAddress::from_bytes(field)
+ }
+
+ /// Returns the value of the `hops` field.
+ ///
+ /// The `hops` field is set to zero by clients and optionally used by relay agents.
+ pub fn hops(&self) -> u8 {
+ self.buffer.as_ref()[field::HOPS]
+ }
+
+ /// Returns the value of the `secs` field.
+ ///
+ /// The secs field is filled by clients and describes the number of seconds elapsed
+ /// since client began process.
+ pub fn secs(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::SECS];
+ NetworkEndian::read_u16(field)
+ }
+
+ /// Returns the value of the `magic cookie` field in the DHCP options.
+ ///
+ /// This field should be always be `0x63825363`.
+ pub fn magic_number(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::MAGIC_NUMBER];
+ NetworkEndian::read_u32(field)
+ }
+
+ /// Returns the Ipv4 address of the client, zero if not set.
+ ///
+ /// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
+ /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
+ /// and can respond to ARP requests”.
+ pub fn client_ip(&self) -> Ipv4Address {
+ let field = &self.buffer.as_ref()[field::CIADDR];
+ Ipv4Address::from_bytes(field)
+ }
+
+ /// Returns the value of the `yiaddr` field, zero if not set.
+ pub fn your_ip(&self) -> Ipv4Address {
+ let field = &self.buffer.as_ref()[field::YIADDR];
+ Ipv4Address::from_bytes(field)
+ }
+
+ /// Returns the value of the `siaddr` field, zero if not set.
+ pub fn server_ip(&self) -> Ipv4Address {
+ let field = &self.buffer.as_ref()[field::SIADDR];
+ Ipv4Address::from_bytes(field)
+ }
+
+ /// Returns the value of the `giaddr` field, zero if not set.
+ pub fn relay_agent_ip(&self) -> Ipv4Address {
+ let field = &self.buffer.as_ref()[field::GIADDR];
+ Ipv4Address::from_bytes(field)
+ }
+
+ pub fn flags(&self) -> Flags {
+ let field = &self.buffer.as_ref()[field::FLAGS];
+ Flags::from_bits_truncate(NetworkEndian::read_u16(field))
+ }
+
+ /// Return an iterator over the options.
+ #[inline]
+ pub fn options(&self) -> impl Iterator<Item = DhcpOption<'_>> + '_ {
+ let mut buf = &self.buffer.as_ref()[field::OPTIONS];
+ iter::from_fn(move || {
+ loop {
+ match buf.first().copied() {
+ // No more options, return.
+ None => return None,
+ Some(field::OPT_END) => return None,
+
+ // Skip padding.
+ Some(field::OPT_PAD) => buf = &buf[1..],
+ Some(kind) => {
+ if buf.len() < 2 {
+ return None;
+ }
+
+ let len = buf[1] as usize;
+
+ if buf.len() < 2 + len {
+ return None;
+ }
+
+ let opt = DhcpOption {
+ kind,
+ data: &buf[2..2 + len],
+ };
+
+ buf = &buf[2 + len..];
+ return Some(opt);
+ }
+ }
+ }
+ })
+ }
+
+ pub fn get_sname(&self) -> Result<&str> {
+ let data = &self.buffer.as_ref()[field::SNAME];
+ let len = data.iter().position(|&x| x == 0).ok_or(Error)?;
+ if len == 0 {
+ return Err(Error);
+ }
+
+ let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?;
+ Ok(data)
+ }
+
+ pub fn get_boot_file(&self) -> Result<&str> {
+ let data = &self.buffer.as_ref()[field::FILE];
+ let len = data.iter().position(|&x| x == 0).ok_or(Error)?;
+ if len == 0 {
+ return Err(Error);
+ }
+ let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?;
+ Ok(data)
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Sets the optional `sname` (“server name”) and `file` (“boot file name”) fields to zero.
+ ///
+ /// The fields are not commonly used, so we set their value always to zero. **This method
+ /// must be called when creating a packet, otherwise the emitted values for these fields
+ /// are undefined!**
+ pub fn set_sname_and_boot_file_to_zero(&mut self) {
+ let data = self.buffer.as_mut();
+ for byte in &mut data[field::SNAME] {
+ *byte = 0;
+ }
+ for byte in &mut data[field::FILE] {
+ *byte = 0;
+ }
+ }
+
+ /// Sets the `OpCode` for the packet.
+ pub fn set_opcode(&mut self, value: OpCode) {
+ let data = self.buffer.as_mut();
+ data[field::OP] = value.into();
+ }
+
+ /// Sets the hardware address type (only ethernet is supported).
+ pub fn set_hardware_type(&mut self, value: Hardware) {
+ let data = self.buffer.as_mut();
+ let number: u16 = value.into();
+ assert!(number <= u16::from(u8::max_value())); // TODO: Replace with TryFrom when it's stable
+ data[field::HTYPE] = number as u8;
+ }
+
+ /// Sets the hardware address length.
+ ///
+ /// Only ethernet is supported, so this field should be set to the value `6`.
+ pub fn set_hardware_len(&mut self, value: u8) {
+ self.buffer.as_mut()[field::HLEN] = value;
+ }
+
+ /// Sets the transaction ID.
+ ///
+ /// The transaction ID (called `xid` in the specification) is a random number used to
+ /// associate messages and responses between client and server. The number is chosen by
+ /// the client.
+ pub fn set_transaction_id(&mut self, value: u32) {
+ let field = &mut self.buffer.as_mut()[field::XID];
+ NetworkEndian::write_u32(field, value)
+ }
+
+ /// Sets the ethernet address of the client.
+ ///
+ /// Sets the `chaddr` field.
+ pub fn set_client_hardware_address(&mut self, value: EthernetAddress) {
+ let field = &mut self.buffer.as_mut()[field::CHADDR];
+ field.copy_from_slice(value.as_bytes());
+ }
+
+ /// Sets the hops field.
+ ///
+ /// The `hops` field is set to zero by clients and optionally used by relay agents.
+ pub fn set_hops(&mut self, value: u8) {
+ self.buffer.as_mut()[field::HOPS] = value;
+ }
+
+ /// Sets the `secs` field.
+ ///
+ /// The secs field is filled by clients and describes the number of seconds elapsed
+ /// since client began process.
+ pub fn set_secs(&mut self, value: u16) {
+ let field = &mut self.buffer.as_mut()[field::SECS];
+ NetworkEndian::write_u16(field, value);
+ }
+
+ /// Sets the value of the `magic cookie` field in the DHCP options.
+ ///
+ /// This field should be always be `0x63825363`.
+ pub fn set_magic_number(&mut self, value: u32) {
+ let field = &mut self.buffer.as_mut()[field::MAGIC_NUMBER];
+ NetworkEndian::write_u32(field, value);
+ }
+
+ /// Sets the Ipv4 address of the client.
+ ///
+ /// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
+ /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
+ /// and can respond to ARP requests”.
+ pub fn set_client_ip(&mut self, value: Ipv4Address) {
+ let field = &mut self.buffer.as_mut()[field::CIADDR];
+ field.copy_from_slice(value.as_bytes());
+ }
+
+ /// Sets the value of the `yiaddr` field.
+ pub fn set_your_ip(&mut self, value: Ipv4Address) {
+ let field = &mut self.buffer.as_mut()[field::YIADDR];
+ field.copy_from_slice(value.as_bytes());
+ }
+
+ /// Sets the value of the `siaddr` field.
+ pub fn set_server_ip(&mut self, value: Ipv4Address) {
+ let field = &mut self.buffer.as_mut()[field::SIADDR];
+ field.copy_from_slice(value.as_bytes());
+ }
+
+ /// Sets the value of the `giaddr` field.
+ pub fn set_relay_agent_ip(&mut self, value: Ipv4Address) {
+ let field = &mut self.buffer.as_mut()[field::GIADDR];
+ field.copy_from_slice(value.as_bytes());
+ }
+
+ /// Sets the flags to the specified value.
+ pub fn set_flags(&mut self, val: Flags) {
+ let field = &mut self.buffer.as_mut()[field::FLAGS];
+ NetworkEndian::write_u16(field, val.bits());
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
+ /// Return a pointer to the options.
+ #[inline]
+ pub fn options_mut(&mut self) -> DhcpOptionWriter<'_> {
+ DhcpOptionWriter::new(&mut self.buffer.as_mut()[field::OPTIONS])
+ }
+}
+
+/// A high-level representation of a Dynamic Host Configuration Protocol packet.
+///
+/// DHCP messages have the following layout (see [RFC 2131](https://tools.ietf.org/html/rfc2131)
+/// for details):
+///
+/// ```no_rust
+/// 0 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | message_type | htype (N/A) | hlen (N/A) | hops |
+/// +---------------+---------------+---------------+---------------+
+/// | transaction_id |
+/// +-------------------------------+-------------------------------+
+/// | secs | flags |
+/// +-------------------------------+-------------------------------+
+/// | client_ip |
+/// +---------------------------------------------------------------+
+/// | your_ip |
+/// +---------------------------------------------------------------+
+/// | server_ip |
+/// +---------------------------------------------------------------+
+/// | relay_agent_ip |
+/// +---------------------------------------------------------------+
+/// | |
+/// | client_hardware_address |
+/// | |
+/// | |
+/// +---------------------------------------------------------------+
+/// | |
+/// | sname (N/A) |
+/// +---------------------------------------------------------------+
+/// | |
+/// | file (N/A) |
+/// +---------------------------------------------------------------+
+/// | |
+/// | options |
+/// +---------------------------------------------------------------+
+/// ```
+///
+/// It is assumed that the access layer is Ethernet, so `htype` (the field representing the
+/// hardware address type) is always set to `1`, and `hlen` (which represents the hardware address
+/// length) is set to `6`.
+///
+/// The `options` field has a variable length.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+ /// This field is also known as `op` in the RFC. It indicates the type of DHCP message this
+ /// packet represents.
+ pub message_type: MessageType,
+ /// This field is also known as `xid` in the RFC. It is a random number chosen by the client,
+ /// used by the client and server to associate messages and responses between a client and a
+ /// server.
+ pub transaction_id: u32,
+ /// seconds elapsed since client began address acquisition or renewal
+ /// process the DHCPREQUEST message MUST use the same value in the DHCP
+ /// message header's 'secs' field and be sent to the same IP broadcast
+ /// address as the original DHCPDISCOVER message.
+ pub secs: u16,
+ /// This field is also known as `chaddr` in the RFC and for networks where the access layer is
+ /// ethernet, it is the client MAC address.
+ pub client_hardware_address: EthernetAddress,
+ /// This field is also known as `ciaddr` in the RFC. It is only filled in if client is in
+ /// BOUND, RENEW or REBINDING state and can respond to ARP requests.
+ pub client_ip: Ipv4Address,
+ /// This field is also known as `yiaddr` in the RFC.
+ pub your_ip: Ipv4Address,
+ /// This field is also known as `siaddr` in the RFC. It may be set by the server in DHCPOFFER
+ /// and DHCPACK messages, and represent the address of the next server to use in bootstrap.
+ pub server_ip: Ipv4Address,
+ /// Default gateway
+ pub router: Option<Ipv4Address>,
+ /// This field comes from a corresponding DhcpOption.
+ pub subnet_mask: Option<Ipv4Address>,
+ /// This field is also known as `giaddr` in the RFC. In order to allow DHCP clients on subnets
+ /// not directly served by DHCP servers to communicate with DHCP servers, DHCP relay agents can
+ /// be installed on these subnets. The DHCP client broadcasts on the local link; the relay
+ /// agent receives the broadcast and transmits it to one or more DHCP servers using unicast.
+ /// The relay agent stores its own IP address in the `relay_agent_ip` field of the DHCP packet.
+ /// The DHCP server uses the `relay_agent_ip` to determine the subnet on which the relay agent
+ /// received the broadcast, and allocates an IP address on that subnet. When the DHCP server
+ /// replies to the client, it sends the reply to the `relay_agent_ip` address, again using
+ /// unicast. The relay agent then retransmits the response on the local network
+ pub relay_agent_ip: Ipv4Address,
+ /// Broadcast flags. It can be set in DHCPDISCOVER, DHCPINFORM and DHCPREQUEST message if the
+ /// client requires the response to be broadcasted.
+ pub broadcast: bool,
+ /// The "requested IP address" option. It can be used by clients in DHCPREQUEST or DHCPDISCOVER
+ /// messages, or by servers in DHCPDECLINE messages.
+ pub requested_ip: Option<Ipv4Address>,
+ /// The "client identifier" option.
+ ///
+ /// The 'client identifier' is an opaque key, not to be interpreted by the server; for example,
+ /// the 'client identifier' may contain a hardware address, identical to the contents of the
+ /// 'chaddr' field, or it may contain another type of identifier, such as a DNS name. The
+ /// 'client identifier' chosen by a DHCP client MUST be unique to that client within the subnet
+ /// to which the client is attached. If the client uses a 'client identifier' in one message,
+ /// it MUST use that same identifier in all subsequent messages, to ensure that all servers
+ /// correctly identify the client.
+ pub client_identifier: Option<EthernetAddress>,
+ /// The "server identifier" option. It is used both to identify a DHCP server
+ /// in a DHCP message and as a destination address from clients to servers.
+ pub server_identifier: Option<Ipv4Address>,
+ /// The parameter request list informs the server about which configuration parameters
+ /// the client is interested in.
+ pub parameter_request_list: Option<&'a [u8]>,
+ /// DNS servers
+ pub dns_servers: Option<Vec<Ipv4Address, MAX_DNS_SERVER_COUNT>>,
+ /// The maximum size dhcp packet the interface can receive
+ pub max_size: Option<u16>,
+ /// The DHCP IP lease duration, specified in seconds.
+ pub lease_duration: Option<u32>,
+ /// The DHCP IP renew duration (T1 interval), in seconds, if specified in the packet.
+ pub renew_duration: Option<u32>,
+ /// The DHCP IP rebind duration (T2 interval), in seconds, if specified in the packet.
+ pub rebind_duration: Option<u32>,
+ /// When returned from [`Repr::parse`], this field will be `None`.
+ /// However, when calling [`Repr::emit`], this field should contain only
+ /// additional DHCP options not known to smoltcp.
+ pub additional_options: &'a [DhcpOption<'a>],
+}
+
+impl<'a> Repr<'a> {
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub fn buffer_len(&self) -> usize {
+ let mut len = field::OPTIONS.start;
+ // message type and end-of-options options
+ len += 3 + 1;
+ if self.requested_ip.is_some() {
+ len += 6;
+ }
+ if self.client_identifier.is_some() {
+ len += 9;
+ }
+ if self.server_identifier.is_some() {
+ len += 6;
+ }
+ if self.max_size.is_some() {
+ len += 4;
+ }
+ if self.router.is_some() {
+ len += 6;
+ }
+ if self.subnet_mask.is_some() {
+ len += 6;
+ }
+ if self.lease_duration.is_some() {
+ len += 6;
+ }
+ if let Some(dns_servers) = &self.dns_servers {
+ len += 2;
+ len += dns_servers.iter().count() * core::mem::size_of::<u32>();
+ }
+ if let Some(list) = self.parameter_request_list {
+ len += list.len() + 2;
+ }
+ for opt in self.additional_options {
+ len += 2 + opt.data.len()
+ }
+
+ len
+ }
+
+ /// Parse a DHCP packet and return a high-level representation.
+ pub fn parse<T>(packet: &'a Packet<&'a T>) -> Result<Self>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ let transaction_id = packet.transaction_id();
+ let client_hardware_address = packet.client_hardware_address();
+ let client_ip = packet.client_ip();
+ let your_ip = packet.your_ip();
+ let server_ip = packet.server_ip();
+ let relay_agent_ip = packet.relay_agent_ip();
+ let secs = packet.secs();
+
+ // only ethernet is supported right now
+ match packet.hardware_type() {
+ Hardware::Ethernet => {
+ if packet.hardware_len() != 6 {
+ return Err(Error);
+ }
+ }
+ Hardware::Unknown(_) => return Err(Error), // unimplemented
+ }
+
+ if packet.magic_number() != DHCP_MAGIC_NUMBER {
+ return Err(Error);
+ }
+
+ let mut message_type = Err(Error);
+ let mut requested_ip = None;
+ let mut client_identifier = None;
+ let mut server_identifier = None;
+ let mut router = None;
+ let mut subnet_mask = None;
+ let mut parameter_request_list = None;
+ let mut dns_servers = None;
+ let mut max_size = None;
+ let mut lease_duration = None;
+ let mut renew_duration = None;
+ let mut rebind_duration = None;
+
+ for option in packet.options() {
+ let data = option.data;
+ match (option.kind, data.len()) {
+ (field::OPT_DHCP_MESSAGE_TYPE, 1) => {
+ let value = MessageType::from(data[0]);
+ if value.opcode() == packet.opcode() {
+ message_type = Ok(value);
+ }
+ }
+ (field::OPT_REQUESTED_IP, 4) => {
+ requested_ip = Some(Ipv4Address::from_bytes(data));
+ }
+ (field::OPT_CLIENT_ID, 7) => {
+ let hardware_type = Hardware::from(u16::from(data[0]));
+ if hardware_type != Hardware::Ethernet {
+ return Err(Error);
+ }
+ client_identifier = Some(EthernetAddress::from_bytes(&data[1..]));
+ }
+ (field::OPT_SERVER_IDENTIFIER, 4) => {
+ server_identifier = Some(Ipv4Address::from_bytes(data));
+ }
+ (field::OPT_ROUTER, 4) => {
+ router = Some(Ipv4Address::from_bytes(data));
+ }
+ (field::OPT_SUBNET_MASK, 4) => {
+ subnet_mask = Some(Ipv4Address::from_bytes(data));
+ }
+ (field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => {
+ max_size = Some(u16::from_be_bytes([data[0], data[1]]));
+ }
+ (field::OPT_RENEWAL_TIME_VALUE, 4) => {
+ renew_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
+ }
+ (field::OPT_REBINDING_TIME_VALUE, 4) => {
+ rebind_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
+ }
+ (field::OPT_IP_LEASE_TIME, 4) => {
+ lease_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
+ }
+ (field::OPT_PARAMETER_REQUEST_LIST, _) => {
+ parameter_request_list = Some(data);
+ }
+ (field::OPT_DOMAIN_NAME_SERVER, _) => {
+ let mut servers = Vec::new();
+ const IP_ADDR_BYTE_LEN: usize = 4;
+ let mut addrs = data.chunks_exact(IP_ADDR_BYTE_LEN);
+ for chunk in &mut addrs {
+ // We ignore push failures because that will only happen
+ // if we attempt to push more than 4 addresses, and the only
+ // solution to that is to support more addresses.
+ servers.push(Ipv4Address::from_bytes(chunk)).ok();
+ }
+ dns_servers = Some(servers);
+
+ if !addrs.remainder().is_empty() {
+ net_trace!("DHCP domain name servers contained invalid address");
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let broadcast = packet.flags().contains(Flags::BROADCAST);
+
+ Ok(Repr {
+ secs,
+ transaction_id,
+ client_hardware_address,
+ client_ip,
+ your_ip,
+ server_ip,
+ relay_agent_ip,
+ broadcast,
+ requested_ip,
+ server_identifier,
+ router,
+ subnet_mask,
+ client_identifier,
+ parameter_request_list,
+ dns_servers,
+ max_size,
+ lease_duration,
+ renew_duration,
+ rebind_duration,
+ message_type: message_type?,
+ additional_options: &[],
+ })
+ }
+
+ /// Emit a high-level representation into a Dynamic Host
+ /// Configuration Protocol packet.
+ pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ packet.set_sname_and_boot_file_to_zero();
+ packet.set_opcode(self.message_type.opcode());
+ packet.set_hardware_type(Hardware::Ethernet);
+ packet.set_hardware_len(6);
+ packet.set_transaction_id(self.transaction_id);
+ packet.set_client_hardware_address(self.client_hardware_address);
+ packet.set_hops(0);
+ packet.set_secs(self.secs);
+ packet.set_magic_number(0x63825363);
+ packet.set_client_ip(self.client_ip);
+ packet.set_your_ip(self.your_ip);
+ packet.set_server_ip(self.server_ip);
+ packet.set_relay_agent_ip(self.relay_agent_ip);
+
+ let mut flags = Flags::empty();
+ if self.broadcast {
+ flags |= Flags::BROADCAST;
+ }
+ packet.set_flags(flags);
+
+ {
+ let mut options = packet.options_mut();
+
+ options.emit(DhcpOption {
+ kind: field::OPT_DHCP_MESSAGE_TYPE,
+ data: &[self.message_type.into()],
+ })?;
+
+ if let Some(val) = &self.client_identifier {
+ let mut data = [0; 7];
+ data[0] = u16::from(Hardware::Ethernet) as u8;
+ data[1..].copy_from_slice(val.as_bytes());
+
+ options.emit(DhcpOption {
+ kind: field::OPT_CLIENT_ID,
+ data: &data,
+ })?;
+ }
+
+ if let Some(val) = &self.server_identifier {
+ options.emit(DhcpOption {
+ kind: field::OPT_SERVER_IDENTIFIER,
+ data: val.as_bytes(),
+ })?;
+ }
+
+ if let Some(val) = &self.router {
+ options.emit(DhcpOption {
+ kind: field::OPT_ROUTER,
+ data: val.as_bytes(),
+ })?;
+ }
+ if let Some(val) = &self.subnet_mask {
+ options.emit(DhcpOption {
+ kind: field::OPT_SUBNET_MASK,
+ data: val.as_bytes(),
+ })?;
+ }
+ if let Some(val) = &self.requested_ip {
+ options.emit(DhcpOption {
+ kind: field::OPT_REQUESTED_IP,
+ data: val.as_bytes(),
+ })?;
+ }
+ if let Some(val) = &self.max_size {
+ options.emit(DhcpOption {
+ kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
+ data: &val.to_be_bytes(),
+ })?;
+ }
+ if let Some(val) = &self.lease_duration {
+ options.emit(DhcpOption {
+ kind: field::OPT_IP_LEASE_TIME,
+ data: &val.to_be_bytes(),
+ })?;
+ }
+ if let Some(val) = &self.parameter_request_list {
+ options.emit(DhcpOption {
+ kind: field::OPT_PARAMETER_REQUEST_LIST,
+ data: val,
+ })?;
+ }
+
+ if let Some(dns_servers) = &self.dns_servers {
+ const IP_SIZE: usize = core::mem::size_of::<u32>();
+ let mut servers = [0; MAX_DNS_SERVER_COUNT * IP_SIZE];
+
+ let data_len = dns_servers
+ .iter()
+ .enumerate()
+ .inspect(|(i, ip)| {
+ servers[(i * IP_SIZE)..((i + 1) * IP_SIZE)].copy_from_slice(ip.as_bytes());
+ })
+ .count()
+ * IP_SIZE;
+ options.emit(DhcpOption {
+ kind: field::OPT_DOMAIN_NAME_SERVER,
+ data: &servers[..data_len],
+ })?;
+ }
+
+ for option in self.additional_options {
+ options.emit(*option)?;
+ }
+
+ options.end()?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::wire::Ipv4Address;
+
+ const MAGIC_COOKIE: u32 = 0x63825363;
+
+ static DISCOVER_BYTES: &[u8] = &[
+ 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
+ 0x82, 0x01, 0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
+ 0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ static ACK_DNS_SERVER_BYTES: &[u8] = &[
+ 0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06,
+ 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17,
+ 0xeb, 0xc9, 0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
+ 0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00,
+ 0x2b, 0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68,
+ 0x79, 0x73, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00,
+ 0x03, 0x04, 0x0a, 0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a,
+ 0x07, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03,
+ 0xa3, 0x01, 0x4a, 0x04, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08,
+ 0xff,
+ ];
+
+ static ACK_LEASE_TIME_BYTES: &[u8] = &[
+ 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91,
+ 0x62, 0xd2, 0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
+ 0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56,
+ 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ const IP_NULL: Ipv4Address = Ipv4Address([0, 0, 0, 0]);
+ const CLIENT_MAC: EthernetAddress = EthernetAddress([0x0, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
+ const DHCP_SIZE: u16 = 1500;
+
+ #[test]
+ fn test_deconstruct_discover() {
+ let packet = Packet::new_unchecked(DISCOVER_BYTES);
+ assert_eq!(packet.magic_number(), MAGIC_COOKIE);
+ assert_eq!(packet.opcode(), OpCode::Request);
+ assert_eq!(packet.hardware_type(), Hardware::Ethernet);
+ assert_eq!(packet.hardware_len(), 6);
+ assert_eq!(packet.hops(), 0);
+ assert_eq!(packet.transaction_id(), 0x3d1d);
+ assert_eq!(packet.secs(), 0);
+ assert_eq!(packet.client_ip(), IP_NULL);
+ assert_eq!(packet.your_ip(), IP_NULL);
+ assert_eq!(packet.server_ip(), IP_NULL);
+ assert_eq!(packet.relay_agent_ip(), IP_NULL);
+ assert_eq!(packet.client_hardware_address(), CLIENT_MAC);
+
+ let mut options = packet.options();
+ assert_eq!(
+ options.next(),
+ Some(DhcpOption {
+ kind: field::OPT_DHCP_MESSAGE_TYPE,
+ data: &[0x01]
+ })
+ );
+ assert_eq!(
+ options.next(),
+ Some(DhcpOption {
+ kind: field::OPT_CLIENT_ID,
+ data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42],
+ })
+ );
+ assert_eq!(
+ options.next(),
+ Some(DhcpOption {
+ kind: field::OPT_REQUESTED_IP,
+ data: &[0x00, 0x00, 0x00, 0x00],
+ })
+ );
+ assert_eq!(
+ options.next(),
+ Some(DhcpOption {
+ kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
+ data: &DHCP_SIZE.to_be_bytes(),
+ })
+ );
+ assert_eq!(
+ options.next(),
+ Some(DhcpOption {
+ kind: field::OPT_PARAMETER_REQUEST_LIST,
+ data: &[1, 3, 6, 42]
+ })
+ );
+ assert_eq!(options.next(), None);
+ }
+
+ #[test]
+ fn test_construct_discover() {
+ let mut bytes = vec![0xa5; 276];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_magic_number(MAGIC_COOKIE);
+ packet.set_sname_and_boot_file_to_zero();
+ packet.set_opcode(OpCode::Request);
+ packet.set_hardware_type(Hardware::Ethernet);
+ packet.set_hardware_len(6);
+ packet.set_hops(0);
+ packet.set_transaction_id(0x3d1d);
+ packet.set_secs(0);
+ packet.set_flags(Flags::empty());
+ packet.set_client_ip(IP_NULL);
+ packet.set_your_ip(IP_NULL);
+ packet.set_server_ip(IP_NULL);
+ packet.set_relay_agent_ip(IP_NULL);
+ packet.set_client_hardware_address(CLIENT_MAC);
+
+ let mut options = packet.options_mut();
+
+ options
+ .emit(DhcpOption {
+ kind: field::OPT_DHCP_MESSAGE_TYPE,
+ data: &[0x01],
+ })
+ .unwrap();
+ options
+ .emit(DhcpOption {
+ kind: field::OPT_CLIENT_ID,
+ data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42],
+ })
+ .unwrap();
+ options
+ .emit(DhcpOption {
+ kind: field::OPT_REQUESTED_IP,
+ data: &[0x00, 0x00, 0x00, 0x00],
+ })
+ .unwrap();
+ options
+ .emit(DhcpOption {
+ kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
+ data: &DHCP_SIZE.to_be_bytes(),
+ })
+ .unwrap();
+ options
+ .emit(DhcpOption {
+ kind: field::OPT_PARAMETER_REQUEST_LIST,
+ data: &[1, 3, 6, 42],
+ })
+ .unwrap();
+ options.end().unwrap();
+
+ let packet = &mut packet.into_inner()[..];
+ for byte in &mut packet[269..276] {
+ *byte = 0; // padding bytes
+ }
+
+ assert_eq!(packet, DISCOVER_BYTES);
+ }
+
+ const fn offer_repr() -> Repr<'static> {
+ Repr {
+ message_type: MessageType::Offer,
+ transaction_id: 0x3d1d,
+ client_hardware_address: CLIENT_MAC,
+ client_ip: IP_NULL,
+ your_ip: IP_NULL,
+ server_ip: IP_NULL,
+ router: Some(IP_NULL),
+ subnet_mask: Some(IP_NULL),
+ relay_agent_ip: IP_NULL,
+ secs: 0,
+ broadcast: false,
+ requested_ip: None,
+ client_identifier: Some(CLIENT_MAC),
+ server_identifier: None,
+ parameter_request_list: None,
+ dns_servers: None,
+ max_size: None,
+ renew_duration: None,
+ rebind_duration: None,
+ lease_duration: Some(0xffff_ffff), // Infinite lease
+ additional_options: &[],
+ }
+ }
+
+ const fn discover_repr() -> Repr<'static> {
+ Repr {
+ message_type: MessageType::Discover,
+ transaction_id: 0x3d1d,
+ client_hardware_address: CLIENT_MAC,
+ client_ip: IP_NULL,
+ your_ip: IP_NULL,
+ server_ip: IP_NULL,
+ router: None,
+ subnet_mask: None,
+ relay_agent_ip: IP_NULL,
+ broadcast: false,
+ secs: 0,
+ max_size: Some(DHCP_SIZE),
+ renew_duration: None,
+ rebind_duration: None,
+ lease_duration: None,
+ requested_ip: Some(IP_NULL),
+ client_identifier: Some(CLIENT_MAC),
+ server_identifier: None,
+ parameter_request_list: Some(&[1, 3, 6, 42]),
+ dns_servers: None,
+ additional_options: &[],
+ }
+ }
+
+ #[test]
+ fn test_parse_discover() {
+ let packet = Packet::new_unchecked(DISCOVER_BYTES);
+ let repr = Repr::parse(&packet).unwrap();
+ assert_eq!(repr, discover_repr());
+ }
+
+ #[test]
+ fn test_emit_discover() {
+ let repr = discover_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet).unwrap();
+ let packet = &*packet.into_inner();
+ let packet_len = packet.len();
+ assert_eq!(packet, &DISCOVER_BYTES[..packet_len]);
+ for byte in &DISCOVER_BYTES[packet_len..] {
+ assert_eq!(*byte, 0); // padding bytes
+ }
+ }
+
+ #[test]
+ fn test_emit_offer() {
+ let repr = offer_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet).unwrap();
+ }
+
+ #[test]
+ fn test_emit_offer_dns() {
+ let repr = {
+ let mut repr = offer_repr();
+ repr.dns_servers = Some(
+ Vec::from_slice(&[
+ Ipv4Address([163, 1, 74, 6]),
+ Ipv4Address([163, 1, 74, 7]),
+ Ipv4Address([163, 1, 74, 3]),
+ ])
+ .unwrap(),
+ );
+ repr
+ };
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet).unwrap();
+
+ let packet = Packet::new_unchecked(&bytes);
+ let repr_parsed = Repr::parse(&packet).unwrap();
+
+ assert_eq!(
+ repr_parsed.dns_servers,
+ Some(
+ Vec::from_slice(&[
+ Ipv4Address([163, 1, 74, 6]),
+ Ipv4Address([163, 1, 74, 7]),
+ Ipv4Address([163, 1, 74, 3]),
+ ])
+ .unwrap()
+ )
+ );
+ }
+
+ #[test]
+ fn test_emit_dhcp_option() {
+ static DATA: &[u8] = &[1, 3, 6];
+ let dhcp_option = DhcpOption {
+ kind: field::OPT_PARAMETER_REQUEST_LIST,
+ data: DATA,
+ };
+
+ let mut bytes = vec![0xa5; 5];
+ let mut writer = DhcpOptionWriter::new(&mut bytes);
+ writer.emit(dhcp_option).unwrap();
+
+ assert_eq!(
+ &bytes[0..2],
+ &[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8]
+ );
+ assert_eq!(&bytes[2..], DATA);
+ }
+
+ #[test]
+ fn test_parse_ack_dns_servers() {
+ let packet = Packet::new_unchecked(ACK_DNS_SERVER_BYTES);
+ let repr = Repr::parse(&packet).unwrap();
+
+ // The packet described by ACK_BYTES advertises 4 DNS servers
+ // Here we ensure that we correctly parse the first 3 into our fixed
+ // length-3 array (see issue #305)
+ assert_eq!(
+ repr.dns_servers,
+ Some(
+ Vec::from_slice(&[
+ Ipv4Address([163, 1, 74, 6]),
+ Ipv4Address([163, 1, 74, 7]),
+ Ipv4Address([163, 1, 74, 3])
+ ])
+ .unwrap()
+ )
+ );
+ }
+
+ #[test]
+ fn test_parse_ack_lease_duration() {
+ let packet = Packet::new_unchecked(ACK_LEASE_TIME_BYTES);
+ let repr = Repr::parse(&packet).unwrap();
+
+ // Verify that the lease time in the ACK is properly parsed. The packet contains a lease
+ // duration of 598s.
+ assert_eq!(repr.lease_duration, Some(598));
+ }
+}
diff --git a/src/wire/dns.rs b/src/wire/dns.rs
new file mode 100644
index 0000000..2c9c10d
--- /dev/null
+++ b/src/wire/dns.rs
@@ -0,0 +1,793 @@
+#![allow(dead_code)]
+
+use bitflags::bitflags;
+use byteorder::{ByteOrder, NetworkEndian};
+use core::iter;
+use core::iter::Iterator;
+
+use super::{Error, Result};
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::Ipv4Address;
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::Ipv6Address;
+
+enum_with_unknown! {
+ /// DNS OpCodes
+ pub enum Opcode(u8) {
+ Query = 0x00,
+ Status = 0x01,
+ }
+}
+enum_with_unknown! {
+ /// DNS OpCodes
+ pub enum Rcode(u8) {
+ NoError = 0x00,
+ FormErr = 0x01,
+ ServFail = 0x02,
+ NXDomain = 0x03,
+ NotImp = 0x04,
+ Refused = 0x05,
+ YXDomain = 0x06,
+ YXRRSet = 0x07,
+ NXRRSet = 0x08,
+ NotAuth = 0x09,
+ NotZone = 0x0a,
+ }
+}
+
+enum_with_unknown! {
+ /// DNS record types
+ pub enum Type(u16) {
+ A = 0x0001,
+ Ns = 0x0002,
+ Cname = 0x0005,
+ Soa = 0x0006,
+ Aaaa = 0x001c,
+ }
+}
+
+bitflags! {
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct Flags: u16 {
+ const RESPONSE = 0b1000_0000_0000_0000;
+ const AUTHORITATIVE = 0b0000_0100_0000_0000;
+ const TRUNCATED = 0b0000_0010_0000_0000;
+ const RECURSION_DESIRED = 0b0000_0001_0000_0000;
+ const RECURSION_AVAILABLE = 0b0000_0000_1000_0000;
+ const AUTHENTIC_DATA = 0b0000_0000_0010_0000;
+ const CHECK_DISABLED = 0b0000_0000_0001_0000;
+ }
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const ID: Field = 0..2;
+ pub const FLAGS: Field = 2..4;
+ pub const QDCOUNT: Field = 4..6;
+ pub const ANCOUNT: Field = 6..8;
+ pub const NSCOUNT: Field = 8..10;
+ pub const ARCOUNT: Field = 10..12;
+
+ pub const HEADER_END: usize = 12;
+}
+
+// DNS class IN (Internet)
+const CLASS_IN: u16 = 1;
+
+/// A read/write wrapper around a DNS packet buffer.
+#[derive(Debug, PartialEq, Eq)]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with DNS packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is smaller than
+ /// the header length.
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::HEADER_END {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ pub fn payload(&self) -> &[u8] {
+ &self.buffer.as_ref()[field::HEADER_END..]
+ }
+
+ pub fn transaction_id(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::ID];
+ NetworkEndian::read_u16(field)
+ }
+
+ pub fn flags(&self) -> Flags {
+ let field = &self.buffer.as_ref()[field::FLAGS];
+ Flags::from_bits_truncate(NetworkEndian::read_u16(field))
+ }
+
+ pub fn opcode(&self) -> Opcode {
+ let field = &self.buffer.as_ref()[field::FLAGS];
+ let flags = NetworkEndian::read_u16(field);
+ Opcode::from((flags >> 11 & 0xF) as u8)
+ }
+
+ pub fn rcode(&self) -> Rcode {
+ let field = &self.buffer.as_ref()[field::FLAGS];
+ let flags = NetworkEndian::read_u16(field);
+ Rcode::from((flags & 0xF) as u8)
+ }
+
+ pub fn question_count(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::QDCOUNT];
+ NetworkEndian::read_u16(field)
+ }
+
+ pub fn answer_record_count(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::ANCOUNT];
+ NetworkEndian::read_u16(field)
+ }
+
+ pub fn authority_record_count(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::NSCOUNT];
+ NetworkEndian::read_u16(field)
+ }
+
+ pub fn additional_record_count(&self) -> u16 {
+ let field = &self.buffer.as_ref()[field::ARCOUNT];
+ NetworkEndian::read_u16(field)
+ }
+
+ /// Parse part of a name from `bytes`, following pointers if any.
+ pub fn parse_name<'a>(&'a self, mut bytes: &'a [u8]) -> impl Iterator<Item = Result<&'a [u8]>> {
+ let mut packet = self.buffer.as_ref();
+
+ iter::from_fn(move || loop {
+ if bytes.is_empty() {
+ return Some(Err(Error));
+ }
+ match bytes[0] {
+ 0x00 => return None,
+ x if x & 0xC0 == 0x00 => {
+ let len = (x & 0x3F) as usize;
+ if bytes.len() < 1 + len {
+ return Some(Err(Error));
+ }
+ let label = &bytes[1..1 + len];
+ bytes = &bytes[1 + len..];
+ return Some(Ok(label));
+ }
+ x if x & 0xC0 == 0xC0 => {
+ if bytes.len() < 2 {
+ return Some(Err(Error));
+ }
+ let y = bytes[1];
+ let ptr = ((x & 0x3F) as usize) << 8 | (y as usize);
+ if packet.len() <= ptr {
+ return Some(Err(Error));
+ }
+
+ // RFC1035 says: "In this scheme, an entire domain name or a list of labels at
+ // the end of a domain name is replaced with a pointer to a ***prior*** occurrence
+ // of the same name.
+ //
+ // Is it unclear if this means the pointer MUST point backwards in the packet or not. Either way,
+ // pointers that don't point backwards are never seen in the fields, so use this to check that
+ // there are no pointer loops.
+
+ // Split packet into parts before and after `ptr`.
+ // parse the part after, keep only the part before in `packet`. This ensure we never
+ // parse the same byte twice, therefore eliminating pointer loops.
+
+ bytes = &packet[ptr..];
+ packet = &packet[..ptr];
+ }
+ _ => return Some(Err(Error)),
+ }
+ })
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let data = self.buffer.as_mut();
+ &mut data[field::HEADER_END..]
+ }
+
+ pub fn set_transaction_id(&mut self, val: u16) {
+ let field = &mut self.buffer.as_mut()[field::ID];
+ NetworkEndian::write_u16(field, val)
+ }
+
+ pub fn set_flags(&mut self, val: Flags) {
+ let field = &mut self.buffer.as_mut()[field::FLAGS];
+ let mask = Flags::all().bits;
+ let old = NetworkEndian::read_u16(field);
+ NetworkEndian::write_u16(field, (old & !mask) | val.bits());
+ }
+
+ pub fn set_opcode(&mut self, val: Opcode) {
+ let field = &mut self.buffer.as_mut()[field::FLAGS];
+ let mask = 0x3800;
+ let val: u8 = val.into();
+ let val = (val as u16) << 11;
+ let old = NetworkEndian::read_u16(field);
+ NetworkEndian::write_u16(field, (old & !mask) | val);
+ }
+
+ pub fn set_question_count(&mut self, val: u16) {
+ let field = &mut self.buffer.as_mut()[field::QDCOUNT];
+ NetworkEndian::write_u16(field, val)
+ }
+ pub fn set_answer_record_count(&mut self, val: u16) {
+ let field = &mut self.buffer.as_mut()[field::ANCOUNT];
+ NetworkEndian::write_u16(field, val)
+ }
+ pub fn set_authority_record_count(&mut self, val: u16) {
+ let field = &mut self.buffer.as_mut()[field::NSCOUNT];
+ NetworkEndian::write_u16(field, val)
+ }
+ pub fn set_additional_record_count(&mut self, val: u16) {
+ let field = &mut self.buffer.as_mut()[field::ARCOUNT];
+ NetworkEndian::write_u16(field, val)
+ }
+}
+
+/// Parse part of a name from `bytes`, not following pointers.
+/// Returns the unused part of `bytes`, and the pointer offset if the sequence ends with a pointer.
+fn parse_name_part<'a>(
+ mut bytes: &'a [u8],
+ mut f: impl FnMut(&'a [u8]),
+) -> Result<(&'a [u8], Option<usize>)> {
+ loop {
+ let x = *bytes.first().ok_or(Error)?;
+ bytes = &bytes[1..];
+ match x {
+ 0x00 => return Ok((bytes, None)),
+ x if x & 0xC0 == 0x00 => {
+ let len = (x & 0x3F) as usize;
+ let label = bytes.get(..len).ok_or(Error)?;
+ bytes = &bytes[len..];
+ f(label);
+ }
+ x if x & 0xC0 == 0xC0 => {
+ let y = *bytes.first().ok_or(Error)?;
+ bytes = &bytes[1..];
+
+ let ptr = ((x & 0x3F) as usize) << 8 | (y as usize);
+ return Ok((bytes, Some(ptr)));
+ }
+ _ => return Err(Error),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Question<'a> {
+ pub name: &'a [u8],
+ pub type_: Type,
+}
+
+impl<'a> Question<'a> {
+ pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Question<'a>)> {
+ let (rest, _) = parse_name_part(buffer, |_| ())?;
+ let name = &buffer[..buffer.len() - rest.len()];
+
+ if rest.len() < 4 {
+ return Err(Error);
+ }
+ let type_ = NetworkEndian::read_u16(&rest[0..2]).into();
+ let class = NetworkEndian::read_u16(&rest[2..4]);
+ let rest = &rest[4..];
+
+ if class != CLASS_IN {
+ return Err(Error);
+ }
+
+ Ok((rest, Question { name, type_ }))
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ self.name.len() + 4
+ }
+
+ /// Emit a high-level representation into a DNS packet.
+ pub fn emit(&self, packet: &mut [u8]) {
+ packet[..self.name.len()].copy_from_slice(self.name);
+ let rest = &mut packet[self.name.len()..];
+ NetworkEndian::write_u16(&mut rest[0..2], self.type_.into());
+ NetworkEndian::write_u16(&mut rest[2..4], CLASS_IN);
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Record<'a> {
+ pub name: &'a [u8],
+ pub ttl: u32,
+ pub data: RecordData<'a>,
+}
+
+impl<'a> RecordData<'a> {
+ pub fn parse(type_: Type, data: &'a [u8]) -> Result<RecordData<'a>> {
+ match type_ {
+ #[cfg(feature = "proto-ipv4")]
+ Type::A => {
+ if data.len() != 4 {
+ return Err(Error);
+ }
+ Ok(RecordData::A(Ipv4Address::from_bytes(data)))
+ }
+ #[cfg(feature = "proto-ipv6")]
+ Type::Aaaa => {
+ if data.len() != 16 {
+ return Err(Error);
+ }
+ Ok(RecordData::Aaaa(Ipv6Address::from_bytes(data)))
+ }
+ Type::Cname => Ok(RecordData::Cname(data)),
+ x => Ok(RecordData::Other(x, data)),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecordData<'a> {
+ #[cfg(feature = "proto-ipv4")]
+ A(Ipv4Address),
+ #[cfg(feature = "proto-ipv6")]
+ Aaaa(Ipv6Address),
+ Cname(&'a [u8]),
+ Other(Type, &'a [u8]),
+}
+
+impl<'a> Record<'a> {
+ pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Record<'a>)> {
+ let (rest, _) = parse_name_part(buffer, |_| ())?;
+ let name = &buffer[..buffer.len() - rest.len()];
+
+ if rest.len() < 10 {
+ return Err(Error);
+ }
+ let type_ = NetworkEndian::read_u16(&rest[0..2]).into();
+ let class = NetworkEndian::read_u16(&rest[2..4]);
+ let ttl = NetworkEndian::read_u32(&rest[4..8]);
+ let len = NetworkEndian::read_u16(&rest[8..10]) as usize;
+ let rest = &rest[10..];
+
+ if class != CLASS_IN {
+ return Err(Error);
+ }
+
+ let data = rest.get(..len).ok_or(Error)?;
+ let rest = &rest[len..];
+
+ Ok((
+ rest,
+ Record {
+ name,
+ ttl,
+ data: RecordData::parse(type_, data)?,
+ },
+ ))
+ }
+}
+
+/// High-level DNS packet representation.
+///
+/// Currently only supports query packets.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+ pub transaction_id: u16,
+ pub opcode: Opcode,
+ pub flags: Flags,
+ pub question: Question<'a>,
+}
+
+impl<'a> Repr<'a> {
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ field::HEADER_END + self.question.buffer_len()
+ }
+
+ /// Emit a high-level representation into a DNS packet.
+ pub fn emit<T: ?Sized>(&self, packet: &mut Packet<&mut T>)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]>,
+ {
+ packet.set_transaction_id(self.transaction_id);
+ packet.set_flags(self.flags);
+ packet.set_opcode(self.opcode);
+ packet.set_question_count(1);
+ packet.set_answer_record_count(0);
+ packet.set_authority_record_count(0);
+ packet.set_additional_record_count(0);
+ self.question.emit(packet.payload_mut())
+ }
+}
+
+#[cfg(feature = "proto-ipv4")] // tests assume ipv4
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::vec::Vec;
+
+ #[test]
+ fn test_parse_name() {
+ let bytes = &[
+ 0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77,
+ 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f,
+ 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
+ 0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69,
+ 0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24,
+ ];
+ let packet = Packet::new_unchecked(bytes);
+
+ let name_vec = |bytes| {
+ let mut v = Vec::new();
+ packet
+ .parse_name(bytes)
+ .try_for_each(|label| label.map(|label| v.push(label)))
+ .map(|_| v)
+ };
+
+ //assert_eq!(parse_name_len(bytes, 0x0c), Ok(18));
+ assert_eq!(
+ name_vec(&bytes[0x0c..]),
+ Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]])
+ );
+ //assert_eq!(parse_name_len(bytes, 0x22), Ok(2));
+ assert_eq!(
+ name_vec(&bytes[0x22..]),
+ Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]])
+ );
+ //assert_eq!(parse_name_len(bytes, 0x2e), Ok(17));
+ assert_eq!(
+ name_vec(&bytes[0x2e..]),
+ Ok(vec![
+ &b"star-mini"[..],
+ &b"c10r"[..],
+ &b"facebook"[..],
+ &b"com"[..]
+ ])
+ );
+ //assert_eq!(parse_name_len(bytes, 0x3f), Ok(2));
+ assert_eq!(
+ name_vec(&bytes[0x3f..]),
+ Ok(vec![
+ &b"star-mini"[..],
+ &b"c10r"[..],
+ &b"facebook"[..],
+ &b"com"[..]
+ ])
+ );
+ }
+
+ struct Parsed<'a> {
+ packet: Packet<&'a [u8]>,
+ questions: Vec<Question<'a>>,
+ answers: Vec<Record<'a>>,
+ authorities: Vec<Record<'a>>,
+ additionals: Vec<Record<'a>>,
+ }
+
+ impl<'a> Parsed<'a> {
+ fn parse(bytes: &'a [u8]) -> Result<Self> {
+ let packet = Packet::new_unchecked(bytes);
+ let mut questions = Vec::new();
+ let mut answers = Vec::new();
+ let mut authorities = Vec::new();
+ let mut additionals = Vec::new();
+
+ let mut payload = &bytes[12..];
+
+ for _ in 0..packet.question_count() {
+ let (p, r) = Question::parse(payload)?;
+ questions.push(r);
+ payload = p;
+ }
+ for _ in 0..packet.answer_record_count() {
+ let (p, r) = Record::parse(payload)?;
+ answers.push(r);
+ payload = p;
+ }
+ for _ in 0..packet.authority_record_count() {
+ let (p, r) = Record::parse(payload)?;
+ authorities.push(r);
+ payload = p;
+ }
+ for _ in 0..packet.additional_record_count() {
+ let (p, r) = Record::parse(payload)?;
+ additionals.push(r);
+ payload = p;
+ }
+
+ // Check that there are no bytes left
+ assert_eq!(payload.len(), 0);
+
+ Ok(Parsed {
+ packet,
+ questions,
+ answers,
+ authorities,
+ additionals,
+ })
+ }
+ }
+
+ #[test]
+ fn test_parse_request() {
+ let p = Parsed::parse(&[
+ 0x51, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+ ])
+ .unwrap();
+
+ assert_eq!(p.packet.transaction_id(), 0x5184);
+ assert_eq!(
+ p.packet.flags(),
+ Flags::RECURSION_DESIRED | Flags::AUTHENTIC_DATA
+ );
+ assert_eq!(p.packet.opcode(), Opcode::Query);
+ assert_eq!(p.packet.question_count(), 1);
+ assert_eq!(p.packet.answer_record_count(), 0);
+ assert_eq!(p.packet.authority_record_count(), 0);
+ assert_eq!(p.packet.additional_record_count(), 0);
+
+ assert_eq!(p.questions.len(), 1);
+ assert_eq!(
+ p.questions[0].name,
+ &[0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00]
+ );
+ assert_eq!(p.questions[0].type_, Type::A);
+
+ assert_eq!(p.answers.len(), 0);
+ assert_eq!(p.authorities.len(), 0);
+ assert_eq!(p.additionals.len(), 0);
+ }
+
+ #[test]
+ fn test_parse_response() {
+ let p = Parsed::parse(&[
+ 0x51, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xca, 0x00, 0x04, 0xac, 0xd9,
+ 0xa8, 0xae,
+ ])
+ .unwrap();
+
+ assert_eq!(p.packet.transaction_id(), 0x5184);
+ assert_eq!(
+ p.packet.flags(),
+ Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+ );
+ assert_eq!(p.packet.opcode(), Opcode::Query);
+ assert_eq!(p.packet.rcode(), Rcode::NoError);
+ assert_eq!(p.packet.question_count(), 1);
+ assert_eq!(p.packet.answer_record_count(), 1);
+ assert_eq!(p.packet.authority_record_count(), 0);
+ assert_eq!(p.packet.additional_record_count(), 0);
+
+ assert_eq!(
+ p.questions[0].name,
+ &[0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00]
+ );
+ assert_eq!(p.questions[0].type_, Type::A);
+
+ assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[0].ttl, 202);
+ assert_eq!(
+ p.answers[0].data,
+ RecordData::A(Ipv4Address::new(0xac, 0xd9, 0xa8, 0xae))
+ );
+ }
+
+ #[test]
+ fn test_parse_response_multiple_a() {
+ let p = Parsed::parse(&[
+ 0x4b, 0x9e, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72,
+ 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00,
+ 0x04, 0x0d, 0xe0, 0x77, 0x35, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x28, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x43, 0xc0, 0x0c, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x62,
+ ])
+ .unwrap();
+
+ assert_eq!(p.packet.transaction_id(), 0x4b9e);
+ assert_eq!(
+ p.packet.flags(),
+ Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+ );
+ assert_eq!(p.packet.opcode(), Opcode::Query);
+ assert_eq!(p.packet.rcode(), Rcode::NoError);
+ assert_eq!(p.packet.question_count(), 1);
+ assert_eq!(p.packet.answer_record_count(), 4);
+ assert_eq!(p.packet.authority_record_count(), 0);
+ assert_eq!(p.packet.additional_record_count(), 0);
+
+ assert_eq!(
+ p.questions[0].name,
+ &[
+ 0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67,
+ 0x00
+ ]
+ );
+ assert_eq!(p.questions[0].type_, Type::A);
+
+ assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[0].ttl, 9);
+ assert_eq!(
+ p.answers[0].data,
+ RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x35))
+ );
+
+ assert_eq!(p.answers[1].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[1].ttl, 9);
+ assert_eq!(
+ p.answers[1].data,
+ RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x28))
+ );
+
+ assert_eq!(p.answers[2].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[2].ttl, 9);
+ assert_eq!(
+ p.answers[2].data,
+ RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x43))
+ );
+
+ assert_eq!(p.answers[3].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[3].ttl, 9);
+ assert_eq!(
+ p.answers[3].data,
+ RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x62))
+ );
+ }
+
+ #[test]
+ fn test_parse_response_cname() {
+ let p = Parsed::parse(&[
+ 0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77,
+ 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f,
+ 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
+ 0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69,
+ 0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24,
+ ])
+ .unwrap();
+
+ assert_eq!(p.packet.transaction_id(), 0x786c);
+ assert_eq!(
+ p.packet.flags(),
+ Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+ );
+ assert_eq!(p.packet.opcode(), Opcode::Query);
+ assert_eq!(p.packet.rcode(), Rcode::NoError);
+ assert_eq!(p.packet.question_count(), 1);
+ assert_eq!(p.packet.answer_record_count(), 2);
+ assert_eq!(p.packet.authority_record_count(), 0);
+ assert_eq!(p.packet.additional_record_count(), 0);
+
+ assert_eq!(
+ p.questions[0].name,
+ &[
+ 0x03, 0x77, 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00
+ ]
+ );
+ assert_eq!(p.questions[0].type_, Type::A);
+
+ // cname
+ assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+ assert_eq!(p.answers[0].ttl, 1523);
+ assert_eq!(
+ p.answers[0].data,
+ RecordData::Cname(&[
+ 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69, 0x04, 0x63, 0x31, 0x30,
+ 0x72, 0xc0, 0x10
+ ])
+ );
+ // a
+ assert_eq!(p.answers[1].name, &[0xc0, 0x2e]);
+ assert_eq!(p.answers[1].ttl, 5);
+ assert_eq!(
+ p.answers[1].data,
+ RecordData::A(Ipv4Address::new(0x1f, 0x0d, 0x53, 0x24))
+ );
+ }
+
+ #[test]
+ fn test_parse_response_nxdomain() {
+ let p = Parsed::parse(&[
+ 0x63, 0xc4, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x13, 0x61,
+ 0x68, 0x61, 0x73, 0x64, 0x67, 0x68, 0x6c, 0x61, 0x6b, 0x73, 0x6a, 0x68, 0x62, 0x61,
+ 0x61, 0x73, 0x6c, 0x64, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0,
+ 0x20, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x03, 0x83, 0x00, 0x3d, 0x01, 0x61, 0x0c,
+ 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e,
+ 0x65, 0x74, 0x00, 0x05, 0x6e, 0x73, 0x74, 0x6c, 0x64, 0x0c, 0x76, 0x65, 0x72, 0x69,
+ 0x73, 0x69, 0x67, 0x6e, 0x2d, 0x67, 0x72, 0x73, 0xc0, 0x20, 0x5f, 0xce, 0x8b, 0x85,
+ 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x03, 0x84, 0x00, 0x09, 0x3a, 0x80, 0x00, 0x01,
+ 0x51, 0x80,
+ ])
+ .unwrap();
+
+ assert_eq!(p.packet.transaction_id(), 0x63c4);
+ assert_eq!(
+ p.packet.flags(),
+ Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+ );
+ assert_eq!(p.packet.opcode(), Opcode::Query);
+ assert_eq!(p.packet.rcode(), Rcode::NXDomain);
+ assert_eq!(p.packet.question_count(), 1);
+ assert_eq!(p.packet.answer_record_count(), 0);
+ assert_eq!(p.packet.authority_record_count(), 1);
+ assert_eq!(p.packet.additional_record_count(), 0);
+
+ assert_eq!(p.questions[0].type_, Type::A);
+
+ // SOA authority
+ assert_eq!(p.authorities[0].name, &[0xc0, 0x20]); // com.
+ assert_eq!(p.authorities[0].ttl, 899);
+ assert!(matches!(
+ p.authorities[0].data,
+ RecordData::Other(Type::Soa, _)
+ ));
+ }
+
+ #[test]
+ fn test_emit() {
+ let name = &[
+ 0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67,
+ 0x00,
+ ];
+
+ let repr = Repr {
+ transaction_id: 0x1234,
+ flags: Flags::RECURSION_DESIRED,
+ opcode: Opcode::Query,
+ question: Question {
+ name,
+ type_: Type::A,
+ },
+ };
+
+ let mut buf = Vec::new();
+ buf.resize(repr.buffer_len(), 0);
+ repr.emit(&mut Packet::new_unchecked(&mut buf));
+
+ let want = &[
+ 0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72,
+ 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+ 0x01, 0x00, 0x01,
+ ];
+ assert_eq!(&buf, want);
+ }
+}
diff --git a/src/wire/ethernet.rs b/src/wire/ethernet.rs
new file mode 100644
index 0000000..53dc1ea
--- /dev/null
+++ b/src/wire/ethernet.rs
@@ -0,0 +1,400 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+
+enum_with_unknown! {
+ /// Ethernet protocol type.
+ pub enum EtherType(u16) {
+ Ipv4 = 0x0800,
+ Arp = 0x0806,
+ Ipv6 = 0x86DD
+ }
+}
+
+impl fmt::Display for EtherType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ EtherType::Ipv4 => write!(f, "IPv4"),
+ EtherType::Ipv6 => write!(f, "IPv6"),
+ EtherType::Arp => write!(f, "ARP"),
+ EtherType::Unknown(id) => write!(f, "0x{id:04x}"),
+ }
+ }
+}
+
+/// A six-octet Ethernet II address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Address(pub [u8; 6]);
+
+impl Address {
+ /// The broadcast address.
+ pub const BROADCAST: Address = Address([0xff; 6]);
+
+ /// Construct an Ethernet address from a sequence of octets, in big-endian.
+ ///
+ /// # Panics
+ /// The function panics if `data` is not six octets long.
+ pub fn from_bytes(data: &[u8]) -> Address {
+ let mut bytes = [0; 6];
+ bytes.copy_from_slice(data);
+ Address(bytes)
+ }
+
+ /// Return an Ethernet address as a sequence of octets, in big-endian.
+ pub const fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+
+ /// Query whether the address is an unicast address.
+ pub fn is_unicast(&self) -> bool {
+ !(self.is_broadcast() || self.is_multicast())
+ }
+
+ /// Query whether this address is the broadcast address.
+ pub fn is_broadcast(&self) -> bool {
+ *self == Self::BROADCAST
+ }
+
+ /// Query whether the "multicast" bit in the OUI is set.
+ pub const fn is_multicast(&self) -> bool {
+ self.0[0] & 0x01 != 0
+ }
+
+ /// Query whether the "locally administered" bit in the OUI is set.
+ pub const fn is_local(&self) -> bool {
+ self.0[0] & 0x02 != 0
+ }
+}
+
+impl fmt::Display for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let bytes = self.0;
+ write!(
+ f,
+ "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
+ )
+ }
+}
+
+/// A read/write wrapper around an Ethernet II frame buffer.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Frame<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const DESTINATION: Field = 0..6;
+ pub const SOURCE: Field = 6..12;
+ pub const ETHERTYPE: Field = 12..14;
+ pub const PAYLOAD: Rest = 14..;
+}
+
+/// The Ethernet header length
+pub const HEADER_LEN: usize = field::PAYLOAD.start;
+
+impl<T: AsRef<[u8]>> Frame<T> {
+ /// Imbue a raw octet buffer with Ethernet frame structure.
+ pub const fn new_unchecked(buffer: T) -> Frame<T> {
+ Frame { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Frame<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < HEADER_LEN {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the length of a frame header.
+ pub const fn header_len() -> usize {
+ HEADER_LEN
+ }
+
+ /// Return the length of a buffer required to hold a packet with the payload
+ /// of a given length.
+ pub const fn buffer_len(payload_len: usize) -> usize {
+ HEADER_LEN + payload_len
+ }
+
+ /// Return the destination address field.
+ #[inline]
+ pub fn dst_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::DESTINATION])
+ }
+
+ /// Return the source address field.
+ #[inline]
+ pub fn src_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::SOURCE])
+ }
+
+ /// Return the EtherType field, without checking for 802.1Q.
+ #[inline]
+ pub fn ethertype(&self) -> EtherType {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::ETHERTYPE]);
+ EtherType::from(raw)
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Frame<&'a T> {
+ /// Return a pointer to the payload, without checking for 802.1Q.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ &data[field::PAYLOAD]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Frame<T> {
+ /// Set the destination address field.
+ #[inline]
+ pub fn set_dst_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::DESTINATION].copy_from_slice(value.as_bytes())
+ }
+
+ /// Set the source address field.
+ #[inline]
+ pub fn set_src_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::SOURCE].copy_from_slice(value.as_bytes())
+ }
+
+ /// Set the EtherType field.
+ #[inline]
+ pub fn set_ethertype(&mut self, value: EtherType) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ETHERTYPE], value.into())
+ }
+
+ /// Return a mutable pointer to the payload.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let data = self.buffer.as_mut();
+ &mut data[field::PAYLOAD]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Frame<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+impl<T: AsRef<[u8]>> fmt::Display for Frame<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "EthernetII src={} dst={} type={}",
+ self.src_addr(),
+ self.dst_addr(),
+ self.ethertype()
+ )
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Frame<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ let frame = match Frame::new_checked(buffer) {
+ Err(err) => return write!(f, "{indent}({err})"),
+ Ok(frame) => frame,
+ };
+ write!(f, "{indent}{frame}")?;
+
+ match frame.ethertype() {
+ #[cfg(feature = "proto-ipv4")]
+ EtherType::Arp => {
+ indent.increase(f)?;
+ super::ArpPacket::<&[u8]>::pretty_print(&frame.payload(), f, indent)
+ }
+ #[cfg(feature = "proto-ipv4")]
+ EtherType::Ipv4 => {
+ indent.increase(f)?;
+ super::Ipv4Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent)
+ }
+ #[cfg(feature = "proto-ipv6")]
+ EtherType::Ipv6 => {
+ indent.increase(f)?;
+ super::Ipv6Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent)
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+/// A high-level representation of an Internet Protocol version 4 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr {
+ pub src_addr: Address,
+ pub dst_addr: Address,
+ pub ethertype: EtherType,
+}
+
+impl Repr {
+ /// Parse an Ethernet II frame and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(frame: &Frame<&T>) -> Result<Repr> {
+ frame.check_len()?;
+ Ok(Repr {
+ src_addr: frame.src_addr(),
+ dst_addr: frame.dst_addr(),
+ ethertype: frame.ethertype(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ HEADER_LEN
+ }
+
+ /// Emit a high-level representation into an Ethernet II frame.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, frame: &mut Frame<T>) {
+ frame.set_src_addr(self.src_addr);
+ frame.set_dst_addr(self.dst_addr);
+ frame.set_ethertype(self.ethertype);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ // Tests that are valid with any combination of
+ // "proto-*" features.
+ use super::*;
+
+ #[test]
+ fn test_broadcast() {
+ assert!(Address::BROADCAST.is_broadcast());
+ assert!(!Address::BROADCAST.is_unicast());
+ assert!(Address::BROADCAST.is_multicast());
+ assert!(Address::BROADCAST.is_local());
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "proto-ipv4")]
+mod test_ipv4 {
+ // Tests that are valid only with "proto-ipv4"
+ use super::*;
+
+ static FRAME_BYTES: [u8; 64] = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08, 0x00, 0xaa,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ ];
+
+ static PAYLOAD_BYTES: [u8; 50] = [
+ 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let frame = Frame::new_unchecked(&FRAME_BYTES[..]);
+ assert_eq!(
+ frame.dst_addr(),
+ Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
+ );
+ assert_eq!(
+ frame.src_addr(),
+ Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
+ );
+ assert_eq!(frame.ethertype(), EtherType::Ipv4);
+ assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]);
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 64];
+ let mut frame = Frame::new_unchecked(&mut bytes);
+ frame.set_dst_addr(Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
+ frame.set_src_addr(Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]));
+ frame.set_ethertype(EtherType::Ipv4);
+ frame.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
+ assert_eq!(&frame.into_inner()[..], &FRAME_BYTES[..]);
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "proto-ipv6")]
+mod test_ipv6 {
+ // Tests that are valid only with "proto-ipv6"
+ use super::*;
+
+ static FRAME_BYTES: [u8; 54] = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x86, 0xdd, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ ];
+
+ static PAYLOAD_BYTES: [u8; 40] = [
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let frame = Frame::new_unchecked(&FRAME_BYTES[..]);
+ assert_eq!(
+ frame.dst_addr(),
+ Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
+ );
+ assert_eq!(
+ frame.src_addr(),
+ Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
+ );
+ assert_eq!(frame.ethertype(), EtherType::Ipv6);
+ assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]);
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 54];
+ let mut frame = Frame::new_unchecked(&mut bytes);
+ frame.set_dst_addr(Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
+ frame.set_src_addr(Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]));
+ frame.set_ethertype(EtherType::Ipv6);
+ assert_eq!(PAYLOAD_BYTES.len(), frame.payload_mut().len());
+ frame.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
+ assert_eq!(&frame.into_inner()[..], &FRAME_BYTES[..]);
+ }
+}
diff --git a/src/wire/icmp.rs b/src/wire/icmp.rs
new file mode 100644
index 0000000..6bbc574
--- /dev/null
+++ b/src/wire/icmp.rs
@@ -0,0 +1,25 @@
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::icmpv4;
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::icmpv6;
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr<'a> {
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4(icmpv4::Repr<'a>),
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6(icmpv6::Repr<'a>),
+}
+#[cfg(feature = "proto-ipv4")]
+impl<'a> From<icmpv4::Repr<'a>> for Repr<'a> {
+ fn from(s: icmpv4::Repr<'a>) -> Self {
+ Repr::Ipv4(s)
+ }
+}
+#[cfg(feature = "proto-ipv6")]
+impl<'a> From<icmpv6::Repr<'a>> for Repr<'a> {
+ fn from(s: icmpv6::Repr<'a>) -> Self {
+ Repr::Ipv6(s)
+ }
+}
diff --git a/src/wire/icmpv4.rs b/src/wire/icmpv4.rs
new file mode 100644
index 0000000..60e1215
--- /dev/null
+++ b/src/wire/icmpv4.rs
@@ -0,0 +1,702 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::{cmp, fmt};
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+use crate::wire::ip::checksum;
+use crate::wire::{Ipv4Packet, Ipv4Repr};
+
+enum_with_unknown! {
+ /// Internet protocol control message type.
+ pub enum Message(u8) {
+ /// Echo reply
+ EchoReply = 0,
+ /// Destination unreachable
+ DstUnreachable = 3,
+ /// Message redirect
+ Redirect = 5,
+ /// Echo request
+ EchoRequest = 8,
+ /// Router advertisement
+ RouterAdvert = 9,
+ /// Router solicitation
+ RouterSolicit = 10,
+ /// Time exceeded
+ TimeExceeded = 11,
+ /// Parameter problem
+ ParamProblem = 12,
+ /// Timestamp
+ Timestamp = 13,
+ /// Timestamp reply
+ TimestampReply = 14
+ }
+}
+
+impl fmt::Display for Message {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Message::EchoReply => write!(f, "echo reply"),
+ Message::DstUnreachable => write!(f, "destination unreachable"),
+ Message::Redirect => write!(f, "message redirect"),
+ Message::EchoRequest => write!(f, "echo request"),
+ Message::RouterAdvert => write!(f, "router advertisement"),
+ Message::RouterSolicit => write!(f, "router solicitation"),
+ Message::TimeExceeded => write!(f, "time exceeded"),
+ Message::ParamProblem => write!(f, "parameter problem"),
+ Message::Timestamp => write!(f, "timestamp"),
+ Message::TimestampReply => write!(f, "timestamp reply"),
+ Message::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for type "Destination Unreachable".
+ pub enum DstUnreachable(u8) {
+ /// Destination network unreachable
+ NetUnreachable = 0,
+ /// Destination host unreachable
+ HostUnreachable = 1,
+ /// Destination protocol unreachable
+ ProtoUnreachable = 2,
+ /// Destination port unreachable
+ PortUnreachable = 3,
+ /// Fragmentation required, and DF flag set
+ FragRequired = 4,
+ /// Source route failed
+ SrcRouteFailed = 5,
+ /// Destination network unknown
+ DstNetUnknown = 6,
+ /// Destination host unknown
+ DstHostUnknown = 7,
+ /// Source host isolated
+ SrcHostIsolated = 8,
+ /// Network administratively prohibited
+ NetProhibited = 9,
+ /// Host administratively prohibited
+ HostProhibited = 10,
+ /// Network unreachable for ToS
+ NetUnreachToS = 11,
+ /// Host unreachable for ToS
+ HostUnreachToS = 12,
+ /// Communication administratively prohibited
+ CommProhibited = 13,
+ /// Host precedence violation
+ HostPrecedViol = 14,
+ /// Precedence cutoff in effect
+ PrecedCutoff = 15
+ }
+}
+
+impl fmt::Display for DstUnreachable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ DstUnreachable::NetUnreachable => write!(f, "destination network unreachable"),
+ DstUnreachable::HostUnreachable => write!(f, "destination host unreachable"),
+ DstUnreachable::ProtoUnreachable => write!(f, "destination protocol unreachable"),
+ DstUnreachable::PortUnreachable => write!(f, "destination port unreachable"),
+ DstUnreachable::FragRequired => write!(f, "fragmentation required, and DF flag set"),
+ DstUnreachable::SrcRouteFailed => write!(f, "source route failed"),
+ DstUnreachable::DstNetUnknown => write!(f, "destination network unknown"),
+ DstUnreachable::DstHostUnknown => write!(f, "destination host unknown"),
+ DstUnreachable::SrcHostIsolated => write!(f, "source host isolated"),
+ DstUnreachable::NetProhibited => write!(f, "network administratively prohibited"),
+ DstUnreachable::HostProhibited => write!(f, "host administratively prohibited"),
+ DstUnreachable::NetUnreachToS => write!(f, "network unreachable for ToS"),
+ DstUnreachable::HostUnreachToS => write!(f, "host unreachable for ToS"),
+ DstUnreachable::CommProhibited => {
+ write!(f, "communication administratively prohibited")
+ }
+ DstUnreachable::HostPrecedViol => write!(f, "host precedence violation"),
+ DstUnreachable::PrecedCutoff => write!(f, "precedence cutoff in effect"),
+ DstUnreachable::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for type "Redirect Message".
+ pub enum Redirect(u8) {
+ /// Redirect Datagram for the Network
+ Net = 0,
+ /// Redirect Datagram for the Host
+ Host = 1,
+ /// Redirect Datagram for the ToS & network
+ NetToS = 2,
+ /// Redirect Datagram for the ToS & host
+ HostToS = 3
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for type "Time Exceeded".
+ pub enum TimeExceeded(u8) {
+ /// TTL expired in transit
+ TtlExpired = 0,
+ /// Fragment reassembly time exceeded
+ FragExpired = 1
+ }
+}
+
+impl fmt::Display for TimeExceeded {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ TimeExceeded::TtlExpired => write!(f, "time-to-live exceeded in transit"),
+ TimeExceeded::FragExpired => write!(f, "fragment reassembly time exceeded"),
+ TimeExceeded::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for type "Parameter Problem".
+ pub enum ParamProblem(u8) {
+ /// Pointer indicates the error
+ AtPointer = 0,
+ /// Missing a required option
+ MissingOption = 1,
+ /// Bad length
+ BadLength = 2
+ }
+}
+
+/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const TYPE: usize = 0;
+ pub const CODE: usize = 1;
+ pub const CHECKSUM: Field = 2..4;
+
+ pub const UNUSED: Field = 4..8;
+
+ pub const ECHO_IDENT: Field = 4..6;
+ pub const ECHO_SEQNO: Field = 6..8;
+
+ pub const HEADER_END: usize = 8;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with ICMPv4 packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::HEADER_END {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the message type field.
+ #[inline]
+ pub fn msg_type(&self) -> Message {
+ let data = self.buffer.as_ref();
+ Message::from(data[field::TYPE])
+ }
+
+ /// Return the message code field.
+ #[inline]
+ pub fn msg_code(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::CODE]
+ }
+
+ /// Return the checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Return the identifier field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn echo_ident(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::ECHO_IDENT])
+ }
+
+ /// Return the sequence number field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn echo_seq_no(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
+ }
+
+ /// Return the header length.
+ /// The result depends on the value of the message type field.
+ pub fn header_len(&self) -> usize {
+ match self.msg_type() {
+ Message::EchoRequest => field::ECHO_SEQNO.end,
+ Message::EchoReply => field::ECHO_SEQNO.end,
+ Message::DstUnreachable => field::UNUSED.end,
+ _ => field::UNUSED.end, // make a conservative assumption
+ }
+ }
+
+ /// Validate the header checksum.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::data(data) == !0
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the type-specific data.
+ #[inline]
+ pub fn data(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ &data[self.header_len()..]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the message type field.
+ #[inline]
+ pub fn set_msg_type(&mut self, value: Message) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into()
+ }
+
+ /// Set the message code field.
+ #[inline]
+ pub fn set_msg_code(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::CODE] = value
+ }
+
+ /// Set the checksum field.
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Set the identifier field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn set_echo_ident(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
+ }
+
+ /// Set the sequence number field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn set_echo_seq_no(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
+ }
+
+ /// Compute and fill in the header checksum.
+ pub fn fill_checksum(&mut self) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::data(data)
+ };
+ self.set_checksum(checksum)
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
+ /// Return a mutable pointer to the type-specific data.
+ #[inline]
+ pub fn data_mut(&mut self) -> &mut [u8] {
+ let range = self.header_len()..;
+ let data = self.buffer.as_mut();
+ &mut data[range]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A high-level representation of an Internet Control Message Protocol version 4 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Repr<'a> {
+ EchoRequest {
+ ident: u16,
+ seq_no: u16,
+ data: &'a [u8],
+ },
+ EchoReply {
+ ident: u16,
+ seq_no: u16,
+ data: &'a [u8],
+ },
+ DstUnreachable {
+ reason: DstUnreachable,
+ header: Ipv4Repr,
+ data: &'a [u8],
+ },
+ TimeExceeded {
+ reason: TimeExceeded,
+ header: Ipv4Repr,
+ data: &'a [u8],
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an Internet Control Message Protocol version 4 packet and return
+ /// a high-level representation.
+ pub fn parse<T>(
+ packet: &Packet<&'a T>,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ // Valid checksum is expected.
+ if checksum_caps.icmpv4.rx() && !packet.verify_checksum() {
+ return Err(Error);
+ }
+
+ match (packet.msg_type(), packet.msg_code()) {
+ (Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
+ ident: packet.echo_ident(),
+ seq_no: packet.echo_seq_no(),
+ data: packet.data(),
+ }),
+
+ (Message::EchoReply, 0) => Ok(Repr::EchoReply {
+ ident: packet.echo_ident(),
+ seq_no: packet.echo_seq_no(),
+ data: packet.data(),
+ }),
+
+ (Message::DstUnreachable, code) => {
+ let ip_packet = Ipv4Packet::new_checked(packet.data())?;
+
+ let payload = &packet.data()[ip_packet.header_len() as usize..];
+ // RFC 792 requires exactly eight bytes to be returned.
+ // We allow more, since there isn't a reason not to, but require at least eight.
+ if payload.len() < 8 {
+ return Err(Error);
+ }
+
+ Ok(Repr::DstUnreachable {
+ reason: DstUnreachable::from(code),
+ header: Ipv4Repr {
+ src_addr: ip_packet.src_addr(),
+ dst_addr: ip_packet.dst_addr(),
+ next_header: ip_packet.next_header(),
+ payload_len: payload.len(),
+ hop_limit: ip_packet.hop_limit(),
+ },
+ data: payload,
+ })
+ }
+
+ (Message::TimeExceeded, code) => {
+ let ip_packet = Ipv4Packet::new_checked(packet.data())?;
+
+ let payload = &packet.data()[ip_packet.header_len() as usize..];
+ // RFC 792 requires exactly eight bytes to be returned.
+ // We allow more, since there isn't a reason not to, but require at least eight.
+ if payload.len() < 8 {
+ return Err(Error);
+ }
+
+ Ok(Repr::TimeExceeded {
+ reason: TimeExceeded::from(code),
+ header: Ipv4Repr {
+ src_addr: ip_packet.src_addr(),
+ dst_addr: ip_packet.dst_addr(),
+ next_header: ip_packet.next_header(),
+ payload_len: payload.len(),
+ hop_limit: ip_packet.hop_limit(),
+ },
+ data: payload,
+ })
+ }
+
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
+ field::ECHO_SEQNO.end + data.len()
+ }
+ &Repr::DstUnreachable { header, data, .. }
+ | &Repr::TimeExceeded { header, data, .. } => {
+ field::UNUSED.end + header.buffer_len() + data.len()
+ }
+ }
+ }
+
+ /// Emit a high-level representation into an Internet Control Message Protocol version 4
+ /// packet.
+ pub fn emit<T>(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ packet.set_msg_code(0);
+ match *self {
+ Repr::EchoRequest {
+ ident,
+ seq_no,
+ data,
+ } => {
+ packet.set_msg_type(Message::EchoRequest);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(ident);
+ packet.set_echo_seq_no(seq_no);
+ let data_len = cmp::min(packet.data_mut().len(), data.len());
+ packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
+ }
+
+ Repr::EchoReply {
+ ident,
+ seq_no,
+ data,
+ } => {
+ packet.set_msg_type(Message::EchoReply);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(ident);
+ packet.set_echo_seq_no(seq_no);
+ let data_len = cmp::min(packet.data_mut().len(), data.len());
+ packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
+ }
+
+ Repr::DstUnreachable {
+ reason,
+ header,
+ data,
+ } => {
+ packet.set_msg_type(Message::DstUnreachable);
+ packet.set_msg_code(reason.into());
+
+ let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut());
+ header.emit(&mut ip_packet, checksum_caps);
+ let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
+ payload.copy_from_slice(data)
+ }
+
+ Repr::TimeExceeded {
+ reason,
+ header,
+ data,
+ } => {
+ packet.set_msg_type(Message::TimeExceeded);
+ packet.set_msg_code(reason.into());
+
+ let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut());
+ header.emit(&mut ip_packet, checksum_caps);
+ let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
+ payload.copy_from_slice(data)
+ }
+ }
+
+ if checksum_caps.icmpv4.tx() {
+ packet.fill_checksum()
+ } else {
+ // make sure we get a consistently zeroed checksum,
+ // since implementations might rely on it
+ packet.set_checksum(0);
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self, &ChecksumCapabilities::default()) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "ICMPv4 ({err})")?;
+ write!(f, " type={:?}", self.msg_type())?;
+ match self.msg_type() {
+ Message::DstUnreachable => {
+ write!(f, " code={:?}", DstUnreachable::from(self.msg_code()))
+ }
+ Message::TimeExceeded => {
+ write!(f, " code={:?}", TimeExceeded::from(self.msg_code()))
+ }
+ _ => write!(f, " code={}", self.msg_code()),
+ }
+ }
+ }
+ }
+}
+
+impl<'a> fmt::Display for Repr<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Repr::EchoRequest {
+ ident,
+ seq_no,
+ data,
+ } => write!(
+ f,
+ "ICMPv4 echo request id={} seq={} len={}",
+ ident,
+ seq_no,
+ data.len()
+ ),
+ Repr::EchoReply {
+ ident,
+ seq_no,
+ data,
+ } => write!(
+ f,
+ "ICMPv4 echo reply id={} seq={} len={}",
+ ident,
+ seq_no,
+ data.len()
+ ),
+ Repr::DstUnreachable { reason, .. } => {
+ write!(f, "ICMPv4 destination unreachable ({reason})")
+ }
+ Repr::TimeExceeded { reason, .. } => {
+ write!(f, "ICMPv4 time exceeded ({reason})")
+ }
+ }
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ let packet = match Packet::new_checked(buffer) {
+ Err(err) => return write!(f, "{indent}({err})"),
+ Ok(packet) => packet,
+ };
+ write!(f, "{indent}{packet}")?;
+
+ match packet.msg_type() {
+ Message::DstUnreachable | Message::TimeExceeded => {
+ indent.increase(f)?;
+ super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent)
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static ECHO_PACKET_BYTES: [u8; 12] = [
+ 0x08, 0x00, 0x8e, 0xfe, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ static ECHO_DATA_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+
+ #[test]
+ fn test_echo_deconstruct() {
+ let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::EchoRequest);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.checksum(), 0x8efe);
+ assert_eq!(packet.echo_ident(), 0x1234);
+ assert_eq!(packet.echo_seq_no(), 0xabcd);
+ assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]);
+ assert!(packet.verify_checksum());
+ }
+
+ #[test]
+ fn test_echo_construct() {
+ let mut bytes = vec![0xa5; 12];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::EchoRequest);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(0x1234);
+ packet.set_echo_seq_no(0xabcd);
+ packet.data_mut().copy_from_slice(&ECHO_DATA_BYTES[..]);
+ packet.fill_checksum();
+ assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
+ }
+
+ fn echo_packet_repr() -> Repr<'static> {
+ Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &ECHO_DATA_BYTES,
+ }
+ }
+
+ #[test]
+ fn test_echo_parse() {
+ let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
+ let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap();
+ assert_eq!(repr, echo_packet_repr());
+ }
+
+ #[test]
+ fn test_echo_emit() {
+ let repr = echo_packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet, &ChecksumCapabilities::default());
+ assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_check_len() {
+ let bytes = [0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
+ assert_eq!(Packet::new_checked(&[]), Err(Error));
+ assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error));
+ assert!(Packet::new_checked(&bytes[..]).is_ok());
+ }
+}
diff --git a/src/wire/icmpv6.rs b/src/wire/icmpv6.rs
new file mode 100644
index 0000000..72d2451
--- /dev/null
+++ b/src/wire/icmpv6.rs
@@ -0,0 +1,1085 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::{cmp, fmt};
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+use crate::wire::ip::checksum;
+use crate::wire::MldRepr;
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+use crate::wire::NdiscRepr;
+#[cfg(feature = "proto-rpl")]
+use crate::wire::RplRepr;
+use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
+use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU};
+
+/// Error packets must not exceed min MTU
+const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN;
+
+enum_with_unknown! {
+ /// Internet protocol control message type.
+ pub enum Message(u8) {
+ /// Destination Unreachable.
+ DstUnreachable = 0x01,
+ /// Packet Too Big.
+ PktTooBig = 0x02,
+ /// Time Exceeded.
+ TimeExceeded = 0x03,
+ /// Parameter Problem.
+ ParamProblem = 0x04,
+ /// Echo Request
+ EchoRequest = 0x80,
+ /// Echo Reply
+ EchoReply = 0x81,
+ /// Multicast Listener Query
+ MldQuery = 0x82,
+ /// Router Solicitation
+ RouterSolicit = 0x85,
+ /// Router Advertisement
+ RouterAdvert = 0x86,
+ /// Neighbor Solicitation
+ NeighborSolicit = 0x87,
+ /// Neighbor Advertisement
+ NeighborAdvert = 0x88,
+ /// Redirect
+ Redirect = 0x89,
+ /// Multicast Listener Report
+ MldReport = 0x8f,
+ /// RPL Control Message
+ RplControl = 0x9b,
+ }
+}
+
+impl Message {
+ /// Per [RFC 4443 § 2.1] ICMPv6 message types with the highest order
+ /// bit set are informational messages while message types without
+ /// the highest order bit set are error messages.
+ ///
+ /// [RFC 4443 § 2.1]: https://tools.ietf.org/html/rfc4443#section-2.1
+ pub fn is_error(&self) -> bool {
+ (u8::from(*self) & 0x80) != 0x80
+ }
+
+ /// Return a boolean value indicating if the given message type
+ /// is an [NDISC] message type.
+ ///
+ /// [NDISC]: https://tools.ietf.org/html/rfc4861
+ pub const fn is_ndisc(&self) -> bool {
+ match *self {
+ Message::RouterSolicit
+ | Message::RouterAdvert
+ | Message::NeighborSolicit
+ | Message::NeighborAdvert
+ | Message::Redirect => true,
+ _ => false,
+ }
+ }
+
+ /// Return a boolean value indicating if the given message type
+ /// is an [MLD] message type.
+ ///
+ /// [MLD]: https://tools.ietf.org/html/rfc3810
+ pub const fn is_mld(&self) -> bool {
+ match *self {
+ Message::MldQuery | Message::MldReport => true,
+ _ => false,
+ }
+ }
+}
+
+impl fmt::Display for Message {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Message::DstUnreachable => write!(f, "destination unreachable"),
+ Message::PktTooBig => write!(f, "packet too big"),
+ Message::TimeExceeded => write!(f, "time exceeded"),
+ Message::ParamProblem => write!(f, "parameter problem"),
+ Message::EchoReply => write!(f, "echo reply"),
+ Message::EchoRequest => write!(f, "echo request"),
+ Message::RouterSolicit => write!(f, "router solicitation"),
+ Message::RouterAdvert => write!(f, "router advertisement"),
+ Message::NeighborSolicit => write!(f, "neighbor solicitation"),
+ Message::NeighborAdvert => write!(f, "neighbor advert"),
+ Message::Redirect => write!(f, "redirect"),
+ Message::MldQuery => write!(f, "multicast listener query"),
+ Message::MldReport => write!(f, "multicast listener report"),
+ Message::RplControl => write!(f, "RPL control message"),
+ Message::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for type "Destination Unreachable".
+ pub enum DstUnreachable(u8) {
+ /// No Route to destination.
+ NoRoute = 0,
+ /// Communication with destination administratively prohibited.
+ AdminProhibit = 1,
+ /// Beyond scope of source address.
+ BeyondScope = 2,
+ /// Address unreachable.
+ AddrUnreachable = 3,
+ /// Port unreachable.
+ PortUnreachable = 4,
+ /// Source address failed ingress/egress policy.
+ FailedPolicy = 5,
+ /// Reject route to destination.
+ RejectRoute = 6
+ }
+}
+
+impl fmt::Display for DstUnreachable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ DstUnreachable::NoRoute => write!(f, "no route to destination"),
+ DstUnreachable::AdminProhibit => write!(
+ f,
+ "communication with destination administratively prohibited"
+ ),
+ DstUnreachable::BeyondScope => write!(f, "beyond scope of source address"),
+ DstUnreachable::AddrUnreachable => write!(f, "address unreachable"),
+ DstUnreachable::PortUnreachable => write!(f, "port unreachable"),
+ DstUnreachable::FailedPolicy => {
+ write!(f, "source address failed ingress/egress policy")
+ }
+ DstUnreachable::RejectRoute => write!(f, "reject route to destination"),
+ DstUnreachable::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for the type "Parameter Problem".
+ pub enum ParamProblem(u8) {
+ /// Erroneous header field encountered.
+ ErroneousHdrField = 0,
+ /// Unrecognized Next Header type encountered.
+ UnrecognizedNxtHdr = 1,
+ /// Unrecognized IPv6 option encountered.
+ UnrecognizedOption = 2
+ }
+}
+
+impl fmt::Display for ParamProblem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ParamProblem::ErroneousHdrField => write!(f, "erroneous header field."),
+ ParamProblem::UnrecognizedNxtHdr => write!(f, "unrecognized next header type."),
+ ParamProblem::UnrecognizedOption => write!(f, "unrecognized IPv6 option."),
+ ParamProblem::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Internet protocol control message subtype for the type "Time Exceeded".
+ pub enum TimeExceeded(u8) {
+ /// Hop limit exceeded in transit.
+ HopLimitExceeded = 0,
+ /// Fragment reassembly time exceeded.
+ FragReassemExceeded = 1
+ }
+}
+
+impl fmt::Display for TimeExceeded {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ TimeExceeded::HopLimitExceeded => write!(f, "hop limit exceeded in transit"),
+ TimeExceeded::FragReassemExceeded => write!(f, "fragment reassembly time exceeded"),
+ TimeExceeded::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+/// A read/write wrapper around an Internet Control Message Protocol version 6 packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ pub(super) buffer: T,
+}
+
+// Ranges and constants describing key boundaries in the ICMPv6 header.
+pub(super) mod field {
+ use crate::wire::field::*;
+
+ // ICMPv6: See https://tools.ietf.org/html/rfc4443
+ pub const TYPE: usize = 0;
+ pub const CODE: usize = 1;
+ pub const CHECKSUM: Field = 2..4;
+
+ pub const UNUSED: Field = 4..8;
+ pub const MTU: Field = 4..8;
+ pub const POINTER: Field = 4..8;
+ pub const ECHO_IDENT: Field = 4..6;
+ pub const ECHO_SEQNO: Field = 6..8;
+
+ pub const HEADER_END: usize = 8;
+
+ // NDISC: See https://tools.ietf.org/html/rfc4861
+ // Router Advertisement message offsets
+ pub const CUR_HOP_LIMIT: usize = 4;
+ pub const ROUTER_FLAGS: usize = 5;
+ pub const ROUTER_LT: Field = 6..8;
+ pub const REACHABLE_TM: Field = 8..12;
+ pub const RETRANS_TM: Field = 12..16;
+
+ // Neighbor Solicitation message offsets
+ pub const TARGET_ADDR: Field = 8..24;
+
+ // Neighbor Advertisement message offsets
+ pub const NEIGH_FLAGS: usize = 4;
+
+ // Redirected Header message offsets
+ pub const DEST_ADDR: Field = 24..40;
+
+ // MLD:
+ // - https://tools.ietf.org/html/rfc3810
+ // - https://tools.ietf.org/html/rfc3810
+ // Multicast Listener Query message
+ pub const MAX_RESP_CODE: Field = 4..6;
+ pub const QUERY_RESV: Field = 6..8;
+ pub const QUERY_MCAST_ADDR: Field = 8..24;
+ pub const SQRV: usize = 24;
+ pub const QQIC: usize = 25;
+ pub const QUERY_NUM_SRCS: Field = 26..28;
+
+ // Multicast Listener Report Message
+ pub const RECORD_RESV: Field = 4..6;
+ pub const NR_MCAST_RCRDS: Field = 6..8;
+
+ // Multicast Address Record Offsets
+ pub const RECORD_TYPE: usize = 0;
+ pub const AUX_DATA_LEN: usize = 1;
+ pub const RECORD_NUM_SRCS: Field = 2..4;
+ pub const RECORD_MCAST_ADDR: Field = 4..20;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with ICMPv6 packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+
+ if len < 4 {
+ return Err(Error);
+ }
+
+ match self.msg_type() {
+ Message::DstUnreachable
+ | Message::PktTooBig
+ | Message::TimeExceeded
+ | Message::ParamProblem
+ | Message::EchoRequest
+ | Message::EchoReply
+ | Message::MldQuery
+ | Message::RouterSolicit
+ | Message::RouterAdvert
+ | Message::NeighborSolicit
+ | Message::NeighborAdvert
+ | Message::Redirect
+ | Message::MldReport => {
+ if len < field::HEADER_END || len < self.header_len() {
+ return Err(Error);
+ }
+ }
+ #[cfg(feature = "proto-rpl")]
+ Message::RplControl => match super::rpl::RplControlMessage::from(self.msg_code()) {
+ super::rpl::RplControlMessage::DodagInformationSolicitation => {
+ // TODO(thvdveld): replace magic number
+ if len < 6 {
+ return Err(Error);
+ }
+ }
+ super::rpl::RplControlMessage::DodagInformationObject => {
+ // TODO(thvdveld): replace magic number
+ if len < 28 {
+ return Err(Error);
+ }
+ }
+ super::rpl::RplControlMessage::DestinationAdvertisementObject => {
+ // TODO(thvdveld): replace magic number
+ if len < 8 || (self.dao_dodag_id_present() && len < 24) {
+ return Err(Error);
+ }
+ }
+ super::rpl::RplControlMessage::DestinationAdvertisementObjectAck => {
+ // TODO(thvdveld): replace magic number
+ if len < 8 || (self.dao_dodag_id_present() && len < 24) {
+ return Err(Error);
+ }
+ }
+ super::rpl::RplControlMessage::SecureDodagInformationSolicitation
+ | super::rpl::RplControlMessage::SecureDodagInformationObject
+ | super::rpl::RplControlMessage::SecureDestinationAdvertisementObject
+ | super::rpl::RplControlMessage::SecureDestinationAdvertisementObjectAck
+ | super::rpl::RplControlMessage::ConsistencyCheck => return Err(Error),
+ super::rpl::RplControlMessage::Unknown(_) => return Err(Error),
+ },
+ #[cfg(not(feature = "proto-rpl"))]
+ Message::RplControl => return Err(Error),
+ Message::Unknown(_) => return Err(Error),
+ }
+
+ Ok(())
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the message type field.
+ #[inline]
+ pub fn msg_type(&self) -> Message {
+ let data = self.buffer.as_ref();
+ Message::from(data[field::TYPE])
+ }
+
+ /// Return the message code field.
+ #[inline]
+ pub fn msg_code(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::CODE]
+ }
+
+ /// Return the checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Return the identifier field (for echo request and reply packets).
+ #[inline]
+ pub fn echo_ident(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::ECHO_IDENT])
+ }
+
+ /// Return the sequence number field (for echo request and reply packets).
+ #[inline]
+ pub fn echo_seq_no(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
+ }
+
+ /// Return the MTU field (for packet too big messages).
+ #[inline]
+ pub fn pkt_too_big_mtu(&self) -> u32 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u32(&data[field::MTU])
+ }
+
+ /// Return the pointer field (for parameter problem messages).
+ #[inline]
+ pub fn param_problem_ptr(&self) -> u32 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u32(&data[field::POINTER])
+ }
+
+ /// Return the header length. The result depends on the value of
+ /// the message type field.
+ pub fn header_len(&self) -> usize {
+ match self.msg_type() {
+ Message::DstUnreachable => field::UNUSED.end,
+ Message::PktTooBig => field::MTU.end,
+ Message::TimeExceeded => field::UNUSED.end,
+ Message::ParamProblem => field::POINTER.end,
+ Message::EchoRequest => field::ECHO_SEQNO.end,
+ Message::EchoReply => field::ECHO_SEQNO.end,
+ Message::RouterSolicit => field::UNUSED.end,
+ Message::RouterAdvert => field::RETRANS_TM.end,
+ Message::NeighborSolicit => field::TARGET_ADDR.end,
+ Message::NeighborAdvert => field::TARGET_ADDR.end,
+ Message::Redirect => field::DEST_ADDR.end,
+ Message::MldQuery => field::QUERY_NUM_SRCS.end,
+ Message::MldReport => field::NR_MCAST_RCRDS.end,
+ // For packets that are not included in RFC 4443, do not
+ // include the last 32 bits of the ICMPv6 header in
+ // `header_bytes`. This must be done so that these bytes
+ // can be accessed in the `payload`.
+ _ => field::CHECKSUM.end,
+ }
+ }
+
+ /// Validate the header checksum.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
+ checksum::data(data),
+ ]) == !0
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the type-specific data.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ &data[self.header_len()..]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the message type field.
+ #[inline]
+ pub fn set_msg_type(&mut self, value: Message) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into()
+ }
+
+ /// Set the message code field.
+ #[inline]
+ pub fn set_msg_code(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::CODE] = value
+ }
+
+ /// Clear any reserved fields in the message header.
+ ///
+ /// # Panics
+ /// This function panics if the message type has not been set.
+ /// See [set_msg_type].
+ ///
+ /// [set_msg_type]: #method.set_msg_type
+ #[inline]
+ pub fn clear_reserved(&mut self) {
+ match self.msg_type() {
+ Message::RouterSolicit
+ | Message::NeighborSolicit
+ | Message::NeighborAdvert
+ | Message::Redirect => {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::UNUSED], 0);
+ }
+ Message::MldQuery => {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::QUERY_RESV], 0);
+ data[field::SQRV] &= 0xf;
+ }
+ Message::MldReport => {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::RECORD_RESV], 0);
+ }
+ ty => panic!("Message type `{ty}` does not have any reserved fields."),
+ }
+ }
+
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Set the identifier field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn set_echo_ident(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
+ }
+
+ /// Set the sequence number field (for echo request and reply packets).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an echo request or reply packet.
+ #[inline]
+ pub fn set_echo_seq_no(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
+ }
+
+ /// Set the MTU field (for packet too big messages).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not an packet too big packet.
+ #[inline]
+ pub fn set_pkt_too_big_mtu(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::MTU], value)
+ }
+
+ /// Set the pointer field (for parameter problem messages).
+ ///
+ /// # Panics
+ /// This function may panic if this packet is not a parameter problem message.
+ #[inline]
+ pub fn set_param_problem_ptr(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::POINTER], value)
+ }
+
+ /// Compute and fill in the header checksum.
+ pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
+ checksum::data(data),
+ ])
+ };
+ self.set_checksum(checksum)
+ }
+
+ /// Return a mutable pointer to the type-specific data.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let range = self.header_len()..;
+ let data = self.buffer.as_mut();
+ &mut data[range]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A high-level representation of an Internet Control Message Protocol version 6 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Repr<'a> {
+ DstUnreachable {
+ reason: DstUnreachable,
+ header: Ipv6Repr,
+ data: &'a [u8],
+ },
+ PktTooBig {
+ mtu: u32,
+ header: Ipv6Repr,
+ data: &'a [u8],
+ },
+ TimeExceeded {
+ reason: TimeExceeded,
+ header: Ipv6Repr,
+ data: &'a [u8],
+ },
+ ParamProblem {
+ reason: ParamProblem,
+ pointer: u32,
+ header: Ipv6Repr,
+ data: &'a [u8],
+ },
+ EchoRequest {
+ ident: u16,
+ seq_no: u16,
+ data: &'a [u8],
+ },
+ EchoReply {
+ ident: u16,
+ seq_no: u16,
+ data: &'a [u8],
+ },
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ Ndisc(NdiscRepr<'a>),
+ Mld(MldRepr<'a>),
+ #[cfg(feature = "proto-rpl")]
+ Rpl(RplRepr<'a>),
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an Internet Control Message Protocol version 6 packet and return
+ /// a high-level representation.
+ pub fn parse<T>(
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ packet: &Packet<&'a T>,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>) -> Result<(&'a [u8], Ipv6Repr)>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ // The packet must be truncated to fit the min MTU. Since we don't know the offset of
+ // the ICMPv6 header in the L2 frame, we should only check whether the payload's IPv6
+ // header is present, the rest is allowed to be truncated.
+ let ip_packet = if packet.payload().len() >= IPV6_HEADER_LEN {
+ Ipv6Packet::new_unchecked(packet.payload())
+ } else {
+ return Err(Error);
+ };
+
+ let payload = &packet.payload()[ip_packet.header_len()..];
+ let repr = Ipv6Repr {
+ src_addr: ip_packet.src_addr(),
+ dst_addr: ip_packet.dst_addr(),
+ next_header: ip_packet.next_header(),
+ payload_len: ip_packet.payload_len().into(),
+ hop_limit: ip_packet.hop_limit(),
+ };
+ Ok((payload, repr))
+ }
+ // Valid checksum is expected.
+ if checksum_caps.icmpv6.rx() && !packet.verify_checksum(src_addr, dst_addr) {
+ return Err(Error);
+ }
+
+ match (packet.msg_type(), packet.msg_code()) {
+ (Message::DstUnreachable, code) => {
+ let (payload, repr) = create_packet_from_payload(packet)?;
+ Ok(Repr::DstUnreachable {
+ reason: DstUnreachable::from(code),
+ header: repr,
+ data: payload,
+ })
+ }
+ (Message::PktTooBig, 0) => {
+ let (payload, repr) = create_packet_from_payload(packet)?;
+ Ok(Repr::PktTooBig {
+ mtu: packet.pkt_too_big_mtu(),
+ header: repr,
+ data: payload,
+ })
+ }
+ (Message::TimeExceeded, code) => {
+ let (payload, repr) = create_packet_from_payload(packet)?;
+ Ok(Repr::TimeExceeded {
+ reason: TimeExceeded::from(code),
+ header: repr,
+ data: payload,
+ })
+ }
+ (Message::ParamProblem, code) => {
+ let (payload, repr) = create_packet_from_payload(packet)?;
+ Ok(Repr::ParamProblem {
+ reason: ParamProblem::from(code),
+ pointer: packet.param_problem_ptr(),
+ header: repr,
+ data: payload,
+ })
+ }
+ (Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
+ ident: packet.echo_ident(),
+ seq_no: packet.echo_seq_no(),
+ data: packet.payload(),
+ }),
+ (Message::EchoReply, 0) => Ok(Repr::EchoReply {
+ ident: packet.echo_ident(),
+ seq_no: packet.echo_seq_no(),
+ data: packet.payload(),
+ }),
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ (msg_type, 0) if msg_type.is_ndisc() => NdiscRepr::parse(packet).map(Repr::Ndisc),
+ (msg_type, 0) if msg_type.is_mld() => MldRepr::parse(packet).map(Repr::Mld),
+ #[cfg(feature = "proto-rpl")]
+ (Message::RplControl, _) => RplRepr::parse(packet).map(Repr::Rpl),
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub fn buffer_len(&self) -> usize {
+ match self {
+ &Repr::DstUnreachable { header, data, .. }
+ | &Repr::PktTooBig { header, data, .. }
+ | &Repr::TimeExceeded { header, data, .. }
+ | &Repr::ParamProblem { header, data, .. } => cmp::min(
+ field::UNUSED.end + header.buffer_len() + data.len(),
+ MAX_ERROR_PACKET_LEN,
+ ),
+ &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
+ field::ECHO_SEQNO.end + data.len()
+ }
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ &Repr::Ndisc(ndisc) => ndisc.buffer_len(),
+ &Repr::Mld(mld) => mld.buffer_len(),
+ #[cfg(feature = "proto-rpl")]
+ Repr::Rpl(rpl) => rpl.buffer_len(),
+ }
+ }
+
+ /// Emit a high-level representation into an Internet Control Message Protocol version 6
+ /// packet.
+ pub fn emit<T>(
+ &self,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ packet: &mut Packet<&mut T>,
+ checksum_caps: &ChecksumCapabilities,
+ ) where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ let icmp_header_len = packet.header_len();
+ let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
+ header.emit(&mut ip_packet);
+ let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
+ // FIXME: this should rather be checked at link level, as we can't know in advance how
+ // much space we have for the packet due to IPv6 options and etc
+ let payload_len = cmp::min(
+ data.len(),
+ MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
+ );
+ payload[..payload_len].copy_from_slice(&data[..payload_len]);
+ }
+
+ match *self {
+ Repr::DstUnreachable {
+ reason,
+ header,
+ data,
+ } => {
+ packet.set_msg_type(Message::DstUnreachable);
+ packet.set_msg_code(reason.into());
+
+ emit_contained_packet(packet, header, data);
+ }
+
+ Repr::PktTooBig { mtu, header, data } => {
+ packet.set_msg_type(Message::PktTooBig);
+ packet.set_msg_code(0);
+ packet.set_pkt_too_big_mtu(mtu);
+
+ emit_contained_packet(packet, header, data);
+ }
+
+ Repr::TimeExceeded {
+ reason,
+ header,
+ data,
+ } => {
+ packet.set_msg_type(Message::TimeExceeded);
+ packet.set_msg_code(reason.into());
+
+ emit_contained_packet(packet, header, data);
+ }
+
+ Repr::ParamProblem {
+ reason,
+ pointer,
+ header,
+ data,
+ } => {
+ packet.set_msg_type(Message::ParamProblem);
+ packet.set_msg_code(reason.into());
+ packet.set_param_problem_ptr(pointer);
+
+ emit_contained_packet(packet, header, data);
+ }
+
+ Repr::EchoRequest {
+ ident,
+ seq_no,
+ data,
+ } => {
+ packet.set_msg_type(Message::EchoRequest);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(ident);
+ packet.set_echo_seq_no(seq_no);
+ let data_len = cmp::min(packet.payload_mut().len(), data.len());
+ packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
+ }
+
+ Repr::EchoReply {
+ ident,
+ seq_no,
+ data,
+ } => {
+ packet.set_msg_type(Message::EchoReply);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(ident);
+ packet.set_echo_seq_no(seq_no);
+ let data_len = cmp::min(packet.payload_mut().len(), data.len());
+ packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
+ }
+
+ #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+ Repr::Ndisc(ndisc) => ndisc.emit(packet),
+
+ Repr::Mld(mld) => mld.emit(packet),
+
+ #[cfg(feature = "proto-rpl")]
+ Repr::Rpl(ref rpl) => rpl.emit(packet),
+ }
+
+ if checksum_caps.icmpv6.tx() {
+ packet.fill_checksum(src_addr, dst_addr);
+ } else {
+ // make sure we get a consistently zeroed checksum, since implementations might rely on it
+ packet.set_checksum(0);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
+ use crate::wire::{IpProtocol, Ipv6Address, Ipv6Repr};
+
+ static ECHO_PACKET_BYTES: [u8; 12] = [
+ 0x80, 0x00, 0x19, 0xb3, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ static ECHO_PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+
+ static PKT_TOO_BIG_BYTES: [u8; 60] = [
+ 0x02, 0x00, 0x0f, 0xc9, 0x00, 0x00, 0x05, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11,
+ 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] = [
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00,
+ 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] = [
+ 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ fn echo_packet_repr() -> Repr<'static> {
+ Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &ECHO_PACKET_PAYLOAD,
+ }
+ }
+
+ fn too_big_packet_repr() -> Repr<'static> {
+ Repr::PktTooBig {
+ mtu: 1500,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01,
+ ]),
+ dst_addr: Ipv6Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02,
+ ]),
+ next_header: IpProtocol::Udp,
+ payload_len: 12,
+ hop_limit: 0x40,
+ },
+ data: &PKT_TOO_BIG_UDP_PAYLOAD,
+ }
+ }
+
+ #[test]
+ fn test_echo_deconstruct() {
+ let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::EchoRequest);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.checksum(), 0x19b3);
+ assert_eq!(packet.echo_ident(), 0x1234);
+ assert_eq!(packet.echo_seq_no(), 0xabcd);
+ assert_eq!(packet.payload(), &ECHO_PACKET_PAYLOAD[..]);
+ assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
+ assert!(!packet.msg_type().is_error());
+ }
+
+ #[test]
+ fn test_echo_construct() {
+ let mut bytes = vec![0xa5; 12];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::EchoRequest);
+ packet.set_msg_code(0);
+ packet.set_echo_ident(0x1234);
+ packet.set_echo_seq_no(0xabcd);
+ packet
+ .payload_mut()
+ .copy_from_slice(&ECHO_PACKET_PAYLOAD[..]);
+ packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
+ assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_echo_repr_parse() {
+ let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
+ let repr = Repr::parse(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &packet,
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+ assert_eq!(repr, echo_packet_repr());
+ }
+
+ #[test]
+ fn test_echo_emit() {
+ let repr = echo_packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_too_big_deconstruct() {
+ let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::PktTooBig);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.checksum(), 0x0fc9);
+ assert_eq!(packet.pkt_too_big_mtu(), 1500);
+ assert_eq!(packet.payload(), &PKT_TOO_BIG_IP_PAYLOAD[..]);
+ assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
+ assert!(packet.msg_type().is_error());
+ }
+
+ #[test]
+ fn test_too_big_construct() {
+ let mut bytes = vec![0xa5; 60];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::PktTooBig);
+ packet.set_msg_code(0);
+ packet.set_pkt_too_big_mtu(1500);
+ packet
+ .payload_mut()
+ .copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]);
+ packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
+ assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
+ }
+
+ #[test]
+ fn test_too_big_repr_parse() {
+ let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
+ let repr = Repr::parse(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &packet,
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+ assert_eq!(repr, too_big_packet_repr());
+ }
+
+ #[test]
+ fn test_too_big_emit() {
+ let repr = too_big_packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
+ }
+
+ #[test]
+ fn test_buffer_length_is_truncated_to_mtu() {
+ let repr = Repr::PktTooBig {
+ mtu: 1280,
+ header: Ipv6Repr {
+ src_addr: Default::default(),
+ dst_addr: Default::default(),
+ next_header: IpProtocol::Tcp,
+ hop_limit: 64,
+ payload_len: 1280,
+ },
+ data: &vec![0; 9999],
+ };
+ assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN);
+ }
+
+ #[test]
+ fn test_mtu_truncated_payload_roundtrip() {
+ let ip_packet_repr = Ipv6Repr {
+ src_addr: Default::default(),
+ dst_addr: Default::default(),
+ next_header: IpProtocol::Tcp,
+ hop_limit: 64,
+ payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN,
+ };
+ let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]);
+ ip_packet_repr.emit(&mut ip_packet);
+
+ let repr1 = Repr::PktTooBig {
+ mtu: IPV6_MIN_MTU as u32,
+ header: ip_packet_repr,
+ data: &ip_packet.as_ref()[IPV6_HEADER_LEN..],
+ };
+ // this is needed to make sure roundtrip gives the same value
+ // it is not needed for ensuring the correct bytes get emitted
+ let repr1 = Repr::PktTooBig {
+ mtu: IPV6_MIN_MTU as u32,
+ header: ip_packet_repr,
+ data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end],
+ };
+ let mut data = vec![0; MAX_ERROR_PACKET_LEN];
+ let mut packet = Packet::new_unchecked(&mut data);
+ repr1.emit(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+
+ let packet = Packet::new_unchecked(&data);
+ let repr2 = Repr::parse(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &packet,
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+
+ assert_eq!(repr1, repr2);
+ }
+
+ #[test]
+ fn test_truncated_payload_ipv6_header_parse_fails() {
+ let repr = too_big_packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ let packet = Packet::new_unchecked(&bytes[..field::HEADER_END + IPV6_HEADER_LEN - 1]);
+ assert!(Repr::parse(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &packet,
+ &ChecksumCapabilities::ignored(),
+ )
+ .is_err());
+ }
+}
diff --git a/src/wire/ieee802154.rs b/src/wire/ieee802154.rs
new file mode 100644
index 0000000..33fafb6
--- /dev/null
+++ b/src/wire/ieee802154.rs
@@ -0,0 +1,1182 @@
+use core::fmt;
+
+use byteorder::{ByteOrder, LittleEndian};
+
+use super::{Error, Result};
+use crate::wire::ipv6::Address as Ipv6Address;
+
+enum_with_unknown! {
+ /// IEEE 802.15.4 frame type.
+ pub enum FrameType(u8) {
+ Beacon = 0b000,
+ Data = 0b001,
+ Acknowledgement = 0b010,
+ MacCommand = 0b011,
+ Multipurpose = 0b101,
+ FragmentOrFrak = 0b110,
+ Extended = 0b111,
+ }
+}
+
+impl fmt::Display for FrameType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ FrameType::Beacon => write!(f, "Beacon"),
+ FrameType::Data => write!(f, "Data"),
+ FrameType::Acknowledgement => write!(f, "Ack"),
+ FrameType::MacCommand => write!(f, "MAC command"),
+ FrameType::Multipurpose => write!(f, "Multipurpose"),
+ FrameType::FragmentOrFrak => write!(f, "FragmentOrFrak"),
+ FrameType::Extended => write!(f, "Extended"),
+ FrameType::Unknown(id) => write!(f, "0b{id:04b}"),
+ }
+ }
+}
+enum_with_unknown! {
+ /// IEEE 802.15.4 addressing mode for destination and source addresses.
+ pub enum AddressingMode(u8) {
+ Absent = 0b00,
+ Short = 0b10,
+ Extended = 0b11,
+ }
+}
+
+impl AddressingMode {
+ /// Return the size in octets of the address.
+ const fn size(&self) -> usize {
+ match self {
+ AddressingMode::Absent => 0,
+ AddressingMode::Short => 2,
+ AddressingMode::Extended => 8,
+ AddressingMode::Unknown(_) => 0, // TODO(thvdveld): what do we need to here?
+ }
+ }
+}
+
+impl fmt::Display for AddressingMode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ AddressingMode::Absent => write!(f, "Absent"),
+ AddressingMode::Short => write!(f, "Short"),
+ AddressingMode::Extended => write!(f, "Extended"),
+ AddressingMode::Unknown(id) => write!(f, "0b{id:04b}"),
+ }
+ }
+}
+
+/// A IEEE 802.15.4 PAN.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub struct Pan(pub u16);
+
+impl Pan {
+ pub const BROADCAST: Self = Self(0xffff);
+
+ /// Return the PAN ID as bytes.
+ pub fn as_bytes(&self) -> [u8; 2] {
+ let mut pan = [0u8; 2];
+ LittleEndian::write_u16(&mut pan, self.0);
+ pan
+ }
+}
+
+impl fmt::Display for Pan {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:0x}", self.0)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Pan {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(fmt, "{:02x}", self.0)
+ }
+}
+
+/// A IEEE 802.15.4 address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub enum Address {
+ Absent,
+ Short([u8; 2]),
+ Extended([u8; 8]),
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Address {
+ fn format(&self, f: defmt::Formatter) {
+ match self {
+ Self::Absent => defmt::write!(f, "not-present"),
+ Self::Short(bytes) => defmt::write!(f, "{:02x}:{:02x}", bytes[0], bytes[1]),
+ Self::Extended(bytes) => defmt::write!(
+ f,
+ "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
+ bytes[0],
+ bytes[1],
+ bytes[2],
+ bytes[3],
+ bytes[4],
+ bytes[5],
+ bytes[6],
+ bytes[7]
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+impl Default for Address {
+ fn default() -> Self {
+ Address::Extended([0u8; 8])
+ }
+}
+
+impl Address {
+ /// The broadcast address.
+ pub const BROADCAST: Address = Address::Short([0xff; 2]);
+
+ /// Query whether the address is an unicast address.
+ pub fn is_unicast(&self) -> bool {
+ !self.is_broadcast()
+ }
+
+ /// Query whether this address is the broadcast address.
+ pub fn is_broadcast(&self) -> bool {
+ *self == Self::BROADCAST
+ }
+
+ const fn short_from_bytes(a: [u8; 2]) -> Self {
+ Self::Short(a)
+ }
+
+ const fn extended_from_bytes(a: [u8; 8]) -> Self {
+ Self::Extended(a)
+ }
+
+ pub fn from_bytes(a: &[u8]) -> Self {
+ if a.len() == 2 {
+ let mut b = [0u8; 2];
+ b.copy_from_slice(a);
+ Address::Short(b)
+ } else if a.len() == 8 {
+ let mut b = [0u8; 8];
+ b.copy_from_slice(a);
+ Address::Extended(b)
+ } else {
+ panic!("Not an IEEE802.15.4 address");
+ }
+ }
+
+ pub const fn as_bytes(&self) -> &[u8] {
+ match self {
+ Address::Absent => &[],
+ Address::Short(value) => value,
+ Address::Extended(value) => value,
+ }
+ }
+
+ /// Convert the extended address to an Extended Unique Identifier (EUI-64)
+ pub fn as_eui_64(&self) -> Option<[u8; 8]> {
+ match self {
+ Address::Absent | Address::Short(_) => None,
+ Address::Extended(value) => {
+ let mut bytes = [0; 8];
+ bytes.copy_from_slice(&value[..]);
+
+ bytes[0] ^= 1 << 1;
+
+ Some(bytes)
+ }
+ }
+ }
+
+ /// Convert an extended address to a link-local IPv6 address using the EUI-64 format from
+ /// RFC2464.
+ pub fn as_link_local_address(&self) -> Option<Ipv6Address> {
+ let mut bytes = [0; 16];
+ bytes[0] = 0xfe;
+ bytes[1] = 0x80;
+ bytes[8..].copy_from_slice(&self.as_eui_64()?);
+
+ Some(Ipv6Address::from_bytes(&bytes))
+ }
+}
+
+impl fmt::Display for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Absent => write!(f, "not-present"),
+ Self::Short(bytes) => write!(f, "{:02x}:{:02x}", bytes[0], bytes[1]),
+ Self::Extended(bytes) => write!(
+ f,
+ "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]
+ ),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// IEEE 802.15.4 addressing mode for destination and source addresses.
+ pub enum FrameVersion(u8) {
+ Ieee802154_2003 = 0b00,
+ Ieee802154_2006 = 0b01,
+ Ieee802154 = 0b10,
+ }
+}
+
+/// A read/write wrapper around an IEEE 802.15.4 frame buffer.
+#[derive(Debug, Clone)]
+pub struct Frame<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const FRAMECONTROL: Field = 0..2;
+ pub const SEQUENCE_NUMBER: usize = 2;
+ pub const ADDRESSING: Rest = 3..;
+}
+
+macro_rules! fc_bit_field {
+ ($field:ident, $bit:literal) => {
+ #[inline]
+ pub fn $field(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]);
+
+ ((raw >> $bit) & 0b1) == 0b1
+ }
+ };
+}
+
+macro_rules! set_fc_bit_field {
+ ($field:ident, $bit:literal) => {
+ #[inline]
+ pub fn $field(&mut self, val: bool) {
+ let data = &mut self.buffer.as_mut()[field::FRAMECONTROL];
+ let mut raw = LittleEndian::read_u16(data);
+ raw |= ((val as u16) << $bit);
+
+ data.copy_from_slice(&raw.to_le_bytes());
+ }
+ };
+}
+
+impl<T: AsRef<[u8]>> Frame<T> {
+ /// Input a raw octet buffer with Ethernet frame structure.
+ pub const fn new_unchecked(buffer: T) -> Frame<T> {
+ Frame { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Frame<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+
+ // We don't handle unknown frame versions.
+ if matches!(packet.frame_version(), FrameVersion::Unknown(_)) {
+ return Err(Error);
+ }
+
+ // We don't handle unknown addressing modes.
+ if matches!(packet.dst_addressing_mode(), AddressingMode::Unknown(_))
+ || matches!(packet.src_addressing_mode(), AddressingMode::Unknown(_))
+ {
+ return Err(Error);
+ }
+
+ // We don't handle absent addressing mode with PAN ID compression for older frame versions.
+ if matches!(
+ packet.frame_version(),
+ FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006
+ ) && packet.pan_id_compression()
+ && matches!(packet.dst_addressing_mode(), AddressingMode::Absent)
+ && matches!(packet.src_addressing_mode(), AddressingMode::Absent)
+ {
+ return Err(Error);
+ }
+
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ // We need at least 3 bytes
+ if self.buffer.as_ref().len() < 3 {
+ return Err(Error);
+ }
+
+ // We don't handle frames with a payload larger than 127 bytes.
+ if self.buffer.as_ref().len() > 127 {
+ return Err(Error);
+ }
+
+ let mut offset = field::ADDRESSING.start
+ + if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags()
+ {
+ let mut offset = if dst_pan_id { 2 } else { 0 };
+ offset += dst_addr.size();
+ offset += if src_pan_id { 2 } else { 0 };
+ offset += src_addr.size();
+
+ if offset > self.buffer.as_ref().len() {
+ return Err(Error);
+ }
+ offset
+ } else {
+ 0
+ };
+
+ if self.security_enabled() {
+ // First check that we can access the security header control bits.
+ if offset + 1 > self.buffer.as_ref().len() {
+ return Err(Error);
+ }
+
+ offset += self.security_header_len();
+ }
+
+ if offset > self.buffer.as_ref().len() {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the FrameType field.
+ #[inline]
+ pub fn frame_type(&self) -> FrameType {
+ let data = self.buffer.as_ref();
+ let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]);
+ let ft = (raw & 0b111) as u8;
+ FrameType::from(ft)
+ }
+
+ fc_bit_field!(security_enabled, 3);
+ fc_bit_field!(frame_pending, 4);
+ fc_bit_field!(ack_request, 5);
+ fc_bit_field!(pan_id_compression, 6);
+
+ fc_bit_field!(sequence_number_suppression, 8);
+ fc_bit_field!(ie_present, 9);
+
+ /// Return the destination addressing mode.
+ #[inline]
+ pub fn dst_addressing_mode(&self) -> AddressingMode {
+ let data = self.buffer.as_ref();
+ let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]);
+ let am = ((raw >> 10) & 0b11) as u8;
+ AddressingMode::from(am)
+ }
+
+ /// Return the frame version.
+ #[inline]
+ pub fn frame_version(&self) -> FrameVersion {
+ let data = self.buffer.as_ref();
+ let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]);
+ let fv = ((raw >> 12) & 0b11) as u8;
+ FrameVersion::from(fv)
+ }
+
+ /// Return the source addressing mode.
+ #[inline]
+ pub fn src_addressing_mode(&self) -> AddressingMode {
+ let data = self.buffer.as_ref();
+ let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]);
+ let am = ((raw >> 14) & 0b11) as u8;
+ AddressingMode::from(am)
+ }
+
+ /// Return the sequence number of the frame.
+ #[inline]
+ pub fn sequence_number(&self) -> Option<u8> {
+ match self.frame_type() {
+ FrameType::Beacon
+ | FrameType::Data
+ | FrameType::Acknowledgement
+ | FrameType::MacCommand
+ | FrameType::Multipurpose => {
+ let data = self.buffer.as_ref();
+ let raw = data[field::SEQUENCE_NUMBER];
+ Some(raw)
+ }
+ FrameType::Extended | FrameType::FragmentOrFrak | FrameType::Unknown(_) => None,
+ }
+ }
+
+ /// Return the addressing fields.
+ #[inline]
+ fn addressing_fields(&self) -> Option<&[u8]> {
+ match self.frame_type() {
+ FrameType::Beacon
+ | FrameType::Data
+ | FrameType::MacCommand
+ | FrameType::Multipurpose => (),
+ FrameType::Acknowledgement if self.frame_version() == FrameVersion::Ieee802154 => (),
+ FrameType::Acknowledgement
+ | FrameType::Extended
+ | FrameType::FragmentOrFrak
+ | FrameType::Unknown(_) => return None,
+ }
+
+ if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags() {
+ let mut offset = if dst_pan_id { 2 } else { 0 };
+ offset += dst_addr.size();
+ offset += if src_pan_id { 2 } else { 0 };
+ offset += src_addr.size();
+
+ let data = self.buffer.as_ref();
+ Some(&data[field::ADDRESSING][..offset])
+ } else {
+ None
+ }
+ }
+
+ fn addr_present_flags(&self) -> Option<(bool, AddressingMode, bool, AddressingMode)> {
+ let dst_addr_mode = self.dst_addressing_mode();
+ let src_addr_mode = self.src_addressing_mode();
+ let pan_id_compression = self.pan_id_compression();
+
+ use AddressingMode::*;
+ match self.frame_version() {
+ FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006 => {
+ match (dst_addr_mode, src_addr_mode) {
+ (Absent, src) => Some((false, Absent, true, src)),
+ (dst, Absent) => Some((true, dst, false, Absent)),
+
+ (dst, src) if pan_id_compression => Some((true, dst, false, src)),
+ (dst, src) if !pan_id_compression => Some((true, dst, true, src)),
+ _ => None,
+ }
+ }
+ FrameVersion::Ieee802154 => {
+ Some(match (dst_addr_mode, src_addr_mode, pan_id_compression) {
+ (Absent, Absent, false) => (false, Absent, false, Absent),
+ (Absent, Absent, true) => (true, Absent, false, Absent),
+ (dst, Absent, false) if !matches!(dst, Absent) => (true, dst, false, Absent),
+ (dst, Absent, true) if !matches!(dst, Absent) => (false, dst, false, Absent),
+ (Absent, src, false) if !matches!(src, Absent) => (false, Absent, true, src),
+ (Absent, src, true) if !matches!(src, Absent) => (false, Absent, true, src),
+ (Extended, Extended, false) => (true, Extended, false, Extended),
+ (Extended, Extended, true) => (false, Extended, false, Extended),
+ (Short, Short, false) => (true, Short, true, Short),
+ (Short, Extended, false) => (true, Short, true, Extended),
+ (Extended, Short, false) => (true, Extended, true, Short),
+ (Short, Extended, true) => (true, Short, false, Extended),
+ (Extended, Short, true) => (true, Extended, false, Short),
+ (Short, Short, true) => (true, Short, false, Short),
+ _ => return None,
+ })
+ }
+ _ => None,
+ }
+ }
+
+ /// Return the destination PAN field.
+ #[inline]
+ pub fn dst_pan_id(&self) -> Option<Pan> {
+ if let Some((true, _, _, _)) = self.addr_present_flags() {
+ let addressing_fields = self.addressing_fields()?;
+ Some(Pan(LittleEndian::read_u16(&addressing_fields[..2])))
+ } else {
+ None
+ }
+ }
+
+ /// Return the destination address field.
+ #[inline]
+ pub fn dst_addr(&self) -> Option<Address> {
+ if let Some((dst_pan_id, dst_addr, _, _)) = self.addr_present_flags() {
+ let addressing_fields = self.addressing_fields()?;
+ let offset = if dst_pan_id { 2 } else { 0 };
+
+ match dst_addr {
+ AddressingMode::Absent => Some(Address::Absent),
+ AddressingMode::Short => {
+ let mut raw = [0u8; 2];
+ raw.clone_from_slice(&addressing_fields[offset..offset + 2]);
+ raw.reverse();
+ Some(Address::short_from_bytes(raw))
+ }
+ AddressingMode::Extended => {
+ let mut raw = [0u8; 8];
+ raw.clone_from_slice(&addressing_fields[offset..offset + 8]);
+ raw.reverse();
+ Some(Address::extended_from_bytes(raw))
+ }
+ AddressingMode::Unknown(_) => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ /// Return the destination PAN field.
+ #[inline]
+ pub fn src_pan_id(&self) -> Option<Pan> {
+ if let Some((dst_pan_id, dst_addr, true, _)) = self.addr_present_flags() {
+ let mut offset = if dst_pan_id { 2 } else { 0 };
+ offset += dst_addr.size();
+ let addressing_fields = self.addressing_fields()?;
+ Some(Pan(LittleEndian::read_u16(
+ &addressing_fields[offset..][..2],
+ )))
+ } else {
+ None
+ }
+ }
+
+ /// Return the source address field.
+ #[inline]
+ pub fn src_addr(&self) -> Option<Address> {
+ if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags() {
+ let addressing_fields = self.addressing_fields()?;
+ let mut offset = if dst_pan_id { 2 } else { 0 };
+ offset += dst_addr.size();
+ offset += if src_pan_id { 2 } else { 0 };
+
+ match src_addr {
+ AddressingMode::Absent => Some(Address::Absent),
+ AddressingMode::Short => {
+ let mut raw = [0u8; 2];
+ raw.clone_from_slice(&addressing_fields[offset..offset + 2]);
+ raw.reverse();
+ Some(Address::short_from_bytes(raw))
+ }
+ AddressingMode::Extended => {
+ let mut raw = [0u8; 8];
+ raw.clone_from_slice(&addressing_fields[offset..offset + 8]);
+ raw.reverse();
+ Some(Address::extended_from_bytes(raw))
+ }
+ AddressingMode::Unknown(_) => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ /// Return the index where the auxiliary security header starts.
+ fn aux_security_header_start(&self) -> usize {
+ // We start with 3, because 2 bytes for frame control and the sequence number.
+ let mut index = 3;
+ index += if let Some(addrs) = self.addressing_fields() {
+ addrs.len()
+ } else {
+ 0
+ };
+ index
+ }
+
+ /// Return the size of the security header.
+ fn security_header_len(&self) -> usize {
+ let mut size = 1;
+ size += if self.frame_counter_suppressed() {
+ 0
+ } else {
+ 4
+ };
+ size += if let Some(len) = self.key_identifier_length() {
+ len as usize
+ } else {
+ 0
+ };
+ size
+ }
+
+ /// Return the index where the payload starts.
+ fn payload_start(&self) -> usize {
+ let mut index = self.aux_security_header_start();
+
+ if self.security_enabled() {
+ index += self.security_header_len();
+ }
+
+ index
+ }
+
+ /// Return the length of the key identifier field.
+ fn key_identifier_length(&self) -> Option<u8> {
+ Some(match self.key_identifier_mode() {
+ 0 => 0,
+ 1 => 1,
+ 2 => 5,
+ 3 => 9,
+ _ => return None,
+ })
+ }
+
+ /// Return the security level of the auxiliary security header.
+ pub fn security_level(&self) -> u8 {
+ let index = self.aux_security_header_start();
+ let b = self.buffer.as_ref()[index..][0];
+ b & 0b111
+ }
+
+ /// Return the key identifier mode used by the auxiliary security header.
+ pub fn key_identifier_mode(&self) -> u8 {
+ let index = self.aux_security_header_start();
+ let b = self.buffer.as_ref()[index..][0];
+ (b >> 3) & 0b11
+ }
+
+ /// Return `true` when the frame counter in the security header is suppressed.
+ pub fn frame_counter_suppressed(&self) -> bool {
+ let index = self.aux_security_header_start();
+ let b = self.buffer.as_ref()[index..][0];
+ ((b >> 5) & 0b1) == 0b1
+ }
+
+ /// Return the frame counter field.
+ pub fn frame_counter(&self) -> Option<u32> {
+ if self.frame_counter_suppressed() {
+ None
+ } else {
+ let index = self.aux_security_header_start();
+ let b = &self.buffer.as_ref()[index..];
+ Some(LittleEndian::read_u32(&b[1..1 + 4]))
+ }
+ }
+
+ /// Return the Key Identifier field.
+ fn key_identifier(&self) -> &[u8] {
+ let index = self.aux_security_header_start();
+ let b = &self.buffer.as_ref()[index..];
+ let length = if let Some(len) = self.key_identifier_length() {
+ len as usize
+ } else {
+ 0
+ };
+ &b[5..][..length]
+ }
+
+ /// Return the Key Source field.
+ pub fn key_source(&self) -> Option<&[u8]> {
+ let ki = self.key_identifier();
+ let len = ki.len();
+ if len > 1 {
+ Some(&ki[..len - 1])
+ } else {
+ None
+ }
+ }
+
+ /// Return the Key Index field.
+ pub fn key_index(&self) -> Option<u8> {
+ let ki = self.key_identifier();
+ let len = ki.len();
+
+ if len > 0 {
+ Some(ki[len - 1])
+ } else {
+ None
+ }
+ }
+
+ /// Return the Message Integrity Code (MIC).
+ pub fn message_integrity_code(&self) -> Option<&[u8]> {
+ let mic_len = match self.security_level() {
+ 0 | 4 => return None,
+ 1 | 5 => 4,
+ 2 | 6 => 8,
+ 3 | 7 => 16,
+ _ => panic!(),
+ };
+
+ let data = &self.buffer.as_ref();
+ let len = data.len();
+
+ Some(&data[len - mic_len..])
+ }
+
+ /// Return the MAC header.
+ pub fn mac_header(&self) -> &[u8] {
+ let data = &self.buffer.as_ref();
+ &data[..self.payload_start()]
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Frame<&'a T> {
+ /// Return a pointer to the payload.
+ #[inline]
+ pub fn payload(&self) -> Option<&'a [u8]> {
+ match self.frame_type() {
+ FrameType::Data => {
+ let index = self.payload_start();
+ let data = &self.buffer.as_ref();
+
+ Some(&data[index..])
+ }
+ _ => None,
+ }
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Frame<T> {
+ /// Set the frame type.
+ #[inline]
+ pub fn set_frame_type(&mut self, frame_type: FrameType) {
+ let data = &mut self.buffer.as_mut()[field::FRAMECONTROL];
+ let mut raw = LittleEndian::read_u16(data);
+
+ raw = (raw & !(0b111)) | (u8::from(frame_type) as u16 & 0b111);
+ data.copy_from_slice(&raw.to_le_bytes());
+ }
+
+ set_fc_bit_field!(set_security_enabled, 3);
+ set_fc_bit_field!(set_frame_pending, 4);
+ set_fc_bit_field!(set_ack_request, 5);
+ set_fc_bit_field!(set_pan_id_compression, 6);
+
+ /// Set the frame version.
+ #[inline]
+ pub fn set_frame_version(&mut self, version: FrameVersion) {
+ let data = &mut self.buffer.as_mut()[field::FRAMECONTROL];
+ let mut raw = LittleEndian::read_u16(data);
+
+ raw = (raw & !(0b11 << 12)) | ((u8::from(version) as u16 & 0b11) << 12);
+ data.copy_from_slice(&raw.to_le_bytes());
+ }
+
+ /// Set the frame sequence number.
+ #[inline]
+ pub fn set_sequence_number(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::SEQUENCE_NUMBER] = value;
+ }
+
+ /// Set the destination PAN ID.
+ #[inline]
+ pub fn set_dst_pan_id(&mut self, value: Pan) {
+ // NOTE the destination addressing mode must be different than Absent.
+ // This is the reason why we set it to Extended.
+ self.set_dst_addressing_mode(AddressingMode::Extended);
+
+ let data = self.buffer.as_mut();
+ data[field::ADDRESSING][..2].copy_from_slice(&value.as_bytes());
+ }
+
+ /// Set the destination address.
+ #[inline]
+ pub fn set_dst_addr(&mut self, value: Address) {
+ match value {
+ Address::Absent => self.set_dst_addressing_mode(AddressingMode::Absent),
+ Address::Short(mut value) => {
+ value.reverse();
+ self.set_dst_addressing_mode(AddressingMode::Short);
+ let data = self.buffer.as_mut();
+ data[field::ADDRESSING][2..2 + 2].copy_from_slice(&value);
+ value.reverse();
+ }
+ Address::Extended(mut value) => {
+ value.reverse();
+ self.set_dst_addressing_mode(AddressingMode::Extended);
+ let data = &mut self.buffer.as_mut()[field::ADDRESSING];
+ data[2..2 + 8].copy_from_slice(&value);
+ value.reverse();
+ }
+ }
+ }
+
+ /// Set the destination addressing mode.
+ #[inline]
+ fn set_dst_addressing_mode(&mut self, value: AddressingMode) {
+ let data = &mut self.buffer.as_mut()[field::FRAMECONTROL];
+ let mut raw = LittleEndian::read_u16(data);
+
+ raw = (raw & !(0b11 << 10)) | ((u8::from(value) as u16 & 0b11) << 10);
+ data.copy_from_slice(&raw.to_le_bytes());
+ }
+
+ /// Set the source PAN ID.
+ #[inline]
+ pub fn set_src_pan_id(&mut self, value: Pan) {
+ let offset = match self.dst_addressing_mode() {
+ AddressingMode::Absent => 0,
+ AddressingMode::Short => 2,
+ AddressingMode::Extended => 8,
+ _ => unreachable!(),
+ } + 2;
+
+ let data = &mut self.buffer.as_mut()[field::ADDRESSING];
+ data[offset..offset + 2].copy_from_slice(&value.as_bytes());
+ }
+
+ /// Set the source address.
+ #[inline]
+ pub fn set_src_addr(&mut self, value: Address) {
+ let offset = match self.dst_addressing_mode() {
+ AddressingMode::Absent => 0,
+ AddressingMode::Short => 2,
+ AddressingMode::Extended => 8,
+ _ => unreachable!(),
+ } + 2;
+
+ let offset = offset + if self.pan_id_compression() { 0 } else { 2 };
+
+ match value {
+ Address::Absent => self.set_src_addressing_mode(AddressingMode::Absent),
+ Address::Short(mut value) => {
+ value.reverse();
+ self.set_src_addressing_mode(AddressingMode::Short);
+ let data = &mut self.buffer.as_mut()[field::ADDRESSING];
+ data[offset..offset + 2].copy_from_slice(&value);
+ value.reverse();
+ }
+ Address::Extended(mut value) => {
+ value.reverse();
+ self.set_src_addressing_mode(AddressingMode::Extended);
+ let data = &mut self.buffer.as_mut()[field::ADDRESSING];
+ data[offset..offset + 8].copy_from_slice(&value);
+ value.reverse();
+ }
+ }
+ }
+
+ /// Set the source addressing mode.
+ #[inline]
+ fn set_src_addressing_mode(&mut self, value: AddressingMode) {
+ let data = &mut self.buffer.as_mut()[field::FRAMECONTROL];
+ let mut raw = LittleEndian::read_u16(data);
+
+ raw = (raw & !(0b11 << 14)) | ((u8::from(value) as u16 & 0b11) << 14);
+ data.copy_from_slice(&raw.to_le_bytes());
+ }
+
+ /// Return a mutable pointer to the payload.
+ #[inline]
+ pub fn payload_mut(&mut self) -> Option<&mut [u8]> {
+ match self.frame_type() {
+ FrameType::Data => {
+ let index = self.payload_start();
+ let data = self.buffer.as_mut();
+ Some(&mut data[index..])
+ }
+ _ => None,
+ }
+ }
+}
+
+impl<T: AsRef<[u8]>> fmt::Display for Frame<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "IEEE802.15.4 frame type={}", self.frame_type())?;
+
+ if let Some(seq) = self.sequence_number() {
+ write!(f, " seq={:02x}", seq)?;
+ }
+
+ if let Some(pan) = self.dst_pan_id() {
+ write!(f, " dst-pan={}", pan)?;
+ }
+
+ if let Some(pan) = self.src_pan_id() {
+ write!(f, " src-pan={}", pan)?;
+ }
+
+ if let Some(addr) = self.dst_addr() {
+ write!(f, " dst={}", addr)?;
+ }
+
+ if let Some(addr) = self.src_addr() {
+ write!(f, " src={}", addr)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl<T: AsRef<[u8]>> defmt::Format for Frame<T> {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "IEEE802.15.4 frame type={}", self.frame_type());
+
+ if let Some(seq) = self.sequence_number() {
+ defmt::write!(f, " seq={:02x}", seq);
+ }
+
+ if let Some(pan) = self.dst_pan_id() {
+ defmt::write!(f, " dst-pan={}", pan);
+ }
+
+ if let Some(pan) = self.src_pan_id() {
+ defmt::write!(f, " src-pan={}", pan);
+ }
+
+ if let Some(addr) = self.dst_addr() {
+ defmt::write!(f, " dst={}", addr);
+ }
+
+ if let Some(addr) = self.src_addr() {
+ defmt::write!(f, " src={}", addr);
+ }
+ }
+}
+
+/// A high-level representation of an IEEE802.15.4 frame.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr {
+ pub frame_type: FrameType,
+ pub security_enabled: bool,
+ pub frame_pending: bool,
+ pub ack_request: bool,
+ pub sequence_number: Option<u8>,
+ pub pan_id_compression: bool,
+ pub frame_version: FrameVersion,
+ pub dst_pan_id: Option<Pan>,
+ pub dst_addr: Option<Address>,
+ pub src_pan_id: Option<Pan>,
+ pub src_addr: Option<Address>,
+}
+
+impl Repr {
+ /// Parse an IEEE 802.15.4 frame and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Frame<&T>) -> Result<Repr> {
+ // Ensure the basic accessors will work.
+ packet.check_len()?;
+
+ Ok(Repr {
+ frame_type: packet.frame_type(),
+ security_enabled: packet.security_enabled(),
+ frame_pending: packet.frame_pending(),
+ ack_request: packet.ack_request(),
+ sequence_number: packet.sequence_number(),
+ pan_id_compression: packet.pan_id_compression(),
+ frame_version: packet.frame_version(),
+ dst_pan_id: packet.dst_pan_id(),
+ dst_addr: packet.dst_addr(),
+ src_pan_id: packet.src_pan_id(),
+ src_addr: packet.src_addr(),
+ })
+ }
+
+ /// Return the length of a buffer required to hold a packet with the payload of a given length.
+ #[inline]
+ pub const fn buffer_len(&self) -> usize {
+ 3 + 2
+ + match self.dst_addr {
+ Some(Address::Absent) | None => 0,
+ Some(Address::Short(_)) => 2,
+ Some(Address::Extended(_)) => 8,
+ }
+ + if !self.pan_id_compression { 2 } else { 0 }
+ + match self.src_addr {
+ Some(Address::Absent) | None => 0,
+ Some(Address::Short(_)) => 2,
+ Some(Address::Extended(_)) => 8,
+ }
+ }
+
+ /// Emit a high-level representation into an IEEE802.15.4 frame.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, frame: &mut Frame<T>) {
+ frame.set_frame_type(self.frame_type);
+ frame.set_security_enabled(self.security_enabled);
+ frame.set_frame_pending(self.frame_pending);
+ frame.set_ack_request(self.ack_request);
+ frame.set_pan_id_compression(self.pan_id_compression);
+ frame.set_frame_version(self.frame_version);
+
+ if let Some(sequence_number) = self.sequence_number {
+ frame.set_sequence_number(sequence_number);
+ }
+
+ if let Some(dst_pan_id) = self.dst_pan_id {
+ frame.set_dst_pan_id(dst_pan_id);
+ }
+ if let Some(dst_addr) = self.dst_addr {
+ frame.set_dst_addr(dst_addr);
+ }
+
+ if !self.pan_id_compression && self.src_pan_id.is_some() {
+ frame.set_src_pan_id(self.src_pan_id.unwrap());
+ }
+
+ if let Some(src_addr) = self.src_addr {
+ frame.set_src_addr(src_addr);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_broadcast() {
+ assert!(Address::BROADCAST.is_broadcast());
+ assert!(!Address::BROADCAST.is_unicast());
+ }
+
+ #[test]
+ fn prepare_frame() {
+ let mut buffer = [0u8; 128];
+
+ let repr = Repr {
+ frame_type: FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: true,
+ pan_id_compression: true,
+ frame_version: FrameVersion::Ieee802154,
+ sequence_number: Some(1),
+ dst_pan_id: Some(Pan(0xabcd)),
+ dst_addr: Some(Address::BROADCAST),
+ src_pan_id: None,
+ src_addr: Some(Address::Extended([
+ 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00,
+ ])),
+ };
+
+ let buffer_len = repr.buffer_len();
+
+ let mut frame = Frame::new_unchecked(&mut buffer[..buffer_len]);
+ repr.emit(&mut frame);
+
+ println!("{frame:2x?}");
+
+ assert_eq!(frame.frame_type(), FrameType::Data);
+ assert!(!frame.security_enabled());
+ assert!(!frame.frame_pending());
+ assert!(frame.ack_request());
+ assert!(frame.pan_id_compression());
+ assert_eq!(frame.frame_version(), FrameVersion::Ieee802154);
+ assert_eq!(frame.sequence_number(), Some(1));
+ assert_eq!(frame.dst_pan_id(), Some(Pan(0xabcd)));
+ assert_eq!(frame.dst_addr(), Some(Address::BROADCAST));
+ assert_eq!(frame.src_pan_id(), None);
+ assert_eq!(
+ frame.src_addr(),
+ Some(Address::Extended([
+ 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00
+ ]))
+ );
+ }
+
+ macro_rules! vector_test {
+ ($name:ident $bytes:expr ; $($test_method:ident -> $expected:expr,)*) => {
+ #[test]
+ #[allow(clippy::bool_assert_comparison)]
+ fn $name() -> Result<()> {
+ let frame = &$bytes;
+ let frame = Frame::new_checked(frame)?;
+
+ $(
+ assert_eq!(frame.$test_method(), $expected, stringify!($test_method));
+ )*
+
+ Ok(())
+ }
+ }
+ }
+
+ vector_test! {
+ extended_addr
+ [
+ 0b0000_0001, 0b1100_1100, // frame control
+ 0b0, // seq
+ 0xcd, 0xab, // pan id
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // dst addr
+ 0x03, 0x04, // pan id
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, // src addr
+ ];
+ frame_type -> FrameType::Data,
+ dst_addr -> Some(Address::Extended([0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00])),
+ src_addr -> Some(Address::Extended([0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00])),
+ dst_pan_id -> Some(Pan(0xabcd)),
+ }
+
+ vector_test! {
+ short_addr
+ [
+ 0x01, 0x98, // frame control
+ 0x00, // sequence number
+ 0x34, 0x12, 0x78, 0x56, // PAN identifier and address of destination
+ 0x34, 0x12, 0xbc, 0x9a, // PAN identifier and address of source
+ ];
+ frame_type -> FrameType::Data,
+ security_enabled -> false,
+ frame_pending -> false,
+ ack_request -> false,
+ pan_id_compression -> false,
+ dst_addressing_mode -> AddressingMode::Short,
+ frame_version -> FrameVersion::Ieee802154_2006,
+ src_addressing_mode -> AddressingMode::Short,
+ dst_pan_id -> Some(Pan(0x1234)),
+ dst_addr -> Some(Address::Short([0x56, 0x78])),
+ src_pan_id -> Some(Pan(0x1234)),
+ src_addr -> Some(Address::Short([0x9a, 0xbc])),
+ }
+
+ vector_test! {
+ zolertia_remote
+ [
+ 0x41, 0xd8, // frame control
+ 0x01, // sequence number
+ 0xcd, 0xab, // Destination PAN id
+ 0xff, 0xff, // Short destination address
+ 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00, // Extended source address
+ 0x2b, 0x00, 0x00, 0x00, // payload
+ ];
+ frame_type -> FrameType::Data,
+ security_enabled -> false,
+ frame_pending -> false,
+ ack_request -> false,
+ pan_id_compression -> true,
+ dst_addressing_mode -> AddressingMode::Short,
+ frame_version -> FrameVersion::Ieee802154_2006,
+ src_addressing_mode -> AddressingMode::Extended,
+ payload -> Some(&[0x2b, 0x00, 0x00, 0x00][..]),
+ }
+
+ vector_test! {
+ security
+ [
+ 0x69,0xdc, // frame control
+ 0x32, // sequence number
+ 0xcd,0xab, // destination PAN id
+ 0xbf,0x9b,0x15,0x06,0x00,0x4b,0x12,0x00, // extended destination address
+ 0xc7,0xd9,0xb5,0x14,0x00,0x4b,0x12,0x00, // extended source address
+ 0x05, // security control field
+ 0x31,0x01,0x00,0x00, // frame counter
+ 0x3e,0xe8,0xfb,0x85,0xe4,0xcc,0xf4,0x48,0x90,0xfe,0x56,0x66,0xf7,0x1c,0x65,0x9e,0xf9, // data
+ 0x93,0xc8,0x34,0x2e,// MIC
+ ];
+ frame_type -> FrameType::Data,
+ security_enabled -> true,
+ frame_pending -> false,
+ ack_request -> true,
+ pan_id_compression -> true,
+ dst_addressing_mode -> AddressingMode::Extended,
+ frame_version -> FrameVersion::Ieee802154_2006,
+ src_addressing_mode -> AddressingMode::Extended,
+ dst_pan_id -> Some(Pan(0xabcd)),
+ dst_addr -> Some(Address::Extended([0x00,0x12,0x4b,0x00,0x06,0x15,0x9b,0xbf])),
+ src_pan_id -> None,
+ src_addr -> Some(Address::Extended([0x00,0x12,0x4b,0x00,0x14,0xb5,0xd9,0xc7])),
+ security_level -> 5,
+ key_identifier_mode -> 0,
+ frame_counter -> Some(305),
+ key_source -> None,
+ key_index -> None,
+ payload -> Some(&[0x3e,0xe8,0xfb,0x85,0xe4,0xcc,0xf4,0x48,0x90,0xfe,0x56,0x66,0xf7,0x1c,0x65,0x9e,0xf9,0x93,0xc8,0x34,0x2e][..]),
+ message_integrity_code -> Some(&[0x93, 0xC8, 0x34, 0x2E][..]),
+ mac_header -> &[
+ 0x69,0xdc, // frame control
+ 0x32, // sequence number
+ 0xcd,0xab, // destination PAN id
+ 0xbf,0x9b,0x15,0x06,0x00,0x4b,0x12,0x00, // extended destination address
+ 0xc7,0xd9,0xb5,0x14,0x00,0x4b,0x12,0x00, // extended source address
+ 0x05, // security control field
+ 0x31,0x01,0x00,0x00, // frame counter
+ ][..],
+ }
+}
diff --git a/src/wire/igmp.rs b/src/wire/igmp.rs
new file mode 100644
index 0000000..ac13ece
--- /dev/null
+++ b/src/wire/igmp.rs
@@ -0,0 +1,445 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+use crate::time::Duration;
+use crate::wire::ip::checksum;
+
+use crate::wire::Ipv4Address;
+
+enum_with_unknown! {
+ /// Internet Group Management Protocol v1/v2 message version/type.
+ pub enum Message(u8) {
+ /// Membership Query
+ MembershipQuery = 0x11,
+ /// Version 2 Membership Report
+ MembershipReportV2 = 0x16,
+ /// Leave Group
+ LeaveGroup = 0x17,
+ /// Version 1 Membership Report
+ MembershipReportV1 = 0x12
+ }
+}
+
+/// A read/write wrapper around an Internet Group Management Protocol v1/v2 packet buffer.
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const TYPE: usize = 0;
+ pub const MAX_RESP_CODE: usize = 1;
+ pub const CHECKSUM: Field = 2..4;
+ pub const GROUP_ADDRESS: Field = 4..8;
+}
+
+impl fmt::Display for Message {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Message::MembershipQuery => write!(f, "membership query"),
+ Message::MembershipReportV2 => write!(f, "version 2 membership report"),
+ Message::LeaveGroup => write!(f, "leave group"),
+ Message::MembershipReportV1 => write!(f, "version 1 membership report"),
+ Message::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+/// Internet Group Management Protocol v1/v2 defined in [RFC 2236].
+///
+/// [RFC 2236]: https://tools.ietf.org/html/rfc2236
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with IGMPv2 packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::GROUP_ADDRESS.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the message type field.
+ #[inline]
+ pub fn msg_type(&self) -> Message {
+ let data = self.buffer.as_ref();
+ Message::from(data[field::TYPE])
+ }
+
+ /// Return the maximum response time, using the encoding specified in
+ /// [RFC 3376]: 4.1.1. Max Resp Code.
+ ///
+ /// [RFC 3376]: https://tools.ietf.org/html/rfc3376
+ #[inline]
+ pub fn max_resp_code(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::MAX_RESP_CODE]
+ }
+
+ /// Return the checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Return the source address field.
+ #[inline]
+ pub fn group_addr(&self) -> Ipv4Address {
+ let data = self.buffer.as_ref();
+ Ipv4Address::from_bytes(&data[field::GROUP_ADDRESS])
+ }
+
+ /// Validate the header checksum.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::data(data) == !0
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the message type field.
+ #[inline]
+ pub fn set_msg_type(&mut self, value: Message) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into()
+ }
+
+ /// Set the maximum response time, using the encoding specified in
+ /// [RFC 3376]: 4.1.1. Max Resp Code.
+ #[inline]
+ pub fn set_max_resp_code(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::MAX_RESP_CODE] = value;
+ }
+
+ /// Set the checksum field.
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Set the group address field
+ #[inline]
+ pub fn set_group_address(&mut self, addr: Ipv4Address) {
+ let data = self.buffer.as_mut();
+ data[field::GROUP_ADDRESS].copy_from_slice(addr.as_bytes());
+ }
+
+ /// Compute and fill in the header checksum.
+ pub fn fill_checksum(&mut self) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::data(data)
+ };
+ self.set_checksum(checksum)
+ }
+}
+
+/// A high-level representation of an Internet Group Management Protocol v1/v2 header.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr {
+ MembershipQuery {
+ max_resp_time: Duration,
+ group_addr: Ipv4Address,
+ version: IgmpVersion,
+ },
+ MembershipReport {
+ group_addr: Ipv4Address,
+ version: IgmpVersion,
+ },
+ LeaveGroup {
+ group_addr: Ipv4Address,
+ },
+}
+
+/// Type of IGMP membership report version
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum IgmpVersion {
+ /// IGMPv1
+ Version1,
+ /// IGMPv2
+ Version2,
+}
+
+impl Repr {
+ /// Parse an Internet Group Management Protocol v1/v2 packet and return
+ /// a high-level representation.
+ pub fn parse<T>(packet: &Packet<&T>) -> Result<Repr>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ // Check if the address is 0.0.0.0 or multicast
+ let addr = packet.group_addr();
+ if !addr.is_unspecified() && !addr.is_multicast() {
+ return Err(Error);
+ }
+
+ // construct a packet based on the Type field
+ match packet.msg_type() {
+ Message::MembershipQuery => {
+ let max_resp_time = max_resp_code_to_duration(packet.max_resp_code());
+ // See RFC 3376: 7.1. Query Version Distinctions
+ let version = if packet.max_resp_code() == 0 {
+ IgmpVersion::Version1
+ } else {
+ IgmpVersion::Version2
+ };
+ Ok(Repr::MembershipQuery {
+ max_resp_time,
+ group_addr: addr,
+ version,
+ })
+ }
+ Message::MembershipReportV2 => Ok(Repr::MembershipReport {
+ group_addr: packet.group_addr(),
+ version: IgmpVersion::Version2,
+ }),
+ Message::LeaveGroup => Ok(Repr::LeaveGroup {
+ group_addr: packet.group_addr(),
+ }),
+ Message::MembershipReportV1 => {
+ // for backwards compatibility with IGMPv1
+ Ok(Repr::MembershipReport {
+ group_addr: packet.group_addr(),
+ version: IgmpVersion::Version1,
+ })
+ }
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ // always 8 bytes
+ field::GROUP_ADDRESS.end
+ }
+
+ /// Emit a high-level representation into an Internet Group Management Protocol v2 packet.
+ pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ match *self {
+ Repr::MembershipQuery {
+ max_resp_time,
+ group_addr,
+ version,
+ } => {
+ packet.set_msg_type(Message::MembershipQuery);
+ match version {
+ IgmpVersion::Version1 => packet.set_max_resp_code(0),
+ IgmpVersion::Version2 => {
+ packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time))
+ }
+ }
+ packet.set_group_address(group_addr);
+ }
+ Repr::MembershipReport {
+ group_addr,
+ version,
+ } => {
+ match version {
+ IgmpVersion::Version1 => packet.set_msg_type(Message::MembershipReportV1),
+ IgmpVersion::Version2 => packet.set_msg_type(Message::MembershipReportV2),
+ };
+ packet.set_max_resp_code(0);
+ packet.set_group_address(group_addr);
+ }
+ Repr::LeaveGroup { group_addr } => {
+ packet.set_msg_type(Message::LeaveGroup);
+ packet.set_group_address(group_addr);
+ }
+ }
+
+ packet.fill_checksum()
+ }
+}
+
+fn max_resp_code_to_duration(value: u8) -> Duration {
+ let value: u64 = value.into();
+ let decisecs = if value < 128 {
+ value
+ } else {
+ let mant = value & 0xF;
+ let exp = (value >> 4) & 0x7;
+ (mant | 0x10) << (exp + 3)
+ };
+ Duration::from_millis(decisecs * 100)
+}
+
+const fn duration_to_max_resp_code(duration: Duration) -> u8 {
+ let decisecs = duration.total_millis() / 100;
+ if decisecs < 128 {
+ decisecs as u8
+ } else if decisecs < 31744 {
+ let mut mant = decisecs >> 3;
+ let mut exp = 0u8;
+ while mant > 0x1F && exp < 0x8 {
+ mant >>= 1;
+ exp += 1;
+ }
+ 0x80 | (exp << 4) | (mant as u8 & 0xF)
+ } else {
+ 0xFF
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => write!(f, "IGMP ({err})"),
+ }
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Repr::MembershipQuery {
+ max_resp_time,
+ group_addr,
+ version,
+ } => write!(
+ f,
+ "IGMP membership query max_resp_time={max_resp_time} group_addr={group_addr} version={version:?}"
+ ),
+ Repr::MembershipReport {
+ group_addr,
+ version,
+ } => write!(
+ f,
+ "IGMP membership report group_addr={group_addr} version={version:?}"
+ ),
+ Repr::LeaveGroup { group_addr } => {
+ write!(f, "IGMP leave group group_addr={group_addr})")
+ }
+ }
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ match Packet::new_checked(buffer) {
+ Err(err) => writeln!(f, "{indent}({err})"),
+ Ok(packet) => writeln!(f, "{indent}{packet}"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static LEAVE_PACKET_BYTES: [u8; 8] = [0x17, 0x00, 0x02, 0x69, 0xe0, 0x00, 0x06, 0x96];
+ static REPORT_PACKET_BYTES: [u8; 8] = [0x16, 0x00, 0x08, 0xda, 0xe1, 0x00, 0x00, 0x25];
+
+ #[test]
+ fn test_leave_group_deconstruct() {
+ let packet = Packet::new_unchecked(&LEAVE_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::LeaveGroup);
+ assert_eq!(packet.max_resp_code(), 0);
+ assert_eq!(packet.checksum(), 0x269);
+ assert_eq!(
+ packet.group_addr(),
+ Ipv4Address::from_bytes(&[224, 0, 6, 150])
+ );
+ assert!(packet.verify_checksum());
+ }
+
+ #[test]
+ fn test_report_deconstruct() {
+ let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::MembershipReportV2);
+ assert_eq!(packet.max_resp_code(), 0);
+ assert_eq!(packet.checksum(), 0x08da);
+ assert_eq!(
+ packet.group_addr(),
+ Ipv4Address::from_bytes(&[225, 0, 0, 37])
+ );
+ assert!(packet.verify_checksum());
+ }
+
+ #[test]
+ fn test_leave_construct() {
+ let mut bytes = vec![0xa5; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::LeaveGroup);
+ packet.set_max_resp_code(0);
+ packet.set_group_address(Ipv4Address::from_bytes(&[224, 0, 6, 150]));
+ packet.fill_checksum();
+ assert_eq!(&*packet.into_inner(), &LEAVE_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_report_construct() {
+ let mut bytes = vec![0xa5; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::MembershipReportV2);
+ packet.set_max_resp_code(0);
+ packet.set_group_address(Ipv4Address::from_bytes(&[225, 0, 0, 37]));
+ packet.fill_checksum();
+ assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn max_resp_time_to_duration_and_back() {
+ for i in 0..256usize {
+ let time1 = i as u8;
+ let duration = max_resp_code_to_duration(time1);
+ let time2 = duration_to_max_resp_code(duration);
+ assert!(time1 == time2);
+ }
+ }
+
+ #[test]
+ fn duration_to_max_resp_time_max() {
+ for duration in 31744..65536 {
+ let time = duration_to_max_resp_code(Duration::from_millis(duration * 100));
+ assert_eq!(time, 0xFF);
+ }
+ }
+}
diff --git a/src/wire/ip.rs b/src/wire/ip.rs
new file mode 100644
index 0000000..da80aba
--- /dev/null
+++ b/src/wire/ip.rs
@@ -0,0 +1,998 @@
+use core::convert::From;
+use core::fmt;
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::{Ipv4Address, Ipv4Cidr, Ipv4Packet, Ipv4Repr};
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::{Ipv6Address, Ipv6Cidr, Ipv6Packet, Ipv6Repr};
+
+/// Internet protocol version.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Version {
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4,
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6,
+}
+
+impl Version {
+ /// Return the version of an IP packet stored in the provided buffer.
+ ///
+ /// This function never returns `Ok(IpVersion::Unspecified)`; instead,
+ /// unknown versions result in `Err(Error)`.
+ pub const fn of_packet(data: &[u8]) -> Result<Version> {
+ match data[0] >> 4 {
+ #[cfg(feature = "proto-ipv4")]
+ 4 => Ok(Version::Ipv4),
+ #[cfg(feature = "proto-ipv6")]
+ 6 => Ok(Version::Ipv6),
+ _ => Err(Error),
+ }
+ }
+}
+
+impl fmt::Display for Version {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Version::Ipv4 => write!(f, "IPv4"),
+ #[cfg(feature = "proto-ipv6")]
+ Version::Ipv6 => write!(f, "IPv6"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// IP datagram encapsulated protocol.
+ pub enum Protocol(u8) {
+ HopByHop = 0x00,
+ Icmp = 0x01,
+ Igmp = 0x02,
+ Tcp = 0x06,
+ Udp = 0x11,
+ Ipv6Route = 0x2b,
+ Ipv6Frag = 0x2c,
+ IpSecEsp = 0x32,
+ IpSecAh = 0x33,
+ Icmpv6 = 0x3a,
+ Ipv6NoNxt = 0x3b,
+ Ipv6Opts = 0x3c
+ }
+}
+
+impl fmt::Display for Protocol {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Protocol::HopByHop => write!(f, "Hop-by-Hop"),
+ Protocol::Icmp => write!(f, "ICMP"),
+ Protocol::Igmp => write!(f, "IGMP"),
+ Protocol::Tcp => write!(f, "TCP"),
+ Protocol::Udp => write!(f, "UDP"),
+ Protocol::Ipv6Route => write!(f, "IPv6-Route"),
+ Protocol::Ipv6Frag => write!(f, "IPv6-Frag"),
+ Protocol::IpSecEsp => write!(f, "IPsec-ESP"),
+ Protocol::IpSecAh => write!(f, "IPsec-AH"),
+ Protocol::Icmpv6 => write!(f, "ICMPv6"),
+ Protocol::Ipv6NoNxt => write!(f, "IPv6-NoNxt"),
+ Protocol::Ipv6Opts => write!(f, "IPv6-Opts"),
+ Protocol::Unknown(id) => write!(f, "0x{id:02x}"),
+ }
+ }
+}
+
+/// An internetworking address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub enum Address {
+ /// An IPv4 address.
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4(Ipv4Address),
+ /// An IPv6 address.
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6(Ipv6Address),
+}
+
+impl Address {
+ /// Create an address wrapping an IPv4 address with the given octets.
+ #[cfg(feature = "proto-ipv4")]
+ pub const fn v4(a0: u8, a1: u8, a2: u8, a3: u8) -> Address {
+ Address::Ipv4(Ipv4Address::new(a0, a1, a2, a3))
+ }
+
+ /// Create an address wrapping an IPv6 address with the given octets.
+ #[cfg(feature = "proto-ipv6")]
+ #[allow(clippy::too_many_arguments)]
+ pub fn v6(a0: u16, a1: u16, a2: u16, a3: u16, a4: u16, a5: u16, a6: u16, a7: u16) -> Address {
+ Address::Ipv6(Ipv6Address::new(a0, a1, a2, a3, a4, a5, a6, a7))
+ }
+
+ /// Return the protocol version.
+ pub const fn version(&self) -> Version {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(_) => Version::Ipv4,
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(_) => Version::Ipv6,
+ }
+ }
+
+ /// Return an address as a sequence of octets, in big-endian.
+ pub const fn as_bytes(&self) -> &[u8] {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => addr.as_bytes(),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => addr.as_bytes(),
+ }
+ }
+
+ /// Query whether the address is a valid unicast address.
+ pub fn is_unicast(&self) -> bool {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => addr.is_unicast(),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => addr.is_unicast(),
+ }
+ }
+
+ /// Query whether the address is a valid multicast address.
+ pub const fn is_multicast(&self) -> bool {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => addr.is_multicast(),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => addr.is_multicast(),
+ }
+ }
+
+ /// Query whether the address is the broadcast address.
+ pub fn is_broadcast(&self) -> bool {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => addr.is_broadcast(),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(_) => false,
+ }
+ }
+
+ /// Query whether the address falls into the "unspecified" range.
+ pub fn is_unspecified(&self) -> bool {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => addr.is_unspecified(),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => addr.is_unspecified(),
+ }
+ }
+
+ /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`,
+ /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise.
+ pub fn prefix_len(&self) -> Option<u8> {
+ let mut ones = true;
+ let mut prefix_len = 0;
+ for byte in self.as_bytes() {
+ let mut mask = 0x80;
+ for _ in 0..8 {
+ let one = *byte & mask != 0;
+ if ones {
+ // Expect 1s until first 0
+ if one {
+ prefix_len += 1;
+ } else {
+ ones = false;
+ }
+ } else if one {
+ // 1 where 0 was expected
+ return None;
+ }
+ mask >>= 1;
+ }
+ }
+ Some(prefix_len)
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4", feature = "proto-ipv6"))]
+impl From<::std::net::IpAddr> for Address {
+ fn from(x: ::std::net::IpAddr) -> Address {
+ match x {
+ ::std::net::IpAddr::V4(ipv4) => Address::Ipv4(ipv4.into()),
+ ::std::net::IpAddr::V6(ipv6) => Address::Ipv6(ipv6.into()),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Address> for ::std::net::IpAddr {
+ fn from(x: Address) -> ::std::net::IpAddr {
+ match x {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(ipv4) => ::std::net::IpAddr::V4(ipv4.into()),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(ipv6) => ::std::net::IpAddr::V6(ipv6.into()),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4"))]
+impl From<::std::net::Ipv4Addr> for Address {
+ fn from(ipv4: ::std::net::Ipv4Addr) -> Address {
+ Address::Ipv4(ipv4.into())
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv6"))]
+impl From<::std::net::Ipv6Addr> for Address {
+ fn from(ipv6: ::std::net::Ipv6Addr) -> Address {
+ Address::Ipv6(ipv6.into())
+ }
+}
+
+#[cfg(feature = "proto-ipv4")]
+impl From<Ipv4Address> for Address {
+ fn from(addr: Ipv4Address) -> Self {
+ Address::Ipv4(addr)
+ }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl From<Ipv6Address> for Address {
+ fn from(addr: Ipv6Address) -> Self {
+ Address::Ipv6(addr)
+ }
+}
+
+impl fmt::Display for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => write!(f, "{addr}"),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => write!(f, "{addr}"),
+ }
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Address {
+ fn format(&self, f: defmt::Formatter) {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ &Address::Ipv4(addr) => defmt::write!(f, "{:?}", addr),
+ #[cfg(feature = "proto-ipv6")]
+ &Address::Ipv6(addr) => defmt::write!(f, "{:?}", addr),
+ }
+ }
+}
+
+/// A specification of a CIDR block, containing an address and a variable-length
+/// subnet masking prefix length.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub enum Cidr {
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4(Ipv4Cidr),
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6(Ipv6Cidr),
+}
+
+impl Cidr {
+ /// Create a CIDR block from the given address and prefix length.
+ ///
+ /// # Panics
+ /// This function panics if the given prefix length is invalid for the given address.
+ pub fn new(addr: Address, prefix_len: u8) -> Cidr {
+ match addr {
+ #[cfg(feature = "proto-ipv4")]
+ Address::Ipv4(addr) => Cidr::Ipv4(Ipv4Cidr::new(addr, prefix_len)),
+ #[cfg(feature = "proto-ipv6")]
+ Address::Ipv6(addr) => Cidr::Ipv6(Ipv6Cidr::new(addr, prefix_len)),
+ }
+ }
+
+ /// Return the IP address of this CIDR block.
+ pub const fn address(&self) -> Address {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Cidr::Ipv4(cidr) => Address::Ipv4(cidr.address()),
+ #[cfg(feature = "proto-ipv6")]
+ Cidr::Ipv6(cidr) => Address::Ipv6(cidr.address()),
+ }
+ }
+
+ /// Return the prefix length of this CIDR block.
+ pub const fn prefix_len(&self) -> u8 {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Cidr::Ipv4(cidr) => cidr.prefix_len(),
+ #[cfg(feature = "proto-ipv6")]
+ Cidr::Ipv6(cidr) => cidr.prefix_len(),
+ }
+ }
+
+ /// Query whether the subnetwork described by this CIDR block contains
+ /// the given address.
+ pub fn contains_addr(&self, addr: &Address) -> bool {
+ match (self, addr) {
+ #[cfg(feature = "proto-ipv4")]
+ (Cidr::Ipv4(cidr), Address::Ipv4(addr)) => cidr.contains_addr(addr),
+ #[cfg(feature = "proto-ipv6")]
+ (Cidr::Ipv6(cidr), Address::Ipv6(addr)) => cidr.contains_addr(addr),
+ #[allow(unreachable_patterns)]
+ _ => false,
+ }
+ }
+
+ /// Query whether the subnetwork described by this CIDR block contains
+ /// the subnetwork described by the given CIDR block.
+ pub fn contains_subnet(&self, subnet: &Cidr) -> bool {
+ match (self, subnet) {
+ #[cfg(feature = "proto-ipv4")]
+ (Cidr::Ipv4(cidr), Cidr::Ipv4(other)) => cidr.contains_subnet(other),
+ #[cfg(feature = "proto-ipv6")]
+ (Cidr::Ipv6(cidr), Cidr::Ipv6(other)) => cidr.contains_subnet(other),
+ #[allow(unreachable_patterns)]
+ _ => false,
+ }
+ }
+}
+
+#[cfg(feature = "proto-ipv4")]
+impl From<Ipv4Cidr> for Cidr {
+ fn from(addr: Ipv4Cidr) -> Self {
+ Cidr::Ipv4(addr)
+ }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl From<Ipv6Cidr> for Cidr {
+ fn from(addr: Ipv6Cidr) -> Self {
+ Cidr::Ipv6(addr)
+ }
+}
+
+impl fmt::Display for Cidr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Cidr::Ipv4(cidr) => write!(f, "{cidr}"),
+ #[cfg(feature = "proto-ipv6")]
+ Cidr::Ipv6(cidr) => write!(f, "{cidr}"),
+ }
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Cidr {
+ fn format(&self, f: defmt::Formatter) {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ &Cidr::Ipv4(cidr) => defmt::write!(f, "{:?}", cidr),
+ #[cfg(feature = "proto-ipv6")]
+ &Cidr::Ipv6(cidr) => defmt::write!(f, "{:?}", cidr),
+ }
+ }
+}
+
+/// An internet endpoint address.
+///
+/// `Endpoint` always fully specifies both the address and the port.
+///
+/// See also ['ListenEndpoint'], which allows not specifying the address
+/// in order to listen on a given port on any address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub struct Endpoint {
+ pub addr: Address,
+ pub port: u16,
+}
+
+impl Endpoint {
+ /// Create an endpoint address from given address and port.
+ pub const fn new(addr: Address, port: u16) -> Endpoint {
+ Endpoint { addr: addr, port }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4", feature = "proto-ipv6"))]
+impl From<::std::net::SocketAddr> for Endpoint {
+ fn from(x: ::std::net::SocketAddr) -> Endpoint {
+ Endpoint {
+ addr: x.ip().into(),
+ port: x.port(),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4"))]
+impl From<::std::net::SocketAddrV4> for Endpoint {
+ fn from(x: ::std::net::SocketAddrV4) -> Endpoint {
+ Endpoint {
+ addr: (*x.ip()).into(),
+ port: x.port(),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv6"))]
+impl From<::std::net::SocketAddrV6> for Endpoint {
+ fn from(x: ::std::net::SocketAddrV6) -> Endpoint {
+ Endpoint {
+ addr: (*x.ip()).into(),
+ port: x.port(),
+ }
+ }
+}
+
+impl fmt::Display for Endpoint {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}:{}", self.addr, self.port)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Endpoint {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{:?}:{=u16}", self.addr, self.port);
+ }
+}
+
+impl<T: Into<Address>> From<(T, u16)> for Endpoint {
+ fn from((addr, port): (T, u16)) -> Endpoint {
+ Endpoint {
+ addr: addr.into(),
+ port,
+ }
+ }
+}
+
+/// An internet endpoint address for listening.
+///
+/// In contrast with [`Endpoint`], `ListenEndpoint` allows not specifying the address,
+/// in order to listen on a given port at all our addresses.
+///
+/// An endpoint can be constructed from a port, in which case the address is unspecified.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+pub struct ListenEndpoint {
+ pub addr: Option<Address>,
+ pub port: u16,
+}
+
+impl ListenEndpoint {
+ /// Query whether the endpoint has a specified address and port.
+ pub const fn is_specified(&self) -> bool {
+ self.addr.is_some() && self.port != 0
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4", feature = "proto-ipv6"))]
+impl From<::std::net::SocketAddr> for ListenEndpoint {
+ fn from(x: ::std::net::SocketAddr) -> ListenEndpoint {
+ ListenEndpoint {
+ addr: Some(x.ip().into()),
+ port: x.port(),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv4"))]
+impl From<::std::net::SocketAddrV4> for ListenEndpoint {
+ fn from(x: ::std::net::SocketAddrV4) -> ListenEndpoint {
+ ListenEndpoint {
+ addr: Some((*x.ip()).into()),
+ port: x.port(),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "proto-ipv6"))]
+impl From<::std::net::SocketAddrV6> for ListenEndpoint {
+ fn from(x: ::std::net::SocketAddrV6) -> ListenEndpoint {
+ ListenEndpoint {
+ addr: Some((*x.ip()).into()),
+ port: x.port(),
+ }
+ }
+}
+
+impl fmt::Display for ListenEndpoint {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(addr) = self.addr {
+ write!(f, "{}:{}", addr, self.port)
+ } else {
+ write!(f, "*:{}", self.port)
+ }
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for ListenEndpoint {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{:?}:{=u16}", self.addr, self.port);
+ }
+}
+
+impl From<u16> for ListenEndpoint {
+ fn from(port: u16) -> ListenEndpoint {
+ ListenEndpoint { addr: None, port }
+ }
+}
+
+impl From<Endpoint> for ListenEndpoint {
+ fn from(endpoint: Endpoint) -> ListenEndpoint {
+ ListenEndpoint {
+ addr: Some(endpoint.addr),
+ port: endpoint.port,
+ }
+ }
+}
+
+impl<T: Into<Address>> From<(T, u16)> for ListenEndpoint {
+ fn from((addr, port): (T, u16)) -> ListenEndpoint {
+ ListenEndpoint {
+ addr: Some(addr.into()),
+ port,
+ }
+ }
+}
+
+/// An IP packet representation.
+///
+/// This enum abstracts the various versions of IP packets. It either contains an IPv4
+/// or IPv6 concrete high-level representation.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr {
+ #[cfg(feature = "proto-ipv4")]
+ Ipv4(Ipv4Repr),
+ #[cfg(feature = "proto-ipv6")]
+ Ipv6(Ipv6Repr),
+}
+
+#[cfg(feature = "proto-ipv4")]
+impl From<Ipv4Repr> for Repr {
+ fn from(repr: Ipv4Repr) -> Repr {
+ Repr::Ipv4(repr)
+ }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl From<Ipv6Repr> for Repr {
+ fn from(repr: Ipv6Repr) -> Repr {
+ Repr::Ipv6(repr)
+ }
+}
+
+impl Repr {
+ /// Create a new IpRepr, choosing the right IP version for the src/dst addrs.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `src_addr` and `dst_addr` are different IP version.
+ pub fn new(
+ src_addr: Address,
+ dst_addr: Address,
+ next_header: Protocol,
+ payload_len: usize,
+ hop_limit: u8,
+ ) -> Self {
+ match (src_addr, dst_addr) {
+ #[cfg(feature = "proto-ipv4")]
+ (Address::Ipv4(src_addr), Address::Ipv4(dst_addr)) => Self::Ipv4(Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header,
+ payload_len,
+ hop_limit,
+ }),
+ #[cfg(feature = "proto-ipv6")]
+ (Address::Ipv6(src_addr), Address::Ipv6(dst_addr)) => Self::Ipv6(Ipv6Repr {
+ src_addr,
+ dst_addr,
+ next_header,
+ payload_len,
+ hop_limit,
+ }),
+ #[allow(unreachable_patterns)]
+ _ => panic!("IP version mismatch: src={src_addr:?} dst={dst_addr:?}"),
+ }
+ }
+
+ /// Return the protocol version.
+ pub const fn version(&self) -> Version {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(_) => Version::Ipv4,
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(_) => Version::Ipv6,
+ }
+ }
+
+ /// Return the source address.
+ pub const fn src_addr(&self) -> Address {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => Address::Ipv4(repr.src_addr),
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => Address::Ipv6(repr.src_addr),
+ }
+ }
+
+ /// Return the destination address.
+ pub const fn dst_addr(&self) -> Address {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => Address::Ipv4(repr.dst_addr),
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => Address::Ipv6(repr.dst_addr),
+ }
+ }
+
+ /// Return the next header (protocol).
+ pub const fn next_header(&self) -> Protocol {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => repr.next_header,
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => repr.next_header,
+ }
+ }
+
+ /// Return the payload length.
+ pub const fn payload_len(&self) -> usize {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => repr.payload_len,
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => repr.payload_len,
+ }
+ }
+
+ /// Set the payload length.
+ pub fn set_payload_len(&mut self, length: usize) {
+ match self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(Ipv4Repr { payload_len, .. }) => *payload_len = length,
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(Ipv6Repr { payload_len, .. }) => *payload_len = length,
+ }
+ }
+
+ /// Return the TTL value.
+ pub const fn hop_limit(&self) -> u8 {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(Ipv4Repr { hop_limit, .. }) => hop_limit,
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(Ipv6Repr { hop_limit, .. }) => hop_limit,
+ }
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn header_len(&self) -> usize {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => repr.buffer_len(),
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => repr.buffer_len(),
+ }
+ }
+
+ /// Emit this high-level representation into a buffer.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
+ &self,
+ buffer: T,
+ _checksum_caps: &ChecksumCapabilities,
+ ) {
+ match *self {
+ #[cfg(feature = "proto-ipv4")]
+ Repr::Ipv4(repr) => repr.emit(&mut Ipv4Packet::new_unchecked(buffer), _checksum_caps),
+ #[cfg(feature = "proto-ipv6")]
+ Repr::Ipv6(repr) => repr.emit(&mut Ipv6Packet::new_unchecked(buffer)),
+ }
+ }
+
+ /// Return the total length of a packet that will be emitted from this
+ /// high-level representation.
+ ///
+ /// This is the same as `repr.buffer_len() + repr.payload_len()`.
+ pub const fn buffer_len(&self) -> usize {
+ self.header_len() + self.payload_len()
+ }
+}
+
+pub mod checksum {
+ use byteorder::{ByteOrder, NetworkEndian};
+
+ use super::*;
+
+ const fn propagate_carries(word: u32) -> u16 {
+ let sum = (word >> 16) + (word & 0xffff);
+ ((sum >> 16) as u16) + (sum as u16)
+ }
+
+ /// Compute an RFC 1071 compliant checksum (without the final complement).
+ pub fn data(mut data: &[u8]) -> u16 {
+ let mut accum = 0;
+
+ // For each 32-byte chunk...
+ const CHUNK_SIZE: usize = 32;
+ while data.len() >= CHUNK_SIZE {
+ let mut d = &data[..CHUNK_SIZE];
+ // ... take by 2 bytes and sum them.
+ while d.len() >= 2 {
+ accum += NetworkEndian::read_u16(d) as u32;
+ d = &d[2..];
+ }
+
+ data = &data[CHUNK_SIZE..];
+ }
+
+ // Sum the rest that does not fit the last 32-byte chunk,
+ // taking by 2 bytes.
+ while data.len() >= 2 {
+ accum += NetworkEndian::read_u16(data) as u32;
+ data = &data[2..];
+ }
+
+ // Add the last remaining odd byte, if any.
+ if let Some(&value) = data.first() {
+ accum += (value as u32) << 8;
+ }
+
+ propagate_carries(accum)
+ }
+
+ /// Combine several RFC 1071 compliant checksums.
+ pub fn combine(checksums: &[u16]) -> u16 {
+ let mut accum: u32 = 0;
+ for &word in checksums {
+ accum += word as u32;
+ }
+ propagate_carries(accum)
+ }
+
+ /// Compute an IP pseudo header checksum.
+ pub fn pseudo_header(
+ src_addr: &Address,
+ dst_addr: &Address,
+ next_header: Protocol,
+ length: u32,
+ ) -> u16 {
+ match (src_addr, dst_addr) {
+ #[cfg(feature = "proto-ipv4")]
+ (&Address::Ipv4(src_addr), &Address::Ipv4(dst_addr)) => {
+ let mut proto_len = [0u8; 4];
+ proto_len[1] = next_header.into();
+ NetworkEndian::write_u16(&mut proto_len[2..4], length as u16);
+
+ combine(&[
+ data(src_addr.as_bytes()),
+ data(dst_addr.as_bytes()),
+ data(&proto_len[..]),
+ ])
+ }
+
+ #[cfg(feature = "proto-ipv6")]
+ (&Address::Ipv6(src_addr), &Address::Ipv6(dst_addr)) => {
+ let mut proto_len = [0u8; 8];
+ proto_len[7] = next_header.into();
+ NetworkEndian::write_u32(&mut proto_len[0..4], length);
+ combine(&[
+ data(src_addr.as_bytes()),
+ data(dst_addr.as_bytes()),
+ data(&proto_len[..]),
+ ])
+ }
+
+ #[allow(unreachable_patterns)]
+ _ => panic!("Unexpected pseudo header addresses: {src_addr}, {dst_addr}"),
+ }
+ }
+
+ // We use this in pretty printer implementations.
+ pub(crate) fn format_checksum(f: &mut fmt::Formatter, correct: bool) -> fmt::Result {
+ if !correct {
+ write!(f, " (checksum incorrect)")
+ } else {
+ Ok(())
+ }
+ }
+}
+
+use crate::wire::pretty_print::PrettyIndent;
+
+pub fn pretty_print_ip_payload<T: Into<Repr>>(
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ip_repr: T,
+ payload: &[u8],
+) -> fmt::Result {
+ #[cfg(feature = "proto-ipv4")]
+ use super::pretty_print::PrettyPrint;
+ use crate::wire::ip::checksum::format_checksum;
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::Icmpv4Packet;
+ use crate::wire::{TcpPacket, TcpRepr, UdpPacket, UdpRepr};
+
+ let checksum_caps = ChecksumCapabilities::ignored();
+ let repr = ip_repr.into();
+ match repr.next_header() {
+ #[cfg(feature = "proto-ipv4")]
+ Protocol::Icmp => {
+ indent.increase(f)?;
+ Icmpv4Packet::<&[u8]>::pretty_print(&payload, f, indent)
+ }
+ Protocol::Udp => {
+ indent.increase(f)?;
+ match UdpPacket::<&[u8]>::new_checked(payload) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(udp_packet) => {
+ match UdpRepr::parse(
+ &udp_packet,
+ &repr.src_addr(),
+ &repr.dst_addr(),
+ &checksum_caps,
+ ) {
+ Err(err) => write!(f, "{indent}{udp_packet} ({err})"),
+ Ok(udp_repr) => {
+ write!(
+ f,
+ "{}{} len={}",
+ indent,
+ udp_repr,
+ udp_packet.payload().len()
+ )?;
+ let valid =
+ udp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr());
+ format_checksum(f, valid)
+ }
+ }
+ }
+ }
+ }
+ Protocol::Tcp => {
+ indent.increase(f)?;
+ match TcpPacket::<&[u8]>::new_checked(payload) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(tcp_packet) => {
+ match TcpRepr::parse(
+ &tcp_packet,
+ &repr.src_addr(),
+ &repr.dst_addr(),
+ &checksum_caps,
+ ) {
+ Err(err) => write!(f, "{indent}{tcp_packet} ({err})"),
+ Ok(tcp_repr) => {
+ write!(f, "{indent}{tcp_repr}")?;
+ let valid =
+ tcp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr());
+ format_checksum(f, valid)
+ }
+ }
+ }
+ }
+ }
+ _ => Ok(()),
+ }
+}
+
+#[cfg(test)]
+pub(crate) mod test {
+ #![allow(unused)]
+
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) const MOCK_IP_ADDR_1: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ ]));
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) const MOCK_IP_ADDR_2: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ ]));
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) const MOCK_IP_ADDR_3: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ ]));
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) const MOCK_IP_ADDR_4: IpAddress = IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
+ ]));
+ #[cfg(feature = "proto-ipv6")]
+ pub(crate) const MOCK_UNSPECIFIED: IpAddress = IpAddress::Ipv6(Ipv6Address::UNSPECIFIED);
+
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ pub(crate) const MOCK_IP_ADDR_1: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 1]));
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ pub(crate) const MOCK_IP_ADDR_2: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 2]));
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ pub(crate) const MOCK_IP_ADDR_3: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 3]));
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ pub(crate) const MOCK_IP_ADDR_4: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 4]));
+ #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+ pub(crate) const MOCK_UNSPECIFIED: IpAddress = IpAddress::Ipv4(Ipv4Address::UNSPECIFIED);
+
+ use super::*;
+ use crate::wire::{IpAddress, IpCidr, IpProtocol};
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::{Ipv4Address, Ipv4Repr};
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn to_prefix_len_ipv4() {
+ fn test_eq<A: Into<Address>>(prefix_len: u8, mask: A) {
+ assert_eq!(Some(prefix_len), mask.into().prefix_len());
+ }
+
+ test_eq(0, Ipv4Address::new(0, 0, 0, 0));
+ test_eq(1, Ipv4Address::new(128, 0, 0, 0));
+ test_eq(2, Ipv4Address::new(192, 0, 0, 0));
+ test_eq(3, Ipv4Address::new(224, 0, 0, 0));
+ test_eq(4, Ipv4Address::new(240, 0, 0, 0));
+ test_eq(5, Ipv4Address::new(248, 0, 0, 0));
+ test_eq(6, Ipv4Address::new(252, 0, 0, 0));
+ test_eq(7, Ipv4Address::new(254, 0, 0, 0));
+ test_eq(8, Ipv4Address::new(255, 0, 0, 0));
+ test_eq(9, Ipv4Address::new(255, 128, 0, 0));
+ test_eq(10, Ipv4Address::new(255, 192, 0, 0));
+ test_eq(11, Ipv4Address::new(255, 224, 0, 0));
+ test_eq(12, Ipv4Address::new(255, 240, 0, 0));
+ test_eq(13, Ipv4Address::new(255, 248, 0, 0));
+ test_eq(14, Ipv4Address::new(255, 252, 0, 0));
+ test_eq(15, Ipv4Address::new(255, 254, 0, 0));
+ test_eq(16, Ipv4Address::new(255, 255, 0, 0));
+ test_eq(17, Ipv4Address::new(255, 255, 128, 0));
+ test_eq(18, Ipv4Address::new(255, 255, 192, 0));
+ test_eq(19, Ipv4Address::new(255, 255, 224, 0));
+ test_eq(20, Ipv4Address::new(255, 255, 240, 0));
+ test_eq(21, Ipv4Address::new(255, 255, 248, 0));
+ test_eq(22, Ipv4Address::new(255, 255, 252, 0));
+ test_eq(23, Ipv4Address::new(255, 255, 254, 0));
+ test_eq(24, Ipv4Address::new(255, 255, 255, 0));
+ test_eq(25, Ipv4Address::new(255, 255, 255, 128));
+ test_eq(26, Ipv4Address::new(255, 255, 255, 192));
+ test_eq(27, Ipv4Address::new(255, 255, 255, 224));
+ test_eq(28, Ipv4Address::new(255, 255, 255, 240));
+ test_eq(29, Ipv4Address::new(255, 255, 255, 248));
+ test_eq(30, Ipv4Address::new(255, 255, 255, 252));
+ test_eq(31, Ipv4Address::new(255, 255, 255, 254));
+ test_eq(32, Ipv4Address::new(255, 255, 255, 255));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn to_prefix_len_ipv4_error() {
+ assert_eq!(
+ None,
+ IpAddress::from(Ipv4Address::new(255, 255, 255, 1)).prefix_len()
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn to_prefix_len_ipv6() {
+ fn test_eq<A: Into<Address>>(prefix_len: u8, mask: A) {
+ assert_eq!(Some(prefix_len), mask.into().prefix_len());
+ }
+
+ test_eq(0, Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0));
+ test_eq(
+ 128,
+ Ipv6Address::new(
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ ),
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv6")]
+ fn to_prefix_len_ipv6_error() {
+ assert_eq!(
+ None,
+ IpAddress::from(Ipv6Address::new(
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 1
+ ))
+ .prefix_len()
+ );
+ }
+}
diff --git a/src/wire/ipsec_ah.rs b/src/wire/ipsec_ah.rs
new file mode 100644
index 0000000..1c3f00b
--- /dev/null
+++ b/src/wire/ipsec_ah.rs
@@ -0,0 +1,286 @@
+use byteorder::{ByteOrder, NetworkEndian};
+
+use super::{Error, IpProtocol, Result};
+
+/// A read/write wrapper around an IPSec Authentication Header packet buffer.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Next Header | Payload Len | RESERVED |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Security Parameters Index (SPI) |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Sequence Number Field |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | |
+// + Integrity Check Value-ICV (variable) |
+// | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::Field;
+
+ pub const NEXT_HEADER: usize = 0;
+ pub const PAYLOAD_LEN: usize = 1;
+ pub const RESERVED: Field = 2..4;
+ pub const SPI: Field = 4..8;
+ pub const SEQUENCE_NUMBER: Field = 8..12;
+
+ pub const fn ICV(payload_len: u8) -> Field {
+ // The `payload_len` is the length of this Authentication Header in 4-octet units, minus 2.
+ let header_len = (payload_len as usize + 2) * 4;
+
+ SEQUENCE_NUMBER.end..header_len
+ }
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with IPsec Authentication Header packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short or shorter than payload length.
+ ///
+ /// The result of this check is invalidated by calling [set_payload_len].
+ ///
+ /// [set_payload_len]: #method.set_payload_len
+ #[allow(clippy::if_same_then_else)]
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+ let len = data.len();
+ if len < field::SEQUENCE_NUMBER.end {
+ Err(Error)
+ } else if len < field::ICV(data[field::PAYLOAD_LEN]).end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return next header protocol type
+ /// The value is taken from the list of IP protocol numbers.
+ pub fn next_header(&self) -> IpProtocol {
+ let data = self.buffer.as_ref();
+ IpProtocol::from(data[field::NEXT_HEADER])
+ }
+
+ /// Return the length of this Authentication Header in 4-octet units, minus 2
+ pub fn payload_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::PAYLOAD_LEN]
+ }
+
+ /// Return the security parameters index
+ pub fn security_parameters_index(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::SPI];
+ NetworkEndian::read_u32(field)
+ }
+
+ /// Return sequence number
+ pub fn sequence_number(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::SEQUENCE_NUMBER];
+ NetworkEndian::read_u32(field)
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the integrity check value
+ #[inline]
+ pub fn integrity_check_value(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ &data[field::ICV(data[field::PAYLOAD_LEN])]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set next header protocol field
+ fn set_next_header(&mut self, value: IpProtocol) {
+ let data = self.buffer.as_mut();
+ data[field::NEXT_HEADER] = value.into()
+ }
+
+ /// Set payload length field
+ fn set_payload_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::PAYLOAD_LEN] = value
+ }
+
+ /// Clear reserved field
+ fn clear_reserved(&mut self) {
+ let data = self.buffer.as_mut();
+ data[field::RESERVED].fill(0)
+ }
+
+ /// Set security parameters index field
+ fn set_security_parameters_index(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::SPI], value)
+ }
+
+ /// Set sequence number
+ fn set_sequence_number(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::SEQUENCE_NUMBER], value)
+ }
+
+ /// Return a mutable pointer to the integrity check value.
+ #[inline]
+ pub fn integrity_check_value_mut(&mut self) -> &mut [u8] {
+ let data = self.buffer.as_mut();
+ let range = field::ICV(data[field::PAYLOAD_LEN]);
+ &mut data[range]
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+ next_header: IpProtocol,
+ security_parameters_index: u32,
+ sequence_number: u32,
+ integrity_check_value: &'a [u8],
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an IPSec Authentication Header packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&'a T>) -> Result<Repr<'a>> {
+ Ok(Repr {
+ next_header: packet.next_header(),
+ security_parameters_index: packet.security_parameters_index(),
+ sequence_number: packet.sequence_number(),
+ integrity_check_value: packet.integrity_check_value(),
+ })
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ self.integrity_check_value.len() + field::SEQUENCE_NUMBER.end
+ }
+
+ /// Emit a high-level representation into an IPSec Authentication Header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&'a mut T>) {
+ packet.set_next_header(self.next_header);
+
+ let payload_len = ((field::SEQUENCE_NUMBER.end + self.integrity_check_value.len()) / 4) - 2;
+ packet.set_payload_len(payload_len as u8);
+
+ packet.clear_reserved();
+ packet.set_security_parameters_index(self.security_parameters_index);
+ packet.set_sequence_number(self.sequence_number);
+ packet
+ .integrity_check_value_mut()
+ .copy_from_slice(self.integrity_check_value);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static PACKET_BYTES1: [u8; 24] = [
+ 0x32, 0x04, 0x00, 0x00, 0x81, 0x79, 0xb7, 0x05, 0x00, 0x00, 0x00, 0x01, 0x27, 0xcf, 0xc0,
+ 0xa5, 0xe4, 0x3d, 0x69, 0xb3, 0x72, 0x8e, 0xc5, 0xb0,
+ ];
+
+ static PACKET_BYTES2: [u8; 24] = [
+ 0x32, 0x04, 0x00, 0x00, 0xba, 0x8b, 0xd0, 0x60, 0x00, 0x00, 0x00, 0x01, 0xaf, 0xd2, 0xe7,
+ 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES1[..]);
+ assert_eq!(packet.next_header(), IpProtocol::IpSecEsp);
+ assert_eq!(packet.payload_len(), 4);
+ assert_eq!(packet.security_parameters_index(), 0x8179b705);
+ assert_eq!(packet.sequence_number(), 1);
+ assert_eq!(
+ packet.integrity_check_value(),
+ &[0x27, 0xcf, 0xc0, 0xa5, 0xe4, 0x3d, 0x69, 0xb3, 0x72, 0x8e, 0xc5, 0xb0]
+ );
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 24];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_next_header(IpProtocol::IpSecEsp);
+ packet.set_payload_len(4);
+ packet.clear_reserved();
+ packet.set_security_parameters_index(0xba8bd060);
+ packet.set_sequence_number(1);
+ const ICV: [u8; 12] = [
+ 0xaf, 0xd2, 0xe7, 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73,
+ ];
+ packet.integrity_check_value_mut().copy_from_slice(&ICV);
+ assert_eq!(bytes, PACKET_BYTES2);
+ }
+ #[test]
+ fn test_check_len() {
+ assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..10]), Err(_)));
+ assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..22]), Err(_)));
+ assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..]), Ok(_)));
+ }
+
+ fn packet_repr<'a>() -> Repr<'a> {
+ Repr {
+ next_header: IpProtocol::IpSecEsp,
+ security_parameters_index: 0xba8bd060,
+ sequence_number: 1,
+ integrity_check_value: &[
+ 0xaf, 0xd2, 0xe7, 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73,
+ ],
+ }
+ }
+
+ #[test]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES2[..]);
+ assert_eq!(Repr::parse(&packet).unwrap(), packet_repr());
+ }
+
+ #[test]
+ fn test_emit() {
+ let mut bytes = vec![0x17; 24];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet_repr().emit(&mut packet);
+ assert_eq!(bytes, PACKET_BYTES2);
+ }
+
+ #[test]
+ fn test_buffer_len() {
+ let header = Packet::new_unchecked(&PACKET_BYTES1[..]);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr.buffer_len(), PACKET_BYTES1.len());
+ }
+}
diff --git a/src/wire/ipsec_esp.rs b/src/wire/ipsec_esp.rs
new file mode 100644
index 0000000..d0cd572
--- /dev/null
+++ b/src/wire/ipsec_esp.rs
@@ -0,0 +1,177 @@
+use super::{Error, Result};
+use byteorder::{ByteOrder, NetworkEndian};
+
+/// A read/write wrapper around an IPSec Encapsulating Security Payload (ESP) packet buffer.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::Field;
+
+ pub const SPI: Field = 0..4;
+ pub const SEQUENCE_NUMBER: Field = 4..8;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with IPsec Encapsulating Security Payload packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+ let len = data.len();
+ if len < field::SEQUENCE_NUMBER.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the security parameters index
+ pub fn security_parameters_index(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::SPI];
+ NetworkEndian::read_u32(field)
+ }
+
+ /// Return sequence number
+ pub fn sequence_number(&self) -> u32 {
+ let field = &self.buffer.as_ref()[field::SEQUENCE_NUMBER];
+ NetworkEndian::read_u32(field)
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set security parameters index field
+ fn set_security_parameters_index(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::SPI], value)
+ }
+
+ /// Set sequence number
+ fn set_sequence_number(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::SEQUENCE_NUMBER], value)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr {
+ security_parameters_index: u32,
+ sequence_number: u32,
+}
+
+impl Repr {
+ /// Parse an IPSec Encapsulating Security Payload packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> {
+ Ok(Repr {
+ security_parameters_index: packet.security_parameters_index(),
+ sequence_number: packet.sequence_number(),
+ })
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ field::SEQUENCE_NUMBER.end
+ }
+
+ /// Emit a high-level representation into an IPSec Encapsulating Security Payload.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+ packet.set_security_parameters_index(self.security_parameters_index);
+ packet.set_sequence_number(self.sequence_number);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static PACKET_BYTES: [u8; 136] = [
+ 0xfb, 0x51, 0x28, 0xa6, 0x00, 0x00, 0x00, 0x02, 0x5d, 0xbe, 0x2d, 0x56, 0xd4, 0x6a, 0x57,
+ 0xf5, 0xfc, 0x69, 0x8b, 0x3c, 0xa6, 0xb6, 0x88, 0x3a, 0x6c, 0xc1, 0x33, 0x92, 0xdb, 0x40,
+ 0xab, 0x11, 0x54, 0xb4, 0x0f, 0x22, 0x4d, 0x37, 0x3a, 0x06, 0x94, 0x1e, 0xd4, 0x25, 0xaf,
+ 0xf0, 0xb0, 0x11, 0x1f, 0x07, 0x96, 0x2a, 0xa7, 0x20, 0xb1, 0xf5, 0x52, 0xb2, 0x12, 0x46,
+ 0xd6, 0xa5, 0x13, 0x4e, 0x97, 0x75, 0x44, 0x19, 0xc7, 0x29, 0x35, 0xc5, 0xed, 0xa4, 0x0c,
+ 0xe7, 0x87, 0xec, 0x9c, 0xb1, 0x12, 0x42, 0x74, 0x7c, 0x12, 0x3c, 0x7f, 0x44, 0x9c, 0x6b,
+ 0x46, 0x27, 0x28, 0xd2, 0x0e, 0xb1, 0x28, 0xd3, 0xd8, 0xc2, 0xd1, 0xac, 0x25, 0xfe, 0xef,
+ 0xed, 0x13, 0xfd, 0x8f, 0x18, 0x9c, 0x2d, 0xb1, 0x0e, 0x50, 0xe9, 0xaa, 0x65, 0x93, 0x56,
+ 0x40, 0x43, 0xa3, 0x72, 0x54, 0xba, 0x1b, 0xb1, 0xaf, 0xca, 0x04, 0x15, 0xf9, 0xef, 0xb7,
+ 0x1d,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(packet.security_parameters_index(), 0xfb5128a6);
+ assert_eq!(packet.sequence_number(), 2);
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_security_parameters_index(0xfb5128a6);
+ packet.set_sequence_number(2);
+ assert_eq!(&bytes, &PACKET_BYTES[..8]);
+ }
+ #[test]
+ fn test_check_len() {
+ assert!(matches!(Packet::new_checked(&PACKET_BYTES[..7]), Err(_)));
+ assert!(matches!(Packet::new_checked(&PACKET_BYTES[..]), Ok(_)));
+ }
+
+ fn packet_repr() -> Repr {
+ Repr {
+ security_parameters_index: 0xfb5128a6,
+ sequence_number: 2,
+ }
+ }
+
+ #[test]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(Repr::parse(&packet).unwrap(), packet_repr());
+ }
+
+ #[test]
+ fn test_emit() {
+ let mut bytes = vec![0x17; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet_repr().emit(&mut packet);
+ assert_eq!(&bytes, &PACKET_BYTES[..8]);
+ }
+
+ #[test]
+ fn test_buffer_len() {
+ let header = Packet::new_unchecked(&PACKET_BYTES[..]);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr.buffer_len(), 8);
+ }
+}
diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs
new file mode 100644
index 0000000..1027fc2
--- /dev/null
+++ b/src/wire/ipv4.rs
@@ -0,0 +1,1178 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+use crate::wire::ip::{checksum, pretty_print_ip_payload};
+
+pub use super::IpProtocol as Protocol;
+
+/// Minimum MTU required of all links supporting IPv4. See [RFC 791 § 3.1].
+///
+/// [RFC 791 § 3.1]: https://tools.ietf.org/html/rfc791#section-3.1
+// RFC 791 states the following:
+//
+// > Every internet module must be able to forward a datagram of 68
+// > octets without further fragmentation... Every internet destination
+// > must be able to receive a datagram of 576 octets either in one piece
+// > or in fragments to be reassembled.
+//
+// As a result, we can assume that every host we send packets to can
+// accept a packet of the following size.
+pub const MIN_MTU: usize = 576;
+
+/// Size of IPv4 adderess in octets.
+///
+/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc791#section-3.2
+pub const ADDR_SIZE: usize = 4;
+
+#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Key {
+ id: u16,
+ src_addr: Address,
+ dst_addr: Address,
+ protocol: Protocol,
+}
+
+/// A four-octet IPv4 address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+pub struct Address(pub [u8; ADDR_SIZE]);
+
+impl Address {
+ /// An unspecified address.
+ pub const UNSPECIFIED: Address = Address([0x00; ADDR_SIZE]);
+
+ /// The broadcast address.
+ pub const BROADCAST: Address = Address([0xff; ADDR_SIZE]);
+
+ /// All multicast-capable nodes
+ pub const MULTICAST_ALL_SYSTEMS: Address = Address([224, 0, 0, 1]);
+
+ /// All multicast-capable routers
+ pub const MULTICAST_ALL_ROUTERS: Address = Address([224, 0, 0, 2]);
+
+ /// Construct an IPv4 address from parts.
+ pub const fn new(a0: u8, a1: u8, a2: u8, a3: u8) -> Address {
+ Address([a0, a1, a2, a3])
+ }
+
+ /// Construct an IPv4 address from a sequence of octets, in big-endian.
+ ///
+ /// # Panics
+ /// The function panics if `data` is not four octets long.
+ pub fn from_bytes(data: &[u8]) -> Address {
+ let mut bytes = [0; ADDR_SIZE];
+ bytes.copy_from_slice(data);
+ Address(bytes)
+ }
+
+ /// Return an IPv4 address as a sequence of octets, in big-endian.
+ pub const fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+
+ /// Query whether the address is an unicast address.
+ pub fn is_unicast(&self) -> bool {
+ !(self.is_broadcast() || self.is_multicast() || self.is_unspecified())
+ }
+
+ /// Query whether the address is the broadcast address.
+ pub fn is_broadcast(&self) -> bool {
+ self.0[0..4] == [255; ADDR_SIZE]
+ }
+
+ /// Query whether the address is a multicast address.
+ pub const fn is_multicast(&self) -> bool {
+ self.0[0] & 0xf0 == 224
+ }
+
+ /// Query whether the address falls into the "unspecified" range.
+ pub const fn is_unspecified(&self) -> bool {
+ self.0[0] == 0
+ }
+
+ /// Query whether the address falls into the "link-local" range.
+ pub fn is_link_local(&self) -> bool {
+ self.0[0..2] == [169, 254]
+ }
+
+ /// Query whether the address falls into the "loopback" range.
+ pub const fn is_loopback(&self) -> bool {
+ self.0[0] == 127
+ }
+
+ /// Convert to an `IpAddress`.
+ ///
+ /// Same as `.into()`, but works in `const`.
+ pub const fn into_address(self) -> super::IpAddress {
+ super::IpAddress::Ipv4(self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::net::Ipv4Addr> for Address {
+ fn from(x: ::std::net::Ipv4Addr) -> Address {
+ Address(x.octets())
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Address> for ::std::net::Ipv4Addr {
+ fn from(Address(x): Address) -> ::std::net::Ipv4Addr {
+ x.into()
+ }
+}
+
+impl fmt::Display for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let bytes = self.0;
+ write!(f, "{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Address {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(
+ f,
+ "{=u8}.{=u8}.{=u8}.{=u8}",
+ self.0[0],
+ self.0[1],
+ self.0[2],
+ self.0[3]
+ )
+ }
+}
+
+/// A specification of an IPv4 CIDR block, containing an address and a variable-length
+/// subnet masking prefix length.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+pub struct Cidr {
+ address: Address,
+ prefix_len: u8,
+}
+
+impl Cidr {
+ /// Create an IPv4 CIDR block from the given address and prefix length.
+ ///
+ /// # Panics
+ /// This function panics if the prefix length is larger than 32.
+ #[allow(clippy::no_effect)]
+ pub const fn new(address: Address, prefix_len: u8) -> Cidr {
+ // Replace with const panic (or assert) when stabilized
+ // see: https://github.com/rust-lang/rust/issues/51999
+ ["Prefix length should be <= 32"][(prefix_len > 32) as usize];
+ Cidr {
+ address,
+ prefix_len,
+ }
+ }
+
+ /// Create an IPv4 CIDR block from the given address and network mask.
+ pub fn from_netmask(addr: Address, netmask: Address) -> Result<Cidr> {
+ let netmask = NetworkEndian::read_u32(&netmask.0[..]);
+ if netmask.leading_zeros() == 0 && netmask.trailing_zeros() == netmask.count_zeros() {
+ Ok(Cidr {
+ address: addr,
+ prefix_len: netmask.count_ones() as u8,
+ })
+ } else {
+ Err(Error)
+ }
+ }
+
+ /// Return the address of this IPv4 CIDR block.
+ pub const fn address(&self) -> Address {
+ self.address
+ }
+
+ /// Return the prefix length of this IPv4 CIDR block.
+ pub const fn prefix_len(&self) -> u8 {
+ self.prefix_len
+ }
+
+ /// Return the network mask of this IPv4 CIDR.
+ pub const fn netmask(&self) -> Address {
+ if self.prefix_len == 0 {
+ return Address([0, 0, 0, 0]);
+ }
+
+ let number = 0xffffffffu32 << (32 - self.prefix_len);
+ let data = [
+ ((number >> 24) & 0xff) as u8,
+ ((number >> 16) & 0xff) as u8,
+ ((number >> 8) & 0xff) as u8,
+ ((number >> 0) & 0xff) as u8,
+ ];
+
+ Address(data)
+ }
+
+ /// Return the broadcast address of this IPv4 CIDR.
+ pub fn broadcast(&self) -> Option<Address> {
+ let network = self.network();
+
+ if network.prefix_len == 31 || network.prefix_len == 32 {
+ return None;
+ }
+
+ let network_number = NetworkEndian::read_u32(&network.address.0[..]);
+ let number = network_number | 0xffffffffu32 >> network.prefix_len;
+ let data = [
+ ((number >> 24) & 0xff) as u8,
+ ((number >> 16) & 0xff) as u8,
+ ((number >> 8) & 0xff) as u8,
+ ((number >> 0) & 0xff) as u8,
+ ];
+
+ Some(Address(data))
+ }
+
+ /// Return the network block of this IPv4 CIDR.
+ pub const fn network(&self) -> Cidr {
+ let mask = self.netmask().0;
+ let network = [
+ self.address.0[0] & mask[0],
+ self.address.0[1] & mask[1],
+ self.address.0[2] & mask[2],
+ self.address.0[3] & mask[3],
+ ];
+ Cidr {
+ address: Address(network),
+ prefix_len: self.prefix_len,
+ }
+ }
+
+ /// Query whether the subnetwork described by this IPv4 CIDR block contains
+ /// the given address.
+ pub fn contains_addr(&self, addr: &Address) -> bool {
+ // right shift by 32 is not legal
+ if self.prefix_len == 0 {
+ return true;
+ }
+
+ let shift = 32 - self.prefix_len;
+ let self_prefix = NetworkEndian::read_u32(self.address.as_bytes()) >> shift;
+ let addr_prefix = NetworkEndian::read_u32(addr.as_bytes()) >> shift;
+ self_prefix == addr_prefix
+ }
+
+ /// Query whether the subnetwork described by this IPv4 CIDR block contains
+ /// the subnetwork described by the given IPv4 CIDR block.
+ pub fn contains_subnet(&self, subnet: &Cidr) -> bool {
+ self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address)
+ }
+}
+
+impl fmt::Display for Cidr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}/{}", self.address, self.prefix_len)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Cidr {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}/{=u8}", self.address, self.prefix_len);
+ }
+}
+
+/// A read/write wrapper around an Internet Protocol version 4 packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const VER_IHL: usize = 0;
+ pub const DSCP_ECN: usize = 1;
+ pub const LENGTH: Field = 2..4;
+ pub const IDENT: Field = 4..6;
+ pub const FLG_OFF: Field = 6..8;
+ pub const TTL: usize = 8;
+ pub const PROTOCOL: usize = 9;
+ pub const CHECKSUM: Field = 10..12;
+ pub const SRC_ADDR: Field = 12..16;
+ pub const DST_ADDR: Field = 16..20;
+}
+
+pub const HEADER_LEN: usize = field::DST_ADDR.end;
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with IPv4 packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ /// Returns `Err(Error)` if the header length is greater
+ /// than total length.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len]
+ /// and [set_total_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ /// [set_total_len]: #method.set_total_len
+ #[allow(clippy::if_same_then_else)]
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::DST_ADDR.end {
+ Err(Error)
+ } else if len < self.header_len() as usize {
+ Err(Error)
+ } else if self.header_len() as u16 > self.total_len() {
+ Err(Error)
+ } else if len < self.total_len() as usize {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the version field.
+ #[inline]
+ pub fn version(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::VER_IHL] >> 4
+ }
+
+ /// Return the header length, in octets.
+ #[inline]
+ pub fn header_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ (data[field::VER_IHL] & 0x0f) * 4
+ }
+
+ /// Return the Differential Services Code Point field.
+ pub fn dscp(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::DSCP_ECN] >> 2
+ }
+
+ /// Return the Explicit Congestion Notification field.
+ pub fn ecn(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::DSCP_ECN] & 0x03
+ }
+
+ /// Return the total length field.
+ #[inline]
+ pub fn total_len(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::LENGTH])
+ }
+
+ /// Return the fragment identification field.
+ #[inline]
+ pub fn ident(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::IDENT])
+ }
+
+ /// Return the "don't fragment" flag.
+ #[inline]
+ pub fn dont_frag(&self) -> bool {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x4000 != 0
+ }
+
+ /// Return the "more fragments" flag.
+ #[inline]
+ pub fn more_frags(&self) -> bool {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x2000 != 0
+ }
+
+ /// Return the fragment offset, in octets.
+ #[inline]
+ pub fn frag_offset(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::FLG_OFF]) << 3
+ }
+
+ /// Return the time to live field.
+ #[inline]
+ pub fn hop_limit(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::TTL]
+ }
+
+ /// Return the next_header (protocol) field.
+ #[inline]
+ pub fn next_header(&self) -> Protocol {
+ let data = self.buffer.as_ref();
+ Protocol::from(data[field::PROTOCOL])
+ }
+
+ /// Return the header checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Return the source address field.
+ #[inline]
+ pub fn src_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::SRC_ADDR])
+ }
+
+ /// Return the destination address field.
+ #[inline]
+ pub fn dst_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::DST_ADDR])
+ }
+
+ /// Validate the header checksum.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::data(&data[..self.header_len() as usize]) == !0
+ }
+
+ /// Returns the key for identifying the packet.
+ pub fn get_key(&self) -> Key {
+ Key {
+ id: self.ident(),
+ src_addr: self.src_addr(),
+ dst_addr: self.dst_addr(),
+ protocol: self.next_header(),
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the payload.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let range = self.header_len() as usize..self.total_len() as usize;
+ let data = self.buffer.as_ref();
+ &data[range]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the version field.
+ #[inline]
+ pub fn set_version(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::VER_IHL] = (data[field::VER_IHL] & !0xf0) | (value << 4);
+ }
+
+ /// Set the header length, in octets.
+ #[inline]
+ pub fn set_header_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::VER_IHL] = (data[field::VER_IHL] & !0x0f) | ((value / 4) & 0x0f);
+ }
+
+ /// Set the Differential Services Code Point field.
+ pub fn set_dscp(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0xfc) | (value << 2)
+ }
+
+ /// Set the Explicit Congestion Notification field.
+ pub fn set_ecn(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0x03) | (value & 0x03)
+ }
+
+ /// Set the total length field.
+ #[inline]
+ pub fn set_total_len(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::LENGTH], value)
+ }
+
+ /// Set the fragment identification field.
+ #[inline]
+ pub fn set_ident(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::IDENT], value)
+ }
+
+ /// Clear the entire flags field.
+ #[inline]
+ pub fn clear_flags(&mut self) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+ let raw = raw & !0xe000;
+ NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
+ }
+
+ /// Set the "don't fragment" flag.
+ #[inline]
+ pub fn set_dont_frag(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+ let raw = if value { raw | 0x4000 } else { raw & !0x4000 };
+ NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
+ }
+
+ /// Set the "more fragments" flag.
+ #[inline]
+ pub fn set_more_frags(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+ let raw = if value { raw | 0x2000 } else { raw & !0x2000 };
+ NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
+ }
+
+ /// Set the fragment offset, in octets.
+ #[inline]
+ pub fn set_frag_offset(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+ let raw = (raw & 0xe000) | (value >> 3);
+ NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
+ }
+
+ /// Set the time to live field.
+ #[inline]
+ pub fn set_hop_limit(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::TTL] = value
+ }
+
+ /// Set the next header (protocol) field.
+ #[inline]
+ pub fn set_next_header(&mut self, value: Protocol) {
+ let data = self.buffer.as_mut();
+ data[field::PROTOCOL] = value.into()
+ }
+
+ /// Set the header checksum field.
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Set the source address field.
+ #[inline]
+ pub fn set_src_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::SRC_ADDR].copy_from_slice(value.as_bytes())
+ }
+
+ /// Set the destination address field.
+ #[inline]
+ pub fn set_dst_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::DST_ADDR].copy_from_slice(value.as_bytes())
+ }
+
+ /// Compute and fill in the header checksum.
+ pub fn fill_checksum(&mut self) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::data(&data[..self.header_len() as usize])
+ };
+ self.set_checksum(checksum)
+ }
+
+ /// Return a mutable pointer to the payload.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let range = self.header_len() as usize..self.total_len() as usize;
+ let data = self.buffer.as_mut();
+ &mut data[range]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A high-level representation of an Internet Protocol version 4 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr {
+ pub src_addr: Address,
+ pub dst_addr: Address,
+ pub next_header: Protocol,
+ pub payload_len: usize,
+ pub hop_limit: u8,
+}
+
+impl Repr {
+ /// Parse an Internet Protocol version 4 packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(
+ packet: &Packet<&T>,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Repr> {
+ // Version 4 is expected.
+ if packet.version() != 4 {
+ return Err(Error);
+ }
+ // Valid checksum is expected.
+ if checksum_caps.ipv4.rx() && !packet.verify_checksum() {
+ return Err(Error);
+ }
+
+ #[cfg(not(feature = "proto-ipv4-fragmentation"))]
+ // We do not support fragmentation.
+ if packet.more_frags() || packet.frag_offset() != 0 {
+ return Err(Error);
+ }
+
+ let payload_len = packet.total_len() as usize - packet.header_len() as usize;
+
+ // All DSCP values are acceptable, since they are of no concern to receiving endpoint.
+ // All ECN values are acceptable, since ECN requires opt-in from both endpoints.
+ // All TTL values are acceptable, since we do not perform routing.
+ Ok(Repr {
+ src_addr: packet.src_addr(),
+ dst_addr: packet.dst_addr(),
+ next_header: packet.next_header(),
+ payload_len,
+ hop_limit: packet.hop_limit(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ // We never emit any options.
+ field::DST_ADDR.end
+ }
+
+ /// Emit a high-level representation into an Internet Protocol version 4 packet.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
+ &self,
+ packet: &mut Packet<T>,
+ checksum_caps: &ChecksumCapabilities,
+ ) {
+ packet.set_version(4);
+ packet.set_header_len(field::DST_ADDR.end as u8);
+ packet.set_dscp(0);
+ packet.set_ecn(0);
+ let total_len = packet.header_len() as u16 + self.payload_len as u16;
+ packet.set_total_len(total_len);
+ packet.set_ident(0);
+ packet.clear_flags();
+ packet.set_more_frags(false);
+ packet.set_dont_frag(true);
+ packet.set_frag_offset(0);
+ packet.set_hop_limit(self.hop_limit);
+ packet.set_next_header(self.next_header);
+ packet.set_src_addr(self.src_addr);
+ packet.set_dst_addr(self.dst_addr);
+
+ if checksum_caps.ipv4.tx() {
+ packet.fill_checksum();
+ } else {
+ // make sure we get a consistently zeroed checksum,
+ // since implementations might rely on it
+ packet.set_checksum(0);
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self, &ChecksumCapabilities::ignored()) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "IPv4 ({err})")?;
+ write!(
+ f,
+ " src={} dst={} proto={} hop_limit={}",
+ self.src_addr(),
+ self.dst_addr(),
+ self.next_header(),
+ self.hop_limit()
+ )?;
+ if self.version() != 4 {
+ write!(f, " ver={}", self.version())?;
+ }
+ if self.header_len() != 20 {
+ write!(f, " hlen={}", self.header_len())?;
+ }
+ if self.dscp() != 0 {
+ write!(f, " dscp={}", self.dscp())?;
+ }
+ if self.ecn() != 0 {
+ write!(f, " ecn={}", self.ecn())?;
+ }
+ write!(f, " tlen={}", self.total_len())?;
+ if self.dont_frag() {
+ write!(f, " df")?;
+ }
+ if self.more_frags() {
+ write!(f, " mf")?;
+ }
+ if self.frag_offset() != 0 {
+ write!(f, " off={}", self.frag_offset())?;
+ }
+ if self.more_frags() || self.frag_offset() != 0 {
+ write!(f, " id={}", self.ident())?;
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "IPv4 src={} dst={} proto={}",
+ self.src_addr, self.dst_addr, self.next_header
+ )
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ use crate::wire::ip::checksum::format_checksum;
+
+ let checksum_caps = ChecksumCapabilities::ignored();
+
+ let (ip_repr, payload) = match Packet::new_checked(buffer) {
+ Err(err) => return write!(f, "{indent}({err})"),
+ Ok(ip_packet) => match Repr::parse(&ip_packet, &checksum_caps) {
+ Err(_) => return Ok(()),
+ Ok(ip_repr) => {
+ if ip_packet.more_frags() || ip_packet.frag_offset() != 0 {
+ write!(
+ f,
+ "{}IPv4 Fragment more_frags={} offset={}",
+ indent,
+ ip_packet.more_frags(),
+ ip_packet.frag_offset()
+ )?;
+ return Ok(());
+ } else {
+ write!(f, "{indent}{ip_repr}")?;
+ format_checksum(f, ip_packet.verify_checksum())?;
+ (ip_repr, ip_packet.payload())
+ }
+ }
+ },
+ };
+
+ pretty_print_ip_payload(f, indent, ip_repr, payload)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static PACKET_BYTES: [u8; 30] = [
+ 0x45, 0x00, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x6e, 0x11, 0x12, 0x13,
+ 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ ];
+
+ static PAYLOAD_BYTES: [u8; 10] = [0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff];
+
+ #[test]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(packet.version(), 4);
+ assert_eq!(packet.header_len(), 20);
+ assert_eq!(packet.dscp(), 0);
+ assert_eq!(packet.ecn(), 0);
+ assert_eq!(packet.total_len(), 30);
+ assert_eq!(packet.ident(), 0x102);
+ assert!(packet.more_frags());
+ assert!(packet.dont_frag());
+ assert_eq!(packet.frag_offset(), 0x203 * 8);
+ assert_eq!(packet.hop_limit(), 0x1a);
+ assert_eq!(packet.next_header(), Protocol::Icmp);
+ assert_eq!(packet.checksum(), 0xd56e);
+ assert_eq!(packet.src_addr(), Address([0x11, 0x12, 0x13, 0x14]));
+ assert_eq!(packet.dst_addr(), Address([0x21, 0x22, 0x23, 0x24]));
+ assert!(packet.verify_checksum());
+ assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 30];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_version(4);
+ packet.set_header_len(20);
+ packet.clear_flags();
+ packet.set_dscp(0);
+ packet.set_ecn(0);
+ packet.set_total_len(30);
+ packet.set_ident(0x102);
+ packet.set_more_frags(true);
+ packet.set_dont_frag(true);
+ packet.set_frag_offset(0x203 * 8);
+ packet.set_hop_limit(0x1a);
+ packet.set_next_header(Protocol::Icmp);
+ packet.set_src_addr(Address([0x11, 0x12, 0x13, 0x14]));
+ packet.set_dst_addr(Address([0x21, 0x22, 0x23, 0x24]));
+ packet.fill_checksum();
+ packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_overlong() {
+ let mut bytes = vec![];
+ bytes.extend(&PACKET_BYTES[..]);
+ bytes.push(0);
+
+ assert_eq!(
+ Packet::new_unchecked(&bytes).payload().len(),
+ PAYLOAD_BYTES.len()
+ );
+ assert_eq!(
+ Packet::new_unchecked(&mut bytes).payload_mut().len(),
+ PAYLOAD_BYTES.len()
+ );
+ }
+
+ #[test]
+ fn test_total_len_overflow() {
+ let mut bytes = vec![];
+ bytes.extend(&PACKET_BYTES[..]);
+ Packet::new_unchecked(&mut bytes).set_total_len(128);
+
+ assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error);
+ }
+
+ static REPR_PACKET_BYTES: [u8; 24] = [
+ 0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x01, 0xd2, 0x79, 0x11, 0x12, 0x13,
+ 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ static REPR_PAYLOAD_BYTES: [u8; ADDR_SIZE] = [0xaa, 0x00, 0x00, 0xff];
+
+ const fn packet_repr() -> Repr {
+ Repr {
+ src_addr: Address([0x11, 0x12, 0x13, 0x14]),
+ dst_addr: Address([0x21, 0x22, 0x23, 0x24]),
+ next_header: Protocol::Icmp,
+ payload_len: 4,
+ hop_limit: 64,
+ }
+ }
+
+ #[test]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]);
+ let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+
+ #[test]
+ fn test_parse_bad_version() {
+ let mut bytes = vec![0; 24];
+ bytes.copy_from_slice(&REPR_PACKET_BYTES[..]);
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_version(6);
+ packet.fill_checksum();
+ let packet = Packet::new_unchecked(&*packet.into_inner());
+ assert_eq!(
+ Repr::parse(&packet, &ChecksumCapabilities::default()),
+ Err(Error)
+ );
+ }
+
+ #[test]
+ fn test_parse_total_len_less_than_header_len() {
+ let mut bytes = vec![0; 40];
+ bytes[0] = 0x09;
+ assert_eq!(Packet::new_checked(&mut bytes), Err(Error));
+ }
+
+ #[test]
+ fn test_emit() {
+ let repr = packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet, &ChecksumCapabilities::default());
+ packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES);
+ assert_eq!(&*packet.into_inner(), &REPR_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_unspecified() {
+ assert!(Address::UNSPECIFIED.is_unspecified());
+ assert!(!Address::UNSPECIFIED.is_broadcast());
+ assert!(!Address::UNSPECIFIED.is_multicast());
+ assert!(!Address::UNSPECIFIED.is_link_local());
+ assert!(!Address::UNSPECIFIED.is_loopback());
+ }
+
+ #[test]
+ fn test_broadcast() {
+ assert!(!Address::BROADCAST.is_unspecified());
+ assert!(Address::BROADCAST.is_broadcast());
+ assert!(!Address::BROADCAST.is_multicast());
+ assert!(!Address::BROADCAST.is_link_local());
+ assert!(!Address::BROADCAST.is_loopback());
+ }
+
+ #[test]
+ fn test_cidr() {
+ let cidr = Cidr::new(Address::new(192, 168, 1, 10), 24);
+
+ let inside_subnet = [
+ [192, 168, 1, 0],
+ [192, 168, 1, 1],
+ [192, 168, 1, 2],
+ [192, 168, 1, 10],
+ [192, 168, 1, 127],
+ [192, 168, 1, 255],
+ ];
+
+ let outside_subnet = [
+ [192, 168, 0, 0],
+ [127, 0, 0, 1],
+ [192, 168, 2, 0],
+ [192, 168, 0, 255],
+ [0, 0, 0, 0],
+ [255, 255, 255, 255],
+ ];
+
+ let subnets = [
+ ([192, 168, 1, 0], 32),
+ ([192, 168, 1, 255], 24),
+ ([192, 168, 1, 10], 30),
+ ];
+
+ let not_subnets = [
+ ([192, 168, 1, 10], 23),
+ ([127, 0, 0, 1], 8),
+ ([192, 168, 1, 0], 0),
+ ([192, 168, 0, 255], 32),
+ ];
+
+ for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) {
+ assert!(cidr.contains_addr(&addr));
+ }
+
+ for addr in outside_subnet.iter().map(|a| Address::from_bytes(a)) {
+ assert!(!cidr.contains_addr(&addr));
+ }
+
+ for subnet in subnets
+ .iter()
+ .map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p))
+ {
+ assert!(cidr.contains_subnet(&subnet));
+ }
+
+ for subnet in not_subnets
+ .iter()
+ .map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p))
+ {
+ assert!(!cidr.contains_subnet(&subnet));
+ }
+
+ let cidr_without_prefix = Cidr::new(cidr.address(), 0);
+ assert!(cidr_without_prefix.contains_addr(&Address::new(127, 0, 0, 1)));
+ }
+
+ #[test]
+ fn test_cidr_from_netmask() {
+ assert!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([1, 0, 2, 0])).is_err());
+ assert!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([0, 0, 0, 0])).is_err());
+ assert_eq!(
+ Cidr::from_netmask(Address([0, 0, 0, 1]), Address([255, 255, 255, 0])).unwrap(),
+ Cidr::new(Address([0, 0, 0, 1]), 24)
+ );
+ assert_eq!(
+ Cidr::from_netmask(Address([192, 168, 0, 1]), Address([255, 255, 0, 0])).unwrap(),
+ Cidr::new(Address([192, 168, 0, 1]), 16)
+ );
+ assert_eq!(
+ Cidr::from_netmask(Address([172, 16, 0, 1]), Address([255, 240, 0, 0])).unwrap(),
+ Cidr::new(Address([172, 16, 0, 1]), 12)
+ );
+ assert_eq!(
+ Cidr::from_netmask(Address([255, 255, 255, 1]), Address([255, 255, 255, 0])).unwrap(),
+ Cidr::new(Address([255, 255, 255, 1]), 24)
+ );
+ assert_eq!(
+ Cidr::from_netmask(Address([255, 255, 255, 255]), Address([255, 255, 255, 255]))
+ .unwrap(),
+ Cidr::new(Address([255, 255, 255, 255]), 32)
+ );
+ }
+
+ #[test]
+ fn test_cidr_netmask() {
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 0]), 0).netmask(),
+ Address([0, 0, 0, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 1]), 24).netmask(),
+ Address([255, 255, 255, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 0]), 32).netmask(),
+ Address([255, 255, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([127, 0, 0, 0]), 8).netmask(),
+ Address([255, 0, 0, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 0, 0]), 16).netmask(),
+ Address([255, 255, 0, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 16).netmask(),
+ Address([255, 255, 0, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 17).netmask(),
+ Address([255, 255, 128, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([172, 16, 0, 0]), 12).netmask(),
+ Address([255, 240, 0, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 1]), 24).netmask(),
+ Address([255, 255, 255, 0])
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 255]), 32).netmask(),
+ Address([255, 255, 255, 255])
+ );
+ }
+
+ #[test]
+ fn test_cidr_broadcast() {
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 0]), 0).broadcast().unwrap(),
+ Address([255, 255, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 1]), 24).broadcast().unwrap(),
+ Address([0, 0, 0, 255])
+ );
+ assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 32).broadcast(), None);
+ assert_eq!(
+ Cidr::new(Address([127, 0, 0, 0]), 8).broadcast().unwrap(),
+ Address([127, 255, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 0, 0]), 16)
+ .broadcast()
+ .unwrap(),
+ Address([192, 168, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 16)
+ .broadcast()
+ .unwrap(),
+ Address([192, 168, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 17)
+ .broadcast()
+ .unwrap(),
+ Address([192, 168, 127, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([172, 16, 0, 1]), 12).broadcast().unwrap(),
+ Address([172, 31, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 1]), 24)
+ .broadcast()
+ .unwrap(),
+ Address([255, 255, 255, 255])
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 254]), 31).broadcast(),
+ None
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 255]), 32).broadcast(),
+ None
+ );
+ }
+
+ #[test]
+ fn test_cidr_network() {
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 0]), 0).network(),
+ Cidr::new(Address([0, 0, 0, 0]), 0)
+ );
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 1]), 24).network(),
+ Cidr::new(Address([0, 0, 0, 0]), 24)
+ );
+ assert_eq!(
+ Cidr::new(Address([0, 0, 0, 0]), 32).network(),
+ Cidr::new(Address([0, 0, 0, 0]), 32)
+ );
+ assert_eq!(
+ Cidr::new(Address([127, 0, 0, 0]), 8).network(),
+ Cidr::new(Address([127, 0, 0, 0]), 8)
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 0, 0]), 16).network(),
+ Cidr::new(Address([192, 168, 0, 0]), 16)
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 16).network(),
+ Cidr::new(Address([192, 168, 0, 0]), 16)
+ );
+ assert_eq!(
+ Cidr::new(Address([192, 168, 1, 1]), 17).network(),
+ Cidr::new(Address([192, 168, 0, 0]), 17)
+ );
+ assert_eq!(
+ Cidr::new(Address([172, 16, 0, 1]), 12).network(),
+ Cidr::new(Address([172, 16, 0, 0]), 12)
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 1]), 24).network(),
+ Cidr::new(Address([255, 255, 255, 0]), 24)
+ );
+ assert_eq!(
+ Cidr::new(Address([255, 255, 255, 255]), 32).network(),
+ Cidr::new(Address([255, 255, 255, 255]), 32)
+ );
+ }
+}
diff --git a/src/wire/ipv6.rs b/src/wire/ipv6.rs
new file mode 100644
index 0000000..236600d
--- /dev/null
+++ b/src/wire/ipv6.rs
@@ -0,0 +1,1449 @@
+#![deny(missing_docs)]
+
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+use crate::wire::ip::pretty_print_ip_payload;
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::ipv4;
+
+pub use super::IpProtocol as Protocol;
+
+/// Minimum MTU required of all links supporting IPv6. See [RFC 8200 § 5].
+///
+/// [RFC 8200 § 5]: https://tools.ietf.org/html/rfc8200#section-5
+pub const MIN_MTU: usize = 1280;
+
+/// Size of IPv6 adderess in octets.
+///
+/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2
+pub const ADDR_SIZE: usize = 16;
+
+/// Size of IPv4-mapping prefix in octets.
+///
+/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2
+pub const IPV4_MAPPED_PREFIX_SIZE: usize = ADDR_SIZE - 4; // 4 == ipv4::ADDR_SIZE , cannot DRY here because of dependency on a IPv4 module which is behind the feature
+
+/// The [scope] of an address.
+///
+/// [scope]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Scope {
+ /// Interface Local scope
+ InterfaceLocal = 0x1,
+ /// Link local scope
+ LinkLocal = 0x2,
+ /// Administratively configured
+ AdminLocal = 0x4,
+ /// Single site scope
+ SiteLocal = 0x5,
+ /// Organization scope
+ OrganizationLocal = 0x8,
+ /// Global scope
+ Global = 0xE,
+ /// Unknown scope
+ Unknown = 0xFF,
+}
+
+impl From<u8> for Scope {
+ fn from(value: u8) -> Self {
+ match value {
+ 0x1 => Self::InterfaceLocal,
+ 0x2 => Self::LinkLocal,
+ 0x4 => Self::AdminLocal,
+ 0x5 => Self::SiteLocal,
+ 0x8 => Self::OrganizationLocal,
+ 0xE => Self::Global,
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// A sixteen-octet IPv6 address.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+pub struct Address(pub [u8; ADDR_SIZE]);
+
+impl Address {
+ /// The [unspecified address].
+ ///
+ /// [unspecified address]: https://tools.ietf.org/html/rfc4291#section-2.5.2
+ pub const UNSPECIFIED: Address = Address([0x00; ADDR_SIZE]);
+
+ /// The link-local [all nodes multicast address].
+ ///
+ /// [all nodes multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1
+ pub const LINK_LOCAL_ALL_NODES: Address = Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01,
+ ]);
+
+ /// The link-local [all routers multicast address].
+ ///
+ /// [all routers multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1
+ pub const LINK_LOCAL_ALL_ROUTERS: Address = Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02,
+ ]);
+
+ /// The link-local [all RPL nodes multicast address].
+ ///
+ /// [all RPL nodes multicast address]: https://www.rfc-editor.org/rfc/rfc6550.html#section-20.19
+ pub const LINK_LOCAL_ALL_RPL_NODES: Address = Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1a,
+ ]);
+
+ /// The [loopback address].
+ ///
+ /// [loopback address]: https://tools.ietf.org/html/rfc4291#section-2.5.3
+ pub const LOOPBACK: Address = Address([
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01,
+ ]);
+
+ /// The prefix used in [IPv4-mapped addresses].
+ ///
+ /// [IPv4-mapped addresses]: https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2
+ pub const IPV4_MAPPED_PREFIX: [u8; IPV4_MAPPED_PREFIX_SIZE] =
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff];
+
+ /// Construct an IPv6 address from parts.
+ #[allow(clippy::too_many_arguments)]
+ pub const fn new(
+ a0: u16,
+ a1: u16,
+ a2: u16,
+ a3: u16,
+ a4: u16,
+ a5: u16,
+ a6: u16,
+ a7: u16,
+ ) -> Address {
+ Address([
+ (a0 >> 8) as u8,
+ a0 as u8,
+ (a1 >> 8) as u8,
+ a1 as u8,
+ (a2 >> 8) as u8,
+ a2 as u8,
+ (a3 >> 8) as u8,
+ a3 as u8,
+ (a4 >> 8) as u8,
+ a4 as u8,
+ (a5 >> 8) as u8,
+ a5 as u8,
+ (a6 >> 8) as u8,
+ a6 as u8,
+ (a7 >> 8) as u8,
+ a7 as u8,
+ ])
+ }
+
+ /// Construct an IPv6 address from a sequence of octets, in big-endian.
+ ///
+ /// # Panics
+ /// The function panics if `data` is not sixteen octets long.
+ pub fn from_bytes(data: &[u8]) -> Address {
+ let mut bytes = [0; ADDR_SIZE];
+ bytes.copy_from_slice(data);
+ Address(bytes)
+ }
+
+ /// Construct an IPv6 address from a sequence of words, in big-endian.
+ ///
+ /// # Panics
+ /// The function panics if `data` is not 8 words long.
+ pub fn from_parts(data: &[u16]) -> Address {
+ assert!(data.len() >= 8);
+ let mut bytes = [0; ADDR_SIZE];
+ for (word_idx, chunk) in bytes.chunks_mut(2).enumerate() {
+ NetworkEndian::write_u16(chunk, data[word_idx]);
+ }
+ Address(bytes)
+ }
+
+ /// Write a IPv6 address to the given slice.
+ ///
+ /// # Panics
+ /// The function panics if `data` is not 8 words long.
+ pub fn write_parts(&self, data: &mut [u16]) {
+ assert!(data.len() >= 8);
+ for (i, chunk) in self.0.chunks(2).enumerate() {
+ data[i] = NetworkEndian::read_u16(chunk);
+ }
+ }
+
+ /// Return an IPv6 address as a sequence of octets, in big-endian.
+ pub const fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+
+ /// Query whether the IPv6 address is an [unicast address].
+ ///
+ /// [unicast address]: https://tools.ietf.org/html/rfc4291#section-2.5
+ pub fn is_unicast(&self) -> bool {
+ !(self.is_multicast() || self.is_unspecified())
+ }
+
+ /// Query whether the IPv6 address is a [global unicast address].
+ ///
+ /// [global unicast address]: https://datatracker.ietf.org/doc/html/rfc3587
+ pub const fn is_global_unicast(&self) -> bool {
+ (self.0[0] >> 5) == 0b001
+ }
+
+ /// Query whether the IPv6 address is a [multicast address].
+ ///
+ /// [multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7
+ pub const fn is_multicast(&self) -> bool {
+ self.0[0] == 0xff
+ }
+
+ /// Query whether the IPv6 address is the [unspecified address].
+ ///
+ /// [unspecified address]: https://tools.ietf.org/html/rfc4291#section-2.5.2
+ pub fn is_unspecified(&self) -> bool {
+ self.0 == [0x00; ADDR_SIZE]
+ }
+
+ /// Query whether the IPv6 address is in the [link-local] scope.
+ ///
+ /// [link-local]: https://tools.ietf.org/html/rfc4291#section-2.5.6
+ pub fn is_link_local(&self) -> bool {
+ self.0[0..8] == [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ }
+
+ /// Query whether the IPv6 address is a [Unique Local Address] (ULA).
+ ///
+ /// [Unique Local Address]: https://tools.ietf.org/html/rfc4193
+ pub fn is_unique_local(&self) -> bool {
+ (self.0[0] & 0b1111_1110) == 0xfc
+ }
+
+ /// Query whether the IPv6 address is the [loopback address].
+ ///
+ /// [loopback address]: https://tools.ietf.org/html/rfc4291#section-2.5.3
+ pub fn is_loopback(&self) -> bool {
+ *self == Self::LOOPBACK
+ }
+
+ /// Query whether the IPv6 address is an [IPv4 mapped IPv6 address].
+ ///
+ /// [IPv4 mapped IPv6 address]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
+ pub fn is_ipv4_mapped(&self) -> bool {
+ self.0[..IPV4_MAPPED_PREFIX_SIZE] == Self::IPV4_MAPPED_PREFIX
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ /// Convert an IPv4 mapped IPv6 address to an IPv4 address.
+ pub fn as_ipv4(&self) -> Option<ipv4::Address> {
+ if self.is_ipv4_mapped() {
+ Some(ipv4::Address::from_bytes(
+ &self.0[IPV4_MAPPED_PREFIX_SIZE..],
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// Helper function used to mask an address given a prefix.
+ ///
+ /// # Panics
+ /// This function panics if `mask` is greater than 128.
+ pub(super) fn mask(&self, mask: u8) -> [u8; ADDR_SIZE] {
+ assert!(mask <= 128);
+ let mut bytes = [0u8; ADDR_SIZE];
+ let idx = (mask as usize) / 8;
+ let modulus = (mask as usize) % 8;
+ let (first, second) = self.0.split_at(idx);
+ bytes[0..idx].copy_from_slice(first);
+ if idx < ADDR_SIZE {
+ let part = second[0];
+ bytes[idx] = part & (!(0xff >> modulus) as u8);
+ }
+ bytes
+ }
+
+ /// The solicited node for the given unicast address.
+ ///
+ /// # Panics
+ /// This function panics if the given address is not
+ /// unicast.
+ pub fn solicited_node(&self) -> Address {
+ assert!(self.is_unicast());
+ Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
+ self.0[13], self.0[14], self.0[15],
+ ])
+ }
+
+ /// Return the scope of the address.
+ pub(crate) fn scope(&self) -> Scope {
+ if self.is_multicast() {
+ return Scope::from(self.as_bytes()[1] & 0b1111);
+ }
+
+ if self.is_link_local() {
+ Scope::LinkLocal
+ } else if self.is_unique_local() || self.is_global_unicast() {
+ // ULA are considered global scope
+ // https://www.rfc-editor.org/rfc/rfc6724#section-3.1
+ Scope::Global
+ } else {
+ Scope::Unknown
+ }
+ }
+
+ /// Convert to an `IpAddress`.
+ ///
+ /// Same as `.into()`, but works in `const`.
+ pub const fn into_address(self) -> super::IpAddress {
+ super::IpAddress::Ipv6(self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::net::Ipv6Addr> for Address {
+ fn from(x: ::std::net::Ipv6Addr) -> Address {
+ Address(x.octets())
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Address> for ::std::net::Ipv6Addr {
+ fn from(Address(x): Address) -> ::std::net::Ipv6Addr {
+ x.into()
+ }
+}
+
+impl fmt::Display for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_ipv4_mapped() {
+ return write!(
+ f,
+ "::ffff:{}.{}.{}.{}",
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 0],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 1],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 2],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 3]
+ );
+ }
+
+ // The string representation of an IPv6 address should
+ // collapse a series of 16 bit sections that evaluate
+ // to 0 to "::"
+ //
+ // See https://tools.ietf.org/html/rfc4291#section-2.2
+ // for details.
+ enum State {
+ Head,
+ HeadBody,
+ Tail,
+ TailBody,
+ }
+ let mut words = [0u16; 8];
+ self.write_parts(&mut words);
+ let mut state = State::Head;
+ for word in words.iter() {
+ state = match (*word, &state) {
+ // Once a u16 equal to zero write a double colon and
+ // skip to the next non-zero u16.
+ (0, &State::Head) | (0, &State::HeadBody) => {
+ write!(f, "::")?;
+ State::Tail
+ }
+ // Continue iterating without writing any characters until
+ // we hit a non-zero value.
+ (0, &State::Tail) => State::Tail,
+ // When the state is Head or Tail write a u16 in hexadecimal
+ // without the leading colon if the value is not 0.
+ (_, &State::Head) => {
+ write!(f, "{word:x}")?;
+ State::HeadBody
+ }
+ (_, &State::Tail) => {
+ write!(f, "{word:x}")?;
+ State::TailBody
+ }
+ // Write the u16 with a leading colon when parsing a value
+ // that isn't the first in a section
+ (_, &State::HeadBody) | (_, &State::TailBody) => {
+ write!(f, ":{word:x}")?;
+ state
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Address {
+ fn format(&self, f: defmt::Formatter) {
+ if self.is_ipv4_mapped() {
+ return defmt::write!(
+ f,
+ "::ffff:{}.{}.{}.{}",
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 0],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 1],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 2],
+ self.0[IPV4_MAPPED_PREFIX_SIZE + 3]
+ );
+ }
+
+ // The string representation of an IPv6 address should
+ // collapse a series of 16 bit sections that evaluate
+ // to 0 to "::"
+ //
+ // See https://tools.ietf.org/html/rfc4291#section-2.2
+ // for details.
+ enum State {
+ Head,
+ HeadBody,
+ Tail,
+ TailBody,
+ }
+ let mut words = [0u16; 8];
+ self.write_parts(&mut words);
+ let mut state = State::Head;
+ for word in words.iter() {
+ state = match (*word, &state) {
+ // Once a u16 equal to zero write a double colon and
+ // skip to the next non-zero u16.
+ (0, &State::Head) | (0, &State::HeadBody) => {
+ defmt::write!(f, "::");
+ State::Tail
+ }
+ // Continue iterating without writing any characters until
+ // we hit a non-zero value.
+ (0, &State::Tail) => State::Tail,
+ // When the state is Head or Tail write a u16 in hexadecimal
+ // without the leading colon if the value is not 0.
+ (_, &State::Head) => {
+ defmt::write!(f, "{:x}", word);
+ State::HeadBody
+ }
+ (_, &State::Tail) => {
+ defmt::write!(f, "{:x}", word);
+ State::TailBody
+ }
+ // Write the u16 with a leading colon when parsing a value
+ // that isn't the first in a section
+ (_, &State::HeadBody) | (_, &State::TailBody) => {
+ defmt::write!(f, ":{:x}", word);
+ state
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "proto-ipv4")]
+/// Convert the given IPv4 address into a IPv4-mapped IPv6 address
+impl From<ipv4::Address> for Address {
+ fn from(address: ipv4::Address) -> Self {
+ let mut b = [0_u8; ADDR_SIZE];
+ b[..Self::IPV4_MAPPED_PREFIX.len()].copy_from_slice(&Self::IPV4_MAPPED_PREFIX);
+ b[Self::IPV4_MAPPED_PREFIX.len()..].copy_from_slice(&address.0);
+ Self(b)
+ }
+}
+
+/// A specification of an IPv6 CIDR block, containing an address and a variable-length
+/// subnet masking prefix length.
+#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
+pub struct Cidr {
+ address: Address,
+ prefix_len: u8,
+}
+
+impl Cidr {
+ /// The [solicited node prefix].
+ ///
+ /// [solicited node prefix]: https://tools.ietf.org/html/rfc4291#section-2.7.1
+ pub const SOLICITED_NODE_PREFIX: Cidr = Cidr {
+ address: Address([
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00,
+ 0x00, 0x00,
+ ]),
+ prefix_len: 104,
+ };
+
+ /// Create an IPv6 CIDR block from the given address and prefix length.
+ ///
+ /// # Panics
+ /// This function panics if the prefix length is larger than 128.
+ pub const fn new(address: Address, prefix_len: u8) -> Cidr {
+ assert!(prefix_len <= 128);
+ Cidr {
+ address,
+ prefix_len,
+ }
+ }
+
+ /// Return the address of this IPv6 CIDR block.
+ pub const fn address(&self) -> Address {
+ self.address
+ }
+
+ /// Return the prefix length of this IPv6 CIDR block.
+ pub const fn prefix_len(&self) -> u8 {
+ self.prefix_len
+ }
+
+ /// Query whether the subnetwork described by this IPv6 CIDR block contains
+ /// the given address.
+ pub fn contains_addr(&self, addr: &Address) -> bool {
+ // right shift by 128 is not legal
+ if self.prefix_len == 0 {
+ return true;
+ }
+
+ self.address.mask(self.prefix_len) == addr.mask(self.prefix_len)
+ }
+
+ /// Query whether the subnetwork described by this IPV6 CIDR block contains
+ /// the subnetwork described by the given IPv6 CIDR block.
+ pub fn contains_subnet(&self, subnet: &Cidr) -> bool {
+ self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address)
+ }
+}
+
+impl fmt::Display for Cidr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // https://tools.ietf.org/html/rfc4291#section-2.3
+ write!(f, "{}/{}", self.address, self.prefix_len)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Cidr {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}/{=u8}", self.address, self.prefix_len);
+ }
+}
+
+/// A read/write wrapper around an Internet Protocol version 6 packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// Ranges and constants describing the IPv6 header
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |Version| Traffic Class | Flow Label |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Payload Length | Next Header | Hop Limit |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | |
+// + +
+// | |
+// + Source Address +
+// | |
+// + +
+// | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | |
+// + +
+// | |
+// + Destination Address +
+// | |
+// + +
+// | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// See https://tools.ietf.org/html/rfc2460#section-3 for details.
+mod field {
+ use crate::wire::field::*;
+ // 4-bit version number, 8-bit traffic class, and the
+ // 20-bit flow label.
+ pub const VER_TC_FLOW: Field = 0..4;
+ // 16-bit value representing the length of the payload.
+ // Note: Options are included in this length.
+ pub const LENGTH: Field = 4..6;
+ // 8-bit value identifying the type of header following this
+ // one. Note: The same numbers are used in IPv4.
+ pub const NXT_HDR: usize = 6;
+ // 8-bit value decremented by each node that forwards this
+ // packet. The packet is discarded when the value is 0.
+ pub const HOP_LIMIT: usize = 7;
+ // IPv6 address of the source node.
+ pub const SRC_ADDR: Field = 8..24;
+ // IPv6 address of the destination node.
+ pub const DST_ADDR: Field = 24..40;
+}
+
+/// Length of an IPv6 header.
+pub const HEADER_LEN: usize = field::DST_ADDR.end;
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Create a raw octet buffer with an IPv6 packet structure.
+ #[inline]
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ #[inline]
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_payload_len].
+ ///
+ /// [set_payload_len]: #method.set_payload_len
+ #[inline]
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::DST_ADDR.end || len < self.total_len() {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ #[inline]
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the header length.
+ #[inline]
+ pub const fn header_len(&self) -> usize {
+ // This is not a strictly necessary function, but it makes
+ // code more readable.
+ field::DST_ADDR.end
+ }
+
+ /// Return the version field.
+ #[inline]
+ pub fn version(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::VER_TC_FLOW.start] >> 4
+ }
+
+ /// Return the traffic class.
+ #[inline]
+ pub fn traffic_class(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ ((NetworkEndian::read_u16(&data[0..2]) & 0x0ff0) >> 4) as u8
+ }
+
+ /// Return the flow label field.
+ #[inline]
+ pub fn flow_label(&self) -> u32 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u24(&data[1..4]) & 0x000fffff
+ }
+
+ /// Return the payload length field.
+ #[inline]
+ pub fn payload_len(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::LENGTH])
+ }
+
+ /// Return the payload length added to the known header length.
+ #[inline]
+ pub fn total_len(&self) -> usize {
+ self.header_len() + self.payload_len() as usize
+ }
+
+ /// Return the next header field.
+ #[inline]
+ pub fn next_header(&self) -> Protocol {
+ let data = self.buffer.as_ref();
+ Protocol::from(data[field::NXT_HDR])
+ }
+
+ /// Return the hop limit field.
+ #[inline]
+ pub fn hop_limit(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::HOP_LIMIT]
+ }
+
+ /// Return the source address field.
+ #[inline]
+ pub fn src_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::SRC_ADDR])
+ }
+
+ /// Return the destination address field.
+ #[inline]
+ pub fn dst_addr(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::DST_ADDR])
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the payload.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ let range = self.header_len()..self.total_len();
+ &data[range]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the version field.
+ #[inline]
+ pub fn set_version(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ // Make sure to retain the lower order bits which contain
+ // the higher order bits of the traffic class
+ data[0] = (data[0] & 0x0f) | ((value & 0x0f) << 4);
+ }
+
+ /// Set the traffic class field.
+ #[inline]
+ pub fn set_traffic_class(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ // Put the higher order 4-bits of value in the lower order
+ // 4-bits of the first byte
+ data[0] = (data[0] & 0xf0) | ((value & 0xf0) >> 4);
+ // Put the lower order 4-bits of value in the higher order
+ // 4-bits of the second byte
+ data[1] = (data[1] & 0x0f) | ((value & 0x0f) << 4);
+ }
+
+ /// Set the flow label field.
+ #[inline]
+ pub fn set_flow_label(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ // Retain the lower order 4-bits of the traffic class
+ let raw = (((data[1] & 0xf0) as u32) << 16) | (value & 0x0fffff);
+ NetworkEndian::write_u24(&mut data[1..4], raw);
+ }
+
+ /// Set the payload length field.
+ #[inline]
+ pub fn set_payload_len(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::LENGTH], value);
+ }
+
+ /// Set the next header field.
+ #[inline]
+ pub fn set_next_header(&mut self, value: Protocol) {
+ let data = self.buffer.as_mut();
+ data[field::NXT_HDR] = value.into();
+ }
+
+ /// Set the hop limit field.
+ #[inline]
+ pub fn set_hop_limit(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::HOP_LIMIT] = value;
+ }
+
+ /// Set the source address field.
+ #[inline]
+ pub fn set_src_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::SRC_ADDR].copy_from_slice(value.as_bytes());
+ }
+
+ /// Set the destination address field.
+ #[inline]
+ pub fn set_dst_addr(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::DST_ADDR].copy_from_slice(value.as_bytes());
+ }
+
+ /// Return a mutable pointer to the payload.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let range = self.header_len()..self.total_len();
+ let data = self.buffer.as_mut();
+ &mut data[range]
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "IPv6 ({err})")?;
+ Ok(())
+ }
+ }
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A high-level representation of an Internet Protocol version 6 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr {
+ /// IPv6 address of the source node.
+ pub src_addr: Address,
+ /// IPv6 address of the destination node.
+ pub dst_addr: Address,
+ /// Protocol contained in the next header.
+ pub next_header: Protocol,
+ /// Length of the payload including the extension headers.
+ pub payload_len: usize,
+ /// The 8-bit hop limit field.
+ pub hop_limit: u8,
+}
+
+impl Repr {
+ /// Parse an Internet Protocol version 6 packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&T>) -> Result<Repr> {
+ // Ensure basic accessors will work
+ packet.check_len()?;
+ if packet.version() != 6 {
+ return Err(Error);
+ }
+ Ok(Repr {
+ src_addr: packet.src_addr(),
+ dst_addr: packet.dst_addr(),
+ next_header: packet.next_header(),
+ payload_len: packet.payload_len() as usize,
+ hop_limit: packet.hop_limit(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ // This function is not strictly necessary, but it can make client code more readable.
+ field::DST_ADDR.end
+ }
+
+ /// Emit a high-level representation into an Internet Protocol version 6 packet.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+ // Make no assumptions about the original state of the packet buffer.
+ // Make sure to set every byte.
+ packet.set_version(6);
+ packet.set_traffic_class(0);
+ packet.set_flow_label(0);
+ packet.set_payload_len(self.payload_len as u16);
+ packet.set_hop_limit(self.hop_limit);
+ packet.set_next_header(self.next_header);
+ packet.set_src_addr(self.src_addr);
+ packet.set_dst_addr(self.dst_addr);
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "IPv6 src={} dst={} nxt_hdr={} hop_limit={}",
+ self.src_addr, self.dst_addr, self.next_header, self.hop_limit
+ )
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Repr {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(
+ fmt,
+ "IPv6 src={} dst={} nxt_hdr={} hop_limit={}",
+ self.src_addr,
+ self.dst_addr,
+ self.next_header,
+ self.hop_limit
+ )
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+// TODO: This is very similar to the implementation for IPv4. Make
+// a way to have less copy and pasted code here.
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ let (ip_repr, payload) = match Packet::new_checked(buffer) {
+ Err(err) => return write!(f, "{indent}({err})"),
+ Ok(ip_packet) => match Repr::parse(&ip_packet) {
+ Err(_) => return Ok(()),
+ Ok(ip_repr) => {
+ write!(f, "{indent}{ip_repr}")?;
+ (ip_repr, ip_packet.payload())
+ }
+ },
+ };
+
+ pretty_print_ip_payload(f, indent, ip_repr, payload)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Error;
+ use super::{Address, Cidr};
+ use super::{Packet, Protocol, Repr};
+ use crate::wire::pretty_print::PrettyPrinter;
+
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::ipv4::Address as Ipv4Address;
+
+ const LINK_LOCAL_ADDR: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
+ const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1);
+
+ #[test]
+ fn test_basic_multicast() {
+ assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unspecified());
+ assert!(Address::LINK_LOCAL_ALL_ROUTERS.is_multicast());
+ assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_link_local());
+ assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_loopback());
+ assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unique_local());
+ assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_global_unicast());
+ assert!(!Address::LINK_LOCAL_ALL_NODES.is_unspecified());
+ assert!(Address::LINK_LOCAL_ALL_NODES.is_multicast());
+ assert!(!Address::LINK_LOCAL_ALL_NODES.is_link_local());
+ assert!(!Address::LINK_LOCAL_ALL_NODES.is_loopback());
+ assert!(!Address::LINK_LOCAL_ALL_NODES.is_unique_local());
+ assert!(!Address::LINK_LOCAL_ALL_NODES.is_global_unicast());
+ }
+
+ #[test]
+ fn test_basic_link_local() {
+ assert!(!LINK_LOCAL_ADDR.is_unspecified());
+ assert!(!LINK_LOCAL_ADDR.is_multicast());
+ assert!(LINK_LOCAL_ADDR.is_link_local());
+ assert!(!LINK_LOCAL_ADDR.is_loopback());
+ assert!(!LINK_LOCAL_ADDR.is_unique_local());
+ assert!(!LINK_LOCAL_ADDR.is_global_unicast());
+ }
+
+ #[test]
+ fn test_basic_loopback() {
+ assert!(!Address::LOOPBACK.is_unspecified());
+ assert!(!Address::LOOPBACK.is_multicast());
+ assert!(!Address::LOOPBACK.is_link_local());
+ assert!(Address::LOOPBACK.is_loopback());
+ assert!(!Address::LOOPBACK.is_unique_local());
+ assert!(!Address::LOOPBACK.is_global_unicast());
+ }
+
+ #[test]
+ fn test_unique_local() {
+ assert!(!UNIQUE_LOCAL_ADDR.is_unspecified());
+ assert!(!UNIQUE_LOCAL_ADDR.is_multicast());
+ assert!(!UNIQUE_LOCAL_ADDR.is_link_local());
+ assert!(!UNIQUE_LOCAL_ADDR.is_loopback());
+ assert!(UNIQUE_LOCAL_ADDR.is_unique_local());
+ assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast());
+ }
+
+ #[test]
+ fn test_global_unicast() {
+ assert!(!GLOBAL_UNICAST_ADDR.is_unspecified());
+ assert!(!GLOBAL_UNICAST_ADDR.is_multicast());
+ assert!(!GLOBAL_UNICAST_ADDR.is_link_local());
+ assert!(!GLOBAL_UNICAST_ADDR.is_loopback());
+ assert!(!GLOBAL_UNICAST_ADDR.is_unique_local());
+ assert!(GLOBAL_UNICAST_ADDR.is_global_unicast());
+ }
+
+ #[test]
+ fn test_address_format() {
+ assert_eq!("ff02::1", format!("{}", Address::LINK_LOCAL_ALL_NODES));
+ assert_eq!("fe80::1", format!("{LINK_LOCAL_ADDR}"));
+ assert_eq!(
+ "fe80::7f00:0:1",
+ format!(
+ "{}",
+ Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001)
+ )
+ );
+ assert_eq!("::", format!("{}", Address::UNSPECIFIED));
+ assert_eq!("::1", format!("{}", Address::LOOPBACK));
+
+ #[cfg(feature = "proto-ipv4")]
+ assert_eq!(
+ "::ffff:192.168.1.1",
+ format!("{}", Address::from(Ipv4Address::new(192, 168, 1, 1)))
+ );
+ }
+
+ #[test]
+ fn test_new() {
+ assert_eq!(
+ Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
+ Address::LINK_LOCAL_ALL_NODES
+ );
+ assert_eq!(
+ Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2),
+ Address::LINK_LOCAL_ALL_ROUTERS
+ );
+ assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 1), Address::LOOPBACK);
+ assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 0), Address::UNSPECIFIED);
+ assert_eq!(Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), LINK_LOCAL_ADDR);
+ }
+
+ #[test]
+ fn test_from_parts() {
+ assert_eq!(
+ Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 1]),
+ Address::LINK_LOCAL_ALL_NODES
+ );
+ assert_eq!(
+ Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 2]),
+ Address::LINK_LOCAL_ALL_ROUTERS
+ );
+ assert_eq!(
+ Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 1]),
+ Address::LOOPBACK
+ );
+ assert_eq!(
+ Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 0]),
+ Address::UNSPECIFIED
+ );
+ assert_eq!(
+ Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 1]),
+ LINK_LOCAL_ADDR
+ );
+ }
+
+ #[test]
+ fn test_write_parts() {
+ let mut bytes = [0u16; 8];
+ {
+ Address::LOOPBACK.write_parts(&mut bytes);
+ assert_eq!(Address::LOOPBACK, Address::from_parts(&bytes));
+ }
+ {
+ Address::LINK_LOCAL_ALL_ROUTERS.write_parts(&mut bytes);
+ assert_eq!(Address::LINK_LOCAL_ALL_ROUTERS, Address::from_parts(&bytes));
+ }
+ {
+ LINK_LOCAL_ADDR.write_parts(&mut bytes);
+ assert_eq!(LINK_LOCAL_ADDR, Address::from_parts(&bytes));
+ }
+ }
+
+ #[test]
+ fn test_mask() {
+ let addr = Address::new(0x0123, 0x4567, 0x89ab, 0, 0, 0, 0, 1);
+ assert_eq!(
+ addr.mask(11),
+ [0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ );
+ assert_eq!(
+ addr.mask(15),
+ [0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ );
+ assert_eq!(
+ addr.mask(26),
+ [0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ );
+ assert_eq!(
+ addr.mask(128),
+ [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
+ );
+ assert_eq!(
+ addr.mask(127),
+ [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ );
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ #[test]
+ fn test_is_ipv4_mapped() {
+ assert!(!Address::UNSPECIFIED.is_ipv4_mapped());
+ assert!(Address::from(Ipv4Address::new(192, 168, 1, 1)).is_ipv4_mapped());
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ #[test]
+ fn test_as_ipv4() {
+ assert_eq!(None, Address::UNSPECIFIED.as_ipv4());
+
+ let ipv4 = Ipv4Address::new(192, 168, 1, 1);
+ assert_eq!(Some(ipv4), Address::from(ipv4).as_ipv4());
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ #[test]
+ fn test_from_ipv4_address() {
+ assert_eq!(
+ Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1]),
+ Address::from(Ipv4Address::new(192, 168, 1, 1))
+ );
+ assert_eq!(
+ Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 222, 1, 41, 90]),
+ Address::from(Ipv4Address::new(222, 1, 41, 90))
+ );
+ }
+
+ #[test]
+ fn test_cidr() {
+ // fe80::1/56
+ // 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ let cidr = Cidr::new(LINK_LOCAL_ADDR, 56);
+
+ let inside_subnet = [
+ // fe80::2
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02,
+ ],
+ // fe80::1122:3344:5566:7788
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x77, 0x88,
+ ],
+ // fe80::ff00:0:0:0
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ ],
+ // fe80::ff
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff,
+ ],
+ ];
+
+ let outside_subnet = [
+ // fe80:0:0:101::1
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ],
+ // ::1
+ [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ],
+ // ff02::1
+ [
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ],
+ // ff02::2
+ [
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02,
+ ],
+ ];
+
+ let subnets = [
+ // fe80::ffff:ffff:ffff:ffff/65
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ],
+ 65,
+ ),
+ // fe80::1/128
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01,
+ ],
+ 128,
+ ),
+ // fe80::1234:5678/96
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
+ 0x34, 0x56, 0x78,
+ ],
+ 96,
+ ),
+ ];
+
+ let not_subnets = [
+ // fe80::101:ffff:ffff:ffff:ffff/55
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ],
+ 55,
+ ),
+ // fe80::101:ffff:ffff:ffff:ffff/56
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ],
+ 56,
+ ),
+ // fe80::101:ffff:ffff:ffff:ffff/57
+ (
+ [
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ],
+ 57,
+ ),
+ // ::1/128
+ (
+ [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01,
+ ],
+ 128,
+ ),
+ ];
+
+ for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) {
+ assert!(cidr.contains_addr(&addr));
+ }
+
+ for addr in outside_subnet.iter().map(|a| Address::from_bytes(a)) {
+ assert!(!cidr.contains_addr(&addr));
+ }
+
+ for subnet in subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) {
+ assert!(cidr.contains_subnet(&subnet));
+ }
+
+ for subnet in not_subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) {
+ assert!(!cidr.contains_subnet(&subnet));
+ }
+
+ let cidr_without_prefix = Cidr::new(LINK_LOCAL_ADDR, 0);
+ assert!(cidr_without_prefix.contains_addr(&Address::LOOPBACK));
+ }
+
+ #[test]
+ #[should_panic(expected = "length")]
+ fn test_from_bytes_too_long() {
+ let _ = Address::from_bytes(&[0u8; 15]);
+ }
+
+ #[test]
+ #[should_panic(expected = "data.len() >= 8")]
+ fn test_from_parts_too_long() {
+ let _ = Address::from_parts(&[0u16; 7]);
+ }
+
+ #[test]
+ fn test_scope() {
+ use super::*;
+ assert_eq!(
+ Address::new(0xff01, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::InterfaceLocal
+ );
+ assert_eq!(
+ Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::LinkLocal
+ );
+ assert_eq!(
+ Address::new(0xff03, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::Unknown
+ );
+ assert_eq!(
+ Address::new(0xff04, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::AdminLocal
+ );
+ assert_eq!(
+ Address::new(0xff05, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::SiteLocal
+ );
+ assert_eq!(
+ Address::new(0xff08, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::OrganizationLocal
+ );
+ assert_eq!(
+ Address::new(0xff0e, 0, 0, 0, 0, 0, 0, 1).scope(),
+ Scope::Global
+ );
+
+ assert_eq!(Address::LINK_LOCAL_ALL_NODES.scope(), Scope::LinkLocal);
+
+ // For source address selection, unicast addresses also have a scope:
+ assert_eq!(LINK_LOCAL_ADDR.scope(), Scope::LinkLocal);
+ assert_eq!(GLOBAL_UNICAST_ADDR.scope(), Scope::Global);
+ assert_eq!(UNIQUE_LOCAL_ADDR.scope(), Scope::Global);
+ }
+
+ static REPR_PACKET_BYTES: [u8; 52] = [
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
+ 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff,
+ ];
+ static REPR_PAYLOAD_BYTES: [u8; 12] = [
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff,
+ ];
+
+ const fn packet_repr() -> Repr {
+ Repr {
+ src_addr: Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ]),
+ dst_addr: Address::LINK_LOCAL_ALL_NODES,
+ next_header: Protocol::Udp,
+ payload_len: 12,
+ hop_limit: 64,
+ }
+ }
+
+ #[test]
+ fn test_packet_deconstruction() {
+ let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]);
+ assert_eq!(packet.check_len(), Ok(()));
+ assert_eq!(packet.version(), 6);
+ assert_eq!(packet.traffic_class(), 0);
+ assert_eq!(packet.flow_label(), 0);
+ assert_eq!(packet.total_len(), 0x34);
+ assert_eq!(packet.payload_len() as usize, REPR_PAYLOAD_BYTES.len());
+ assert_eq!(packet.next_header(), Protocol::Udp);
+ assert_eq!(packet.hop_limit(), 0x40);
+ assert_eq!(
+ packet.src_addr(),
+ Address([
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01
+ ])
+ );
+ assert_eq!(packet.dst_addr(), Address::LINK_LOCAL_ALL_NODES);
+ assert_eq!(packet.payload(), &REPR_PAYLOAD_BYTES[..]);
+ }
+
+ #[test]
+ fn test_packet_construction() {
+ let mut bytes = [0xff; 52];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ // Version, Traffic Class, and Flow Label are not
+ // byte aligned. make sure the setters and getters
+ // do not interfere with each other.
+ packet.set_version(6);
+ assert_eq!(packet.version(), 6);
+ packet.set_traffic_class(0x99);
+ assert_eq!(packet.version(), 6);
+ assert_eq!(packet.traffic_class(), 0x99);
+ packet.set_flow_label(0x54321);
+ assert_eq!(packet.traffic_class(), 0x99);
+ assert_eq!(packet.flow_label(), 0x54321);
+ packet.set_payload_len(0xc);
+ packet.set_next_header(Protocol::Udp);
+ packet.set_hop_limit(0xfe);
+ packet.set_src_addr(Address::LINK_LOCAL_ALL_ROUTERS);
+ packet.set_dst_addr(Address::LINK_LOCAL_ALL_NODES);
+ packet
+ .payload_mut()
+ .copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
+ let mut expected_bytes = [
+ 0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+ let start = expected_bytes.len() - REPR_PAYLOAD_BYTES.len();
+ expected_bytes[start..].copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
+ assert_eq!(packet.check_len(), Ok(()));
+ assert_eq!(&*packet.into_inner(), &expected_bytes[..]);
+ }
+
+ #[test]
+ fn test_overlong() {
+ let mut bytes = vec![];
+ bytes.extend(&REPR_PACKET_BYTES[..]);
+ bytes.push(0);
+
+ assert_eq!(
+ Packet::new_unchecked(&bytes).payload().len(),
+ REPR_PAYLOAD_BYTES.len()
+ );
+ assert_eq!(
+ Packet::new_unchecked(&mut bytes).payload_mut().len(),
+ REPR_PAYLOAD_BYTES.len()
+ );
+ }
+
+ #[test]
+ fn test_total_len_overflow() {
+ let mut bytes = vec![];
+ bytes.extend(&REPR_PACKET_BYTES[..]);
+ Packet::new_unchecked(&mut bytes).set_payload_len(0x80);
+
+ assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error);
+ }
+
+ #[test]
+ fn test_repr_parse_valid() {
+ let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]);
+ let repr = Repr::parse(&packet).unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+
+ #[test]
+ fn test_repr_parse_bad_version() {
+ let mut bytes = vec![0; 40];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ packet.set_version(4);
+ packet.set_payload_len(0);
+ let packet = Packet::new_unchecked(&*packet.into_inner());
+ assert_eq!(Repr::parse(&packet), Err(Error));
+ }
+
+ #[test]
+ fn test_repr_parse_smaller_than_header() {
+ let mut bytes = vec![0; 40];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ packet.set_version(6);
+ packet.set_payload_len(39);
+ let packet = Packet::new_unchecked(&*packet.into_inner());
+ assert_eq!(Repr::parse(&packet), Err(Error));
+ }
+
+ #[test]
+ fn test_repr_parse_smaller_than_payload() {
+ let mut bytes = vec![0; 40];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ packet.set_version(6);
+ packet.set_payload_len(1);
+ let packet = Packet::new_unchecked(&*packet.into_inner());
+ assert_eq!(Repr::parse(&packet), Err(Error));
+ }
+
+ #[test]
+ fn test_basic_repr_emit() {
+ let repr = packet_repr();
+ let mut bytes = vec![0xff; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(&mut packet);
+ packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES);
+ assert_eq!(&*packet.into_inner(), &REPR_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_pretty_print() {
+ assert_eq!(
+ format!(
+ "{}",
+ PrettyPrinter::<Packet<&'static [u8]>>::new("\n", &&REPR_PACKET_BYTES[..])
+ ),
+ "\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4"
+ );
+ }
+}
diff --git a/src/wire/ipv6ext_header.rs b/src/wire/ipv6ext_header.rs
new file mode 100644
index 0000000..bc8ef87
--- /dev/null
+++ b/src/wire/ipv6ext_header.rs
@@ -0,0 +1,305 @@
+#![allow(unused)]
+
+use super::IpProtocol;
+use super::{Error, Result};
+
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ pub const MIN_HEADER_SIZE: usize = 8;
+
+ pub const NXT_HDR: usize = 0;
+ pub const LENGTH: usize = 1;
+ // Variable-length field.
+ //
+ // Length of the header is in 8-octet units, not including the first 8 octets.
+ // The first two octets are the next header type and the header length.
+ pub const fn PAYLOAD(length_field: u8) -> Field {
+ let bytes = length_field as usize * 8 + 8;
+ 2..bytes
+ }
+}
+
+/// A read/write wrapper around an IPv6 Extension Header buffer.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Header<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+/// Core getter methods relevant to any IPv6 extension header.
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Create a raw octet buffer with an IPv6 Extension Header structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Header { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let header = Self::new_unchecked(buffer);
+ header.check_len()?;
+ Ok(header)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+
+ let len = data.len();
+ if len < field::MIN_HEADER_SIZE {
+ return Err(Error);
+ }
+
+ let of = field::PAYLOAD(data[field::LENGTH]);
+ if len < of.end {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consume the header, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the next header field.
+ pub fn next_header(&self) -> IpProtocol {
+ let data = self.buffer.as_ref();
+ IpProtocol::from(data[field::NXT_HDR])
+ }
+
+ /// Return the header length field.
+ pub fn header_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::LENGTH]
+ }
+}
+
+impl<'h, T: AsRef<[u8]> + ?Sized> Header<&'h T> {
+ /// Return the payload of the IPv6 extension header.
+ pub fn payload(&self) -> &'h [u8] {
+ let data = self.buffer.as_ref();
+ &data[field::PAYLOAD(data[field::LENGTH])]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
+ /// Set the next header field.
+ #[inline]
+ pub fn set_next_header(&mut self, value: IpProtocol) {
+ let data = self.buffer.as_mut();
+ data[field::NXT_HDR] = value.into();
+ }
+
+ /// Set the extension header data length. The length of the header is
+ /// in 8-octet units, not including the first 8 octets.
+ #[inline]
+ pub fn set_header_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::LENGTH] = value;
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Header<&'a mut T> {
+ /// Return a mutable pointer to the payload data.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let data = self.buffer.as_mut();
+ let len = data[field::LENGTH];
+ &mut data[field::PAYLOAD(len)]
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+ pub next_header: IpProtocol,
+ pub length: u8,
+ pub data: &'a [u8],
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an IPv6 Extension Header Header and return a high-level representation.
+ pub fn parse<T>(header: &Header<&'a T>) -> Result<Self>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ Ok(Self {
+ next_header: header.next_header(),
+ length: header.header_len(),
+ data: header.payload(),
+ })
+ }
+
+ /// Return the length, in bytes, of a header that will be emitted from this high-level
+ /// representation.
+ pub const fn header_len(&self) -> usize {
+ 2
+ }
+
+ /// Emit a high-level representation into an IPv6 Extension Header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
+ header.set_next_header(self.next_header);
+ header.set_header_len(self.length);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // A Hop-by-Hop Option header with a PadN option of option data length 4.
+ static REPR_PACKET_PAD4: [u8; 8] = [0x6, 0x0, 0x1, 0x4, 0x0, 0x0, 0x0, 0x0];
+
+ // A Hop-by-Hop Option header with a PadN option of option data length 12.
+ static REPR_PACKET_PAD12: [u8; 16] = [
+ 0x06, 0x1, 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ #[test]
+ fn test_check_len() {
+ // zero byte buffer
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len()
+ );
+ // no length field
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&REPR_PACKET_PAD4[..1]).check_len()
+ );
+ // less than 8 bytes
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&REPR_PACKET_PAD4[..7]).check_len()
+ );
+ // valid
+ assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len());
+ // valid
+ assert_eq!(
+ Ok(()),
+ Header::new_unchecked(&REPR_PACKET_PAD12).check_len()
+ );
+ // length field value greater than number of bytes
+ let header: [u8; 8] = [0x06, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
+ assert_eq!(Err(Error), Header::new_unchecked(&header).check_len());
+ }
+
+ #[test]
+ fn test_header_deconstruct() {
+ let header = Header::new_unchecked(&REPR_PACKET_PAD4);
+ assert_eq!(header.next_header(), IpProtocol::Tcp);
+ assert_eq!(header.header_len(), 0);
+ assert_eq!(header.payload(), &REPR_PACKET_PAD4[2..]);
+
+ let header = Header::new_unchecked(&REPR_PACKET_PAD12);
+ assert_eq!(header.next_header(), IpProtocol::Tcp);
+ assert_eq!(header.header_len(), 1);
+ assert_eq!(header.payload(), &REPR_PACKET_PAD12[2..]);
+ }
+
+ #[test]
+ fn test_overlong() {
+ let mut bytes = vec![];
+ bytes.extend(&REPR_PACKET_PAD4[..]);
+ bytes.push(0);
+
+ assert_eq!(
+ Header::new_unchecked(&bytes).payload().len(),
+ REPR_PACKET_PAD4[2..].len()
+ );
+ assert_eq!(
+ Header::new_unchecked(&mut bytes).payload_mut().len(),
+ REPR_PACKET_PAD4[2..].len()
+ );
+
+ let mut bytes = vec![];
+ bytes.extend(&REPR_PACKET_PAD12[..]);
+ bytes.push(0);
+
+ assert_eq!(
+ Header::new_unchecked(&bytes).payload().len(),
+ REPR_PACKET_PAD12[2..].len()
+ );
+ assert_eq!(
+ Header::new_unchecked(&mut bytes).payload_mut().len(),
+ REPR_PACKET_PAD12[2..].len()
+ );
+ }
+
+ #[test]
+ fn test_header_len_overflow() {
+ let mut bytes = vec![];
+ bytes.extend(REPR_PACKET_PAD4);
+ let len = bytes.len() as u8;
+ Header::new_unchecked(&mut bytes).set_header_len(len + 1);
+
+ assert_eq!(Header::new_checked(&bytes).unwrap_err(), Error);
+
+ let mut bytes = vec![];
+ bytes.extend(REPR_PACKET_PAD12);
+ let len = bytes.len() as u8;
+ Header::new_unchecked(&mut bytes).set_header_len(len + 1);
+
+ assert_eq!(Header::new_checked(&bytes).unwrap_err(), Error);
+ }
+
+ #[test]
+ fn test_repr_parse_valid() {
+ let header = Header::new_unchecked(&REPR_PACKET_PAD4);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(
+ repr,
+ Repr {
+ next_header: IpProtocol::Tcp,
+ length: 0,
+ data: &REPR_PACKET_PAD4[2..]
+ }
+ );
+
+ let header = Header::new_unchecked(&REPR_PACKET_PAD12);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(
+ repr,
+ Repr {
+ next_header: IpProtocol::Tcp,
+ length: 1,
+ data: &REPR_PACKET_PAD12[2..]
+ }
+ );
+ }
+
+ #[test]
+ fn test_repr_emit() {
+ let repr = Repr {
+ next_header: IpProtocol::Tcp,
+ length: 0,
+ data: &REPR_PACKET_PAD4[2..],
+ };
+ let mut bytes = [0u8; 2];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..2]);
+
+ let repr = Repr {
+ next_header: IpProtocol::Tcp,
+ length: 1,
+ data: &REPR_PACKET_PAD12[2..],
+ };
+ let mut bytes = [0u8; 2];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..2]);
+ }
+}
diff --git a/src/wire/ipv6fragment.rs b/src/wire/ipv6fragment.rs
new file mode 100644
index 0000000..cf6b6d0
--- /dev/null
+++ b/src/wire/ipv6fragment.rs
@@ -0,0 +1,283 @@
+use super::{Error, Result};
+use core::fmt;
+
+use byteorder::{ByteOrder, NetworkEndian};
+
+/// A read/write wrapper around an IPv6 Fragment Header.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Header<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// Format of the Fragment Header
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Next Header | Reserved | Fragment Offset |Res|M|
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Identification |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// See https://tools.ietf.org/html/rfc8200#section-4.5 for details.
+//
+// **NOTE**: The fields start counting after the header length field.
+mod field {
+ use crate::wire::field::*;
+
+ // 16-bit field containing the fragment offset, reserved and more fragments values.
+ pub const FR_OF_M: Field = 0..2;
+ // 32-bit field identifying the fragmented packet
+ pub const IDENT: Field = 2..6;
+ /// 1 bit flag indicating if there are more fragments coming.
+ pub const M: usize = 1;
+}
+
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Create a raw octet buffer with an IPv6 Fragment Header structure.
+ pub const fn new_unchecked(buffer: T) -> Header<T> {
+ Header { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Header<T>> {
+ let header = Self::new_unchecked(buffer);
+ header.check_len()?;
+ Ok(header)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+ let len = data.len();
+
+ if len < field::IDENT.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the header, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the fragment offset field.
+ #[inline]
+ pub fn frag_offset(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::FR_OF_M]) >> 3
+ }
+
+ /// Return more fragment flag field.
+ #[inline]
+ pub fn more_frags(&self) -> bool {
+ let data = self.buffer.as_ref();
+ (data[field::M] & 0x1) == 1
+ }
+
+ /// Return the fragment identification value field.
+ #[inline]
+ pub fn ident(&self) -> u32 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u32(&data[field::IDENT])
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
+ /// Set reserved fields.
+ ///
+ /// Set 8-bit reserved field after the next header field.
+ /// Set 2-bit reserved field between fragment offset and more fragments.
+ #[inline]
+ pub fn clear_reserved(&mut self) {
+ let data = self.buffer.as_mut();
+ // Retain the higher order 5 bits and lower order 1 bit
+ data[field::M] &= 0xf9;
+ }
+
+ /// Set the fragment offset field.
+ #[inline]
+ pub fn set_frag_offset(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ // Retain the lower order 3 bits
+ let raw = ((value & 0x1fff) << 3) | ((data[field::M] & 0x7) as u16);
+ NetworkEndian::write_u16(&mut data[field::FR_OF_M], raw);
+ }
+
+ /// Set the more fragments flag field.
+ #[inline]
+ pub fn set_more_frags(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ // Retain the high order 7 bits
+ let raw = (data[field::M] & 0xfe) | (value as u8 & 0x1);
+ data[field::M] = raw;
+ }
+
+ /// Set the fragmentation identification field.
+ #[inline]
+ pub fn set_ident(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::IDENT], value);
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "IPv6 Fragment ({err})")?;
+ Ok(())
+ }
+ }
+ }
+}
+
+/// A high-level representation of an IPv6 Fragment header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr {
+ /// The offset of the data following this header, relative to the start of the Fragmentable
+ /// Part of the original packet.
+ pub frag_offset: u16,
+ /// When there are more fragments following this header
+ pub more_frags: bool,
+ /// The identification for every packet that is fragmented.
+ pub ident: u32,
+}
+
+impl Repr {
+ /// Parse an IPv6 Fragment Header and return a high-level representation.
+ pub fn parse<T>(header: &Header<&T>) -> Result<Repr>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ Ok(Repr {
+ frag_offset: header.frag_offset(),
+ more_frags: header.more_frags(),
+ ident: header.ident(),
+ })
+ }
+
+ /// Return the length, in bytes, of a header that will be emitted from this high-level
+ /// representation.
+ pub const fn buffer_len(&self) -> usize {
+ field::IDENT.end
+ }
+
+ /// Emit a high-level representation into an IPv6 Fragment Header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
+ header.clear_reserved();
+ header.set_frag_offset(self.frag_offset);
+ header.set_more_frags(self.more_frags);
+ header.set_ident(self.ident);
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "IPv6 Fragment offset={} more={} ident={}",
+ self.frag_offset, self.more_frags, self.ident
+ )
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // A Fragment Header with more fragments remaining
+ static BYTES_HEADER_MORE_FRAG: [u8; 6] = [0x0, 0x1, 0x0, 0x0, 0x30, 0x39];
+
+ // A Fragment Header with no more fragments remaining
+ static BYTES_HEADER_LAST_FRAG: [u8; 6] = [0xa, 0x0, 0x0, 0x1, 0x9, 0x32];
+
+ #[test]
+ fn test_check_len() {
+ // less than 6 bytes
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&BYTES_HEADER_MORE_FRAG[..5]).check_len()
+ );
+ // valid
+ assert_eq!(
+ Ok(()),
+ Header::new_unchecked(&BYTES_HEADER_MORE_FRAG).check_len()
+ );
+ }
+
+ #[test]
+ fn test_header_deconstruct() {
+ let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG);
+ assert_eq!(header.frag_offset(), 0);
+ assert!(header.more_frags());
+ assert_eq!(header.ident(), 12345);
+
+ let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG);
+ assert_eq!(header.frag_offset(), 320);
+ assert!(!header.more_frags());
+ assert_eq!(header.ident(), 67890);
+ }
+
+ #[test]
+ fn test_repr_parse_valid() {
+ let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(
+ repr,
+ Repr {
+ frag_offset: 0,
+ more_frags: true,
+ ident: 12345
+ }
+ );
+
+ let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(
+ repr,
+ Repr {
+ frag_offset: 320,
+ more_frags: false,
+ ident: 67890
+ }
+ );
+ }
+
+ #[test]
+ fn test_repr_emit() {
+ let repr = Repr {
+ frag_offset: 0,
+ more_frags: true,
+ ident: 12345,
+ };
+ let mut bytes = [0u8; 6];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+ assert_eq!(header.into_inner(), &BYTES_HEADER_MORE_FRAG[0..6]);
+
+ let repr = Repr {
+ frag_offset: 320,
+ more_frags: false,
+ ident: 67890,
+ };
+ let mut bytes = [0u8; 6];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+ assert_eq!(header.into_inner(), &BYTES_HEADER_LAST_FRAG[0..6]);
+ }
+
+ #[test]
+ fn test_buffer_len() {
+ let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG);
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr.buffer_len(), BYTES_HEADER_MORE_FRAG.len());
+ }
+}
diff --git a/src/wire/ipv6hbh.rs b/src/wire/ipv6hbh.rs
new file mode 100644
index 0000000..bc68300
--- /dev/null
+++ b/src/wire/ipv6hbh.rs
@@ -0,0 +1,176 @@
+use super::{Error, Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, Result};
+
+use heapless::Vec;
+
+/// A read/write wrapper around an IPv6 Hop-by-Hop Header buffer.
+pub struct Header<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Create a raw octet buffer with an IPv6 Hop-by-Hop Header structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Header { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let header = Self::new_unchecked(buffer);
+ header.check_len()?;
+ Ok(header)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ if self.buffer.as_ref().is_empty() {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consume the header, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> {
+ /// Return the options of the IPv6 Hop-by-Hop header.
+ pub fn options(&self) -> &'a [u8] {
+ self.buffer.as_ref()
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Header<&'a mut T> {
+ /// Return a mutable pointer to the options of the IPv6 Hop-by-Hop header.
+ pub fn options_mut(&mut self) -> &mut [u8] {
+ self.buffer.as_mut()
+ }
+}
+
+/// A high-level representation of an IPv6 Hop-by-Hop Header.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+ pub options: heapless::Vec<Ipv6OptionRepr<'a>, { crate::config::IPV6_HBH_MAX_OPTIONS }>,
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an IPv6 Hop-by-Hop Header and return a high-level representation.
+ pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ let mut options = Vec::new();
+
+ let iter = Ipv6OptionsIterator::new(header.options());
+
+ for option in iter {
+ let option = option?;
+
+ if let Err(e) = options.push(option) {
+ net_trace!("error when parsing hop-by-hop options: {}", e);
+ break;
+ }
+ }
+
+ Ok(Self { options })
+ }
+
+ /// Return the length, in bytes, of a header that will be emitted from this high-level
+ /// representation.
+ pub fn buffer_len(&self) -> usize {
+ self.options.iter().map(|o| o.buffer_len()).sum()
+ }
+
+ /// Emit a high-level representation into an IPv6 Hop-by-Hop Header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
+ let mut buffer = header.options_mut();
+
+ for opt in &self.options {
+ opt.emit(&mut Ipv6Option::new_unchecked(
+ &mut buffer[..opt.buffer_len()],
+ ));
+ buffer = &mut buffer[opt.buffer_len()..];
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::wire::Error;
+
+ // A Hop-by-Hop Option header with a PadN option of option data length 4.
+ static REPR_PACKET_PAD4: [u8; 6] = [0x1, 0x4, 0x0, 0x0, 0x0, 0x0];
+
+ // A Hop-by-Hop Option header with a PadN option of option data length 12.
+ static REPR_PACKET_PAD12: [u8; 14] = [
+ 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ #[test]
+ fn test_check_len() {
+ // zero byte buffer
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len()
+ );
+ // valid
+ assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len());
+ // valid
+ assert_eq!(
+ Ok(()),
+ Header::new_unchecked(&REPR_PACKET_PAD12).check_len()
+ );
+ }
+
+ #[test]
+ fn test_repr_parse_valid() {
+ let header = Header::new_unchecked(&REPR_PACKET_PAD4);
+ let repr = Repr::parse(&header).unwrap();
+
+ let mut options = Vec::new();
+ options.push(Ipv6OptionRepr::PadN(4)).unwrap();
+ assert_eq!(repr, Repr { options });
+
+ let header = Header::new_unchecked(&REPR_PACKET_PAD12);
+ let repr = Repr::parse(&header).unwrap();
+
+ let mut options = Vec::new();
+ options.push(Ipv6OptionRepr::PadN(12)).unwrap();
+ assert_eq!(repr, Repr { options });
+ }
+
+ #[test]
+ fn test_repr_emit() {
+ let mut options = Vec::new();
+ options.push(Ipv6OptionRepr::PadN(4)).unwrap();
+ let repr = Repr { options };
+
+ let mut bytes = [0u8; 6];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]);
+
+ let mut options = Vec::new();
+ options.push(Ipv6OptionRepr::PadN(12)).unwrap();
+ let repr = Repr { options };
+
+ let mut bytes = [0u8; 14];
+ let mut header = Header::new_unchecked(&mut bytes);
+ repr.emit(&mut header);
+
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..]);
+ }
+}
diff --git a/src/wire/ipv6option.rs b/src/wire/ipv6option.rs
new file mode 100644
index 0000000..dfbd6ac
--- /dev/null
+++ b/src/wire/ipv6option.rs
@@ -0,0 +1,611 @@
+use super::{Error, Result};
+#[cfg(feature = "proto-rpl")]
+use super::{RplHopByHopPacket, RplHopByHopRepr};
+
+use core::fmt;
+
+enum_with_unknown! {
+ /// IPv6 Extension Header Option Type
+ pub enum Type(u8) {
+ /// 1 byte of padding
+ Pad1 = 0,
+ /// Multiple bytes of padding
+ PadN = 1,
+ /// RPL Option
+ Rpl = 0x63,
+ }
+}
+
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Type::Pad1 => write!(f, "Pad1"),
+ Type::PadN => write!(f, "PadN"),
+ Type::Rpl => write!(f, "RPL"),
+ Type::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+enum_with_unknown! {
+ /// Action required when parsing the given IPv6 Extension
+ /// Header Option Type fails
+ pub enum FailureType(u8) {
+ /// Skip this option and continue processing the packet
+ Skip = 0b00000000,
+ /// Discard the containing packet
+ Discard = 0b01000000,
+ /// Discard the containing packet and notify the sender
+ DiscardSendAll = 0b10000000,
+ /// Discard the containing packet and only notify the sender
+ /// if the sender is a unicast address
+ DiscardSendUnicast = 0b11000000,
+ }
+}
+
+impl fmt::Display for FailureType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ FailureType::Skip => write!(f, "skip"),
+ FailureType::Discard => write!(f, "discard"),
+ FailureType::DiscardSendAll => write!(f, "discard and send error"),
+ FailureType::DiscardSendUnicast => write!(f, "discard and send error if unicast"),
+ FailureType::Unknown(id) => write!(f, "Unknown({id})"),
+ }
+ }
+}
+
+impl From<Type> for FailureType {
+ fn from(other: Type) -> FailureType {
+ let raw: u8 = other.into();
+ Self::from(raw & 0b11000000u8)
+ }
+}
+
+/// A read/write wrapper around an IPv6 Extension Header Option.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Ipv6Option<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// Format of Option
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+// | Option Type | Opt Data Len | Option Data
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+//
+//
+// See https://tools.ietf.org/html/rfc8200#section-4.2 for details.
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ // 8-bit identifier of the type of option.
+ pub const TYPE: usize = 0;
+ // 8-bit unsigned integer. Length of the DATA field of this option, in octets.
+ pub const LENGTH: usize = 1;
+ // Variable-length field. Option-Type-specific data.
+ pub const fn DATA(length: u8) -> Field {
+ 2..length as usize + 2
+ }
+}
+
+impl<T: AsRef<[u8]>> Ipv6Option<T> {
+ /// Create a raw octet buffer with an IPv6 Extension Header Option structure.
+ pub const fn new_unchecked(buffer: T) -> Ipv6Option<T> {
+ Ipv6Option { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Ipv6Option<T>> {
+ let opt = Self::new_unchecked(buffer);
+ opt.check_len()?;
+ Ok(opt)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_data_len].
+ ///
+ /// [set_data_len]: #method.set_data_len
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+ let len = data.len();
+
+ if len < field::LENGTH {
+ return Err(Error);
+ }
+
+ if self.option_type() == Type::Pad1 {
+ return Ok(());
+ }
+
+ if len == field::LENGTH {
+ return Err(Error);
+ }
+
+ let df = field::DATA(data[field::LENGTH]);
+
+ if len < df.end {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consume the ipv6 option, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the option type.
+ #[inline]
+ pub fn option_type(&self) -> Type {
+ let data = self.buffer.as_ref();
+ Type::from(data[field::TYPE])
+ }
+
+ /// Return the length of the data.
+ ///
+ /// # Panics
+ /// This function panics if this is an 1-byte padding option.
+ #[inline]
+ pub fn data_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::LENGTH]
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Ipv6Option<&'a T> {
+ /// Return the option data.
+ ///
+ /// # Panics
+ /// This function panics if this is an 1-byte padding option.
+ #[inline]
+ pub fn data(&self) -> &'a [u8] {
+ let len = self.data_len();
+ let data = self.buffer.as_ref();
+ &data[field::DATA(len)]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Ipv6Option<T> {
+ /// Set the option type.
+ #[inline]
+ pub fn set_option_type(&mut self, value: Type) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into();
+ }
+
+ /// Set the option data length.
+ ///
+ /// # Panics
+ /// This function panics if this is an 1-byte padding option.
+ #[inline]
+ pub fn set_data_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::LENGTH] = value;
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Ipv6Option<&'a mut T> {
+ /// Return a mutable pointer to the option data.
+ ///
+ /// # Panics
+ /// This function panics if this is an 1-byte padding option.
+ #[inline]
+ pub fn data_mut(&mut self) -> &mut [u8] {
+ let len = self.data_len();
+ let data = self.buffer.as_mut();
+ &mut data[field::DATA(len)]
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Ipv6Option<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "IPv6 Extension Option ({err})")?;
+ Ok(())
+ }
+ }
+ }
+}
+
+/// A high-level representation of an IPv6 Extension Header Option.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Repr<'a> {
+ Pad1,
+ PadN(u8),
+ #[cfg(feature = "proto-rpl")]
+ Rpl(RplHopByHopRepr),
+ Unknown {
+ type_: Type,
+ length: u8,
+ data: &'a [u8],
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an IPv6 Extension Header Option and return a high-level representation.
+ pub fn parse<T>(opt: &Ipv6Option<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ match opt.option_type() {
+ Type::Pad1 => Ok(Repr::Pad1),
+ Type::PadN => Ok(Repr::PadN(opt.data_len())),
+
+ #[cfg(feature = "proto-rpl")]
+ Type::Rpl => Ok(Repr::Rpl(RplHopByHopRepr::parse(
+ &RplHopByHopPacket::new_checked(opt.data())?,
+ ))),
+ #[cfg(not(feature = "proto-rpl"))]
+ Type::Rpl => Ok(Repr::Unknown {
+ type_: Type::Rpl,
+ length: opt.data_len(),
+ data: opt.data(),
+ }),
+
+ unknown_type @ Type::Unknown(_) => Ok(Repr::Unknown {
+ type_: unknown_type,
+ length: opt.data_len(),
+ data: opt.data(),
+ }),
+ }
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ match *self {
+ Repr::Pad1 => 1,
+ Repr::PadN(length) => field::DATA(length).end,
+ #[cfg(feature = "proto-rpl")]
+ Repr::Rpl(opt) => field::DATA(opt.buffer_len() as u8).end,
+ Repr::Unknown { length, .. } => field::DATA(length).end,
+ }
+ }
+
+ /// Emit a high-level representation into an IPv6 Extension Header Option.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, opt: &mut Ipv6Option<&'a mut T>) {
+ match *self {
+ Repr::Pad1 => opt.set_option_type(Type::Pad1),
+ Repr::PadN(len) => {
+ opt.set_option_type(Type::PadN);
+ opt.set_data_len(len);
+ // Ensure all padding bytes are set to zero.
+ for x in opt.data_mut().iter_mut() {
+ *x = 0
+ }
+ }
+ #[cfg(feature = "proto-rpl")]
+ Repr::Rpl(rpl) => {
+ opt.set_option_type(Type::Rpl);
+ opt.set_data_len(4);
+ rpl.emit(&mut crate::wire::RplHopByHopPacket::new_unchecked(
+ opt.data_mut(),
+ ));
+ }
+ Repr::Unknown {
+ type_,
+ length,
+ data,
+ } => {
+ opt.set_option_type(type_);
+ opt.set_data_len(length);
+ opt.data_mut().copy_from_slice(&data[..length as usize]);
+ }
+ }
+ }
+}
+
+/// A iterator for IPv6 options.
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Ipv6OptionsIterator<'a> {
+ pos: usize,
+ length: usize,
+ data: &'a [u8],
+ hit_error: bool,
+}
+
+impl<'a> Ipv6OptionsIterator<'a> {
+ /// Create a new `Ipv6OptionsIterator`, used to iterate over the
+ /// options contained in a IPv6 Extension Header (e.g. the Hop-by-Hop
+ /// header).
+ pub fn new(data: &'a [u8]) -> Ipv6OptionsIterator<'a> {
+ let length = data.len();
+ Ipv6OptionsIterator {
+ pos: 0,
+ hit_error: false,
+ length,
+ data,
+ }
+ }
+}
+
+impl<'a> Iterator for Ipv6OptionsIterator<'a> {
+ type Item = Result<Repr<'a>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.pos < self.length && !self.hit_error {
+ // If we still have data to parse and we have not previously
+ // hit an error, attempt to parse the next option.
+ match Ipv6Option::new_checked(&self.data[self.pos..]) {
+ Ok(hdr) => match Repr::parse(&hdr) {
+ Ok(repr) => {
+ self.pos += repr.buffer_len();
+ Some(Ok(repr))
+ }
+ Err(e) => {
+ self.hit_error = true;
+ Some(Err(e))
+ }
+ },
+ Err(e) => {
+ self.hit_error = true;
+ Some(Err(e))
+ }
+ }
+ } else {
+ // If we failed to parse a previous option or hit the end of the
+ // buffer, we do not continue to iterate.
+ None
+ }
+ }
+}
+
+impl<'a> fmt::Display for Repr<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "IPv6 Option ")?;
+ match *self {
+ Repr::Pad1 => write!(f, "{} ", Type::Pad1),
+ Repr::PadN(len) => write!(f, "{} length={} ", Type::PadN, len),
+ #[cfg(feature = "proto-rpl")]
+ Repr::Rpl(rpl) => write!(f, "{} {rpl}", Type::Rpl),
+ Repr::Unknown { type_, length, .. } => write!(f, "{type_} length={length} "),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ static IPV6OPTION_BYTES_PAD1: [u8; 1] = [0x0];
+ static IPV6OPTION_BYTES_PADN: [u8; 3] = [0x1, 0x1, 0x0];
+ static IPV6OPTION_BYTES_UNKNOWN: [u8; 5] = [0xff, 0x3, 0x0, 0x0, 0x0];
+ #[cfg(feature = "proto-rpl")]
+ static IPV6OPTION_BYTES_RPL: [u8; 6] = [0x63, 0x04, 0x00, 0x1e, 0x08, 0x00];
+
+ #[test]
+ fn test_check_len() {
+ let bytes = [0u8];
+ // zero byte buffer
+ assert_eq!(
+ Err(Error),
+ Ipv6Option::new_unchecked(&bytes[..0]).check_len()
+ );
+ // pad1
+ assert_eq!(
+ Ok(()),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1).check_len()
+ );
+
+ // padn with truncated data
+ assert_eq!(
+ Err(Error),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN[..2]).check_len()
+ );
+ // padn
+ assert_eq!(
+ Ok(()),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN).check_len()
+ );
+
+ // unknown option type with truncated data
+ assert_eq!(
+ Err(Error),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..4]).check_len()
+ );
+ assert_eq!(
+ Err(Error),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..1]).check_len()
+ );
+ // unknown type
+ assert_eq!(
+ Ok(()),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN).check_len()
+ );
+
+ #[cfg(feature = "proto-rpl")]
+ {
+ assert_eq!(
+ Ok(()),
+ Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL).check_len()
+ );
+ }
+ }
+
+ #[test]
+ #[should_panic(expected = "index out of bounds")]
+ fn test_data_len() {
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
+ opt.data_len();
+ }
+
+ #[test]
+ fn test_option_deconstruct() {
+ // one octet of padding
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
+ assert_eq!(opt.option_type(), Type::Pad1);
+
+ // two octets of padding
+ let bytes: [u8; 2] = [0x1, 0x0];
+ let opt = Ipv6Option::new_unchecked(&bytes);
+ assert_eq!(opt.option_type(), Type::PadN);
+ assert_eq!(opt.data_len(), 0);
+
+ // three octets of padding
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN);
+ assert_eq!(opt.option_type(), Type::PadN);
+ assert_eq!(opt.data_len(), 1);
+ assert_eq!(opt.data(), &[0]);
+
+ // extra bytes in buffer
+ let bytes: [u8; 10] = [0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff];
+ let opt = Ipv6Option::new_unchecked(&bytes);
+ assert_eq!(opt.option_type(), Type::PadN);
+ assert_eq!(opt.data_len(), 7);
+ assert_eq!(opt.data(), &[0, 0, 0, 0, 0, 0, 0]);
+
+ // unrecognized option
+ let bytes: [u8; 1] = [0xff];
+ let opt = Ipv6Option::new_unchecked(&bytes);
+ assert_eq!(opt.option_type(), Type::Unknown(255));
+
+ // unrecognized option without length and data
+ assert_eq!(Ipv6Option::new_checked(&bytes), Err(Error));
+
+ #[cfg(feature = "proto-rpl")]
+ {
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
+ assert_eq!(opt.option_type(), Type::Rpl);
+ assert_eq!(opt.data_len(), 4);
+ assert_eq!(opt.data(), &[0x00, 0x1e, 0x08, 0x00]);
+ }
+ }
+
+ #[test]
+ fn test_option_parse() {
+ // one octet of padding
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1);
+ let pad1 = Repr::parse(&opt).unwrap();
+ assert_eq!(pad1, Repr::Pad1);
+ assert_eq!(pad1.buffer_len(), 1);
+
+ // two or more octets of padding
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN);
+ let padn = Repr::parse(&opt).unwrap();
+ assert_eq!(padn, Repr::PadN(1));
+ assert_eq!(padn.buffer_len(), 3);
+
+ // unrecognized option type
+ let data = [0u8; 3];
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN);
+ let unknown = Repr::parse(&opt).unwrap();
+ assert_eq!(
+ unknown,
+ Repr::Unknown {
+ type_: Type::Unknown(255),
+ length: 3,
+ data: &data
+ }
+ );
+
+ #[cfg(feature = "proto-rpl")]
+ {
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
+ let rpl = Repr::parse(&opt).unwrap();
+
+ assert_eq!(
+ rpl,
+ Repr::Rpl(crate::wire::RplHopByHopRepr {
+ down: false,
+ rank_error: false,
+ forwarding_error: false,
+ instance_id: crate::wire::RplInstanceId::from(0x1e),
+ sender_rank: 0x0800,
+ })
+ );
+ }
+ }
+
+ #[test]
+ fn test_option_emit() {
+ let repr = Repr::Pad1;
+ let mut bytes = [255u8; 1]; // don't assume bytes are initialized to zero
+ let mut opt = Ipv6Option::new_unchecked(&mut bytes);
+ repr.emit(&mut opt);
+ assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PAD1);
+
+ let repr = Repr::PadN(1);
+ let mut bytes = [255u8; 3]; // don't assume bytes are initialized to zero
+ let mut opt = Ipv6Option::new_unchecked(&mut bytes);
+ repr.emit(&mut opt);
+ assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PADN);
+
+ let data = [0u8; 3];
+ let repr = Repr::Unknown {
+ type_: Type::Unknown(255),
+ length: 3,
+ data: &data,
+ };
+ let mut bytes = [254u8; 5]; // don't assume bytes are initialized to zero
+ let mut opt = Ipv6Option::new_unchecked(&mut bytes);
+ repr.emit(&mut opt);
+ assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_UNKNOWN);
+
+ #[cfg(feature = "proto-rpl")]
+ {
+ let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL);
+ let rpl = Repr::parse(&opt).unwrap();
+ let mut bytes = [0u8; 6];
+ rpl.emit(&mut Ipv6Option::new_unchecked(&mut bytes));
+
+ assert_eq!(&bytes, &IPV6OPTION_BYTES_RPL);
+ }
+ }
+
+ #[test]
+ fn test_failure_type() {
+ let mut failure_type: FailureType = Type::Pad1.into();
+ assert_eq!(failure_type, FailureType::Skip);
+ failure_type = Type::PadN.into();
+ assert_eq!(failure_type, FailureType::Skip);
+ failure_type = Type::Unknown(0b01000001).into();
+ assert_eq!(failure_type, FailureType::Discard);
+ failure_type = Type::Unknown(0b10100000).into();
+ assert_eq!(failure_type, FailureType::DiscardSendAll);
+ failure_type = Type::Unknown(0b11000100).into();
+ assert_eq!(failure_type, FailureType::DiscardSendUnicast);
+ }
+
+ #[test]
+ fn test_options_iter() {
+ let options = [
+ 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x11, 0x00, 0x01,
+ 0x08, 0x00,
+ ];
+
+ let iterator = Ipv6OptionsIterator::new(&options);
+ for (i, opt) in iterator.enumerate() {
+ match (i, opt) {
+ (0, Ok(Repr::Pad1)) => continue,
+ (1, Ok(Repr::PadN(1))) => continue,
+ (2, Ok(Repr::PadN(2))) => continue,
+ (3, Ok(Repr::PadN(0))) => continue,
+ (4, Ok(Repr::Pad1)) => continue,
+ (
+ 5,
+ Ok(Repr::Unknown {
+ type_: Type::Unknown(0x11),
+ length: 0,
+ ..
+ }),
+ ) => continue,
+ (6, Err(Error)) => continue,
+ (i, res) => panic!("Unexpected option `{res:?}` at index {i}"),
+ }
+ }
+ }
+}
diff --git a/src/wire/ipv6routing.rs b/src/wire/ipv6routing.rs
new file mode 100644
index 0000000..a4943b7
--- /dev/null
+++ b/src/wire/ipv6routing.rs
@@ -0,0 +1,606 @@
+use super::{Error, Result};
+use core::fmt;
+
+use crate::wire::Ipv6Address as Address;
+
+enum_with_unknown! {
+ /// IPv6 Extension Routing Header Routing Type
+ pub enum Type(u8) {
+ /// Source Route (DEPRECATED)
+ ///
+ /// See https://tools.ietf.org/html/rfc5095 for details.
+ Type0 = 0,
+ /// Nimrod (DEPRECATED 2009-05-06)
+ Nimrod = 1,
+ /// Type 2 Routing Header for Mobile IPv6
+ ///
+ /// See https://tools.ietf.org/html/rfc6275#section-6.4 for details.
+ Type2 = 2,
+ /// RPL Source Routing Header
+ ///
+ /// See https://tools.ietf.org/html/rfc6554 for details.
+ Rpl = 3,
+ /// RFC3692-style Experiment 1
+ ///
+ /// See https://tools.ietf.org/html/rfc4727 for details.
+ Experiment1 = 253,
+ /// RFC3692-style Experiment 2
+ ///
+ /// See https://tools.ietf.org/html/rfc4727 for details.
+ Experiment2 = 254,
+ /// Reserved for future use
+ Reserved = 252
+ }
+}
+
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Type::Type0 => write!(f, "Type0"),
+ Type::Nimrod => write!(f, "Nimrod"),
+ Type::Type2 => write!(f, "Type2"),
+ Type::Rpl => write!(f, "Rpl"),
+ Type::Experiment1 => write!(f, "Experiment1"),
+ Type::Experiment2 => write!(f, "Experiment2"),
+ Type::Reserved => write!(f, "Reserved"),
+ Type::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+/// A read/write wrapper around an IPv6 Routing Header buffer.
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Header<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// Format of the Routing Header
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Next Header | Hdr Ext Len | Routing Type | Segments Left |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | |
+// . .
+// . type-specific data .
+// . .
+// | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//
+// See https://tools.ietf.org/html/rfc8200#section-4.4 for details.
+//
+// **NOTE**: The fields start counting after the header length field.
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ // Minimum size of the header.
+ pub const MIN_HEADER_SIZE: usize = 2;
+
+ // 8-bit identifier of a particular Routing header variant.
+ pub const TYPE: usize = 0;
+ // 8-bit unsigned integer. The number of route segments remaining.
+ pub const SEG_LEFT: usize = 1;
+
+ // The Type 2 Routing Header has the following format:
+ //
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Next Header | Hdr Ext Len=2 | Routing Type=2|Segments Left=1|
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Reserved |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // + +
+ // | |
+ // + Home Address +
+ // | |
+ // + +
+ // | |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // 16-byte field containing the home address of the destination mobile node.
+ pub const HOME_ADDRESS: Field = 6..22;
+
+ // The RPL Source Routing Header has the following format:
+ //
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Next Header | Hdr Ext Len | Routing Type | Segments Left |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | CmprI | CmprE | Pad | Reserved |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // . .
+ // . Addresses[1..n] .
+ // . .
+ // | |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // 8-bit field containing the CmprI and CmprE values.
+ pub const CMPR: usize = 2;
+ // 8-bit field containing the Pad value.
+ pub const PAD: usize = 3;
+ // Variable length field containing addresses
+ pub const ADDRESSES: usize = 6;
+}
+
+/// Core getter methods relevant to any routing type.
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Create a raw octet buffer with an IPv6 Routing Header structure.
+ pub const fn new_unchecked(buffer: T) -> Header<T> {
+ Header { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Header<T>> {
+ let header = Self::new_unchecked(buffer);
+ header.check_len()?;
+ Ok(header)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::MIN_HEADER_SIZE {
+ return Err(Error);
+ }
+
+ match self.routing_type() {
+ Type::Type2 if len < field::HOME_ADDRESS.end => return Err(Error),
+ Type::Rpl if len < field::ADDRESSES => return Err(Error),
+ _ => (),
+ }
+
+ Ok(())
+ }
+
+ /// Consume the header, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the routing type field.
+ #[inline]
+ pub fn routing_type(&self) -> Type {
+ let data = self.buffer.as_ref();
+ Type::from(data[field::TYPE])
+ }
+
+ /// Return the segments left field.
+ #[inline]
+ pub fn segments_left(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::SEG_LEFT]
+ }
+}
+
+/// Getter methods for the Type 2 Routing Header routing type.
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Return the IPv6 Home Address
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the Type2 Routing Header routing type.
+ pub fn home_address(&self) -> Address {
+ let data = self.buffer.as_ref();
+ Address::from_bytes(&data[field::HOME_ADDRESS])
+ }
+}
+
+/// Getter methods for the RPL Source Routing Header routing type.
+impl<T: AsRef<[u8]>> Header<T> {
+ /// Return the number of prefix octets elided from addresses[1..n-1].
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn cmpr_i(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::CMPR] >> 4
+ }
+
+ /// Return the number of prefix octets elided from the last address (`addresses[n]`).
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn cmpr_e(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::CMPR] & 0xf
+ }
+
+ /// Return the number of octets used for padding after `addresses[n]`.
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn pad(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::PAD] >> 4
+ }
+
+ /// Return the address vector in bytes
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn addresses(&self) -> &[u8] {
+ let data = self.buffer.as_ref();
+ &data[field::ADDRESSES..]
+ }
+}
+
+/// Core setter methods relevant to any routing type.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
+ /// Set the routing type.
+ #[inline]
+ pub fn set_routing_type(&mut self, value: Type) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into();
+ }
+
+ /// Set the segments left field.
+ #[inline]
+ pub fn set_segments_left(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::SEG_LEFT] = value;
+ }
+
+ /// Initialize reserved fields to 0.
+ ///
+ /// # Panics
+ /// This function may panic if the routing type is not set.
+ #[inline]
+ pub fn clear_reserved(&mut self) {
+ let routing_type = self.routing_type();
+ let data = self.buffer.as_mut();
+
+ match routing_type {
+ Type::Type2 => {
+ data[4] = 0;
+ data[5] = 0;
+ data[6] = 0;
+ data[7] = 0;
+ }
+ Type::Rpl => {
+ // Retain the higher order 4 bits of the padding field
+ data[field::PAD] &= 0xF0;
+ data[4] = 0;
+ data[5] = 0;
+ }
+
+ _ => panic!("Unrecognized routing type when clearing reserved fields."),
+ }
+ }
+}
+
+/// Setter methods for the RPL Source Routing Header routing type.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
+ /// Set the Ipv6 Home Address
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the Type 2 Routing Header routing type.
+ pub fn set_home_address(&mut self, value: Address) {
+ let data = self.buffer.as_mut();
+ data[field::HOME_ADDRESS].copy_from_slice(value.as_bytes());
+ }
+}
+
+/// Setter methods for the RPL Source Routing Header routing type.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
+ /// Set the number of prefix octets elided from addresses[1..n-1].
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn set_cmpr_i(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ let raw = (value << 4) | (data[field::CMPR] & 0xF);
+ data[field::CMPR] = raw;
+ }
+
+ /// Set the number of prefix octets elided from the last address (`addresses[n]`).
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn set_cmpr_e(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ let raw = (value & 0xF) | (data[field::CMPR] & 0xF0);
+ data[field::CMPR] = raw;
+ }
+
+ /// Set the number of octets used for padding after `addresses[n]`.
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn set_pad(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::PAD] = value << 4;
+ }
+
+ /// Set address data
+ ///
+ /// # Panics
+ /// This function may panic if this header is not the RPL Source Routing Header routing type.
+ pub fn set_addresses(&mut self, value: &[u8]) {
+ let data = self.buffer.as_mut();
+ let addresses = &mut data[field::ADDRESSES..];
+ addresses.copy_from_slice(value);
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "IPv6 Routing ({err})")?;
+ Ok(())
+ }
+ }
+ }
+}
+
+/// A high-level representation of an IPv6 Routing Header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Repr<'a> {
+ Type2 {
+ /// Number of route segments remaining.
+ segments_left: u8,
+ /// The home address of the destination mobile node.
+ home_address: Address,
+ },
+ Rpl {
+ /// Number of route segments remaining.
+ segments_left: u8,
+ /// Number of prefix octets from each segment, except the last segment, that are elided.
+ cmpr_i: u8,
+ /// Number of prefix octets from the last segment that are elided.
+ cmpr_e: u8,
+ /// Number of octets that are used for padding after `address[n]` at the end of the
+ /// RPL Source Route Header.
+ pad: u8,
+ /// Vector of addresses, numbered 1 to `n`.
+ addresses: &'a [u8],
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an IPv6 Routing Header and return a high-level representation.
+ pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ match header.routing_type() {
+ Type::Type2 => Ok(Repr::Type2 {
+ segments_left: header.segments_left(),
+ home_address: header.home_address(),
+ }),
+ Type::Rpl => Ok(Repr::Rpl {
+ segments_left: header.segments_left(),
+ cmpr_i: header.cmpr_i(),
+ cmpr_e: header.cmpr_e(),
+ pad: header.pad(),
+ addresses: header.addresses(),
+ }),
+
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length, in bytes, of a header that will be emitted from this high-level
+ /// representation.
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ // Routing Type + Segments Left + Reserved + Home Address
+ Repr::Type2 { home_address, .. } => 2 + 4 + home_address.as_bytes().len(),
+ Repr::Rpl { addresses, .. } => 2 + 4 + addresses.len(),
+ }
+ }
+
+ /// Emit a high-level representation into an IPv6 Routing Header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
+ match *self {
+ Repr::Type2 {
+ segments_left,
+ home_address,
+ } => {
+ header.set_routing_type(Type::Type2);
+ header.set_segments_left(segments_left);
+ header.clear_reserved();
+ header.set_home_address(home_address);
+ }
+ Repr::Rpl {
+ segments_left,
+ cmpr_i,
+ cmpr_e,
+ pad,
+ addresses,
+ } => {
+ header.set_routing_type(Type::Rpl);
+ header.set_segments_left(segments_left);
+ header.set_cmpr_i(cmpr_i);
+ header.set_cmpr_e(cmpr_e);
+ header.set_pad(pad);
+ header.clear_reserved();
+ header.set_addresses(addresses);
+ }
+ }
+ }
+}
+
+impl<'a> fmt::Display for Repr<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Repr::Type2 {
+ segments_left,
+ home_address,
+ } => {
+ write!(
+ f,
+ "IPv6 Routing type={} seg_left={} home_address={}",
+ Type::Type2,
+ segments_left,
+ home_address
+ )
+ }
+ Repr::Rpl {
+ segments_left,
+ cmpr_i,
+ cmpr_e,
+ pad,
+ ..
+ } => {
+ write!(
+ f,
+ "IPv6 Routing type={} seg_left={} cmpr_i={} cmpr_e={} pad={}",
+ Type::Rpl,
+ segments_left,
+ cmpr_i,
+ cmpr_e,
+ pad
+ )
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // A Type 2 Routing Header
+ static BYTES_TYPE2: [u8; 22] = [
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ // A representation of a Type 2 Routing header
+ static REPR_TYPE2: Repr = Repr::Type2 {
+ segments_left: 1,
+ home_address: Address::LOOPBACK,
+ };
+
+ // A Source Routing Header with full IPv6 addresses in bytes
+ static BYTES_SRH_FULL: [u8; 38] = [
+ 0x3, 0x2, 0x0, 0x0, 0x0, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x3, 0x1,
+ ];
+
+ // A representation of a Source Routing Header with full IPv6 addresses
+ static REPR_SRH_FULL: Repr = Repr::Rpl {
+ segments_left: 2,
+ cmpr_i: 0,
+ cmpr_e: 0,
+ pad: 0,
+ addresses: &[
+ 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1,
+ ],
+ };
+
+ // A Source Routing Header with elided IPv6 addresses in bytes
+ static BYTES_SRH_ELIDED: [u8; 14] = [
+ 0x3, 0x2, 0xfe, 0x50, 0x0, 0x0, 0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ // A representation of a Source Routing Header with elided IPv6 addresses
+ static REPR_SRH_ELIDED: Repr = Repr::Rpl {
+ segments_left: 2,
+ cmpr_i: 15,
+ cmpr_e: 14,
+ pad: 5,
+ addresses: &[0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0],
+ };
+
+ #[test]
+ fn test_check_len() {
+ // less than min header size
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&BYTES_TYPE2[..3]).check_len()
+ );
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&BYTES_SRH_FULL[..3]).check_len()
+ );
+ assert_eq!(
+ Err(Error),
+ Header::new_unchecked(&BYTES_SRH_ELIDED[..3]).check_len()
+ );
+ // valid
+ assert_eq!(Ok(()), Header::new_unchecked(&BYTES_TYPE2[..]).check_len());
+ assert_eq!(
+ Ok(()),
+ Header::new_unchecked(&BYTES_SRH_FULL[..]).check_len()
+ );
+ assert_eq!(
+ Ok(()),
+ Header::new_unchecked(&BYTES_SRH_ELIDED[..]).check_len()
+ );
+ }
+
+ #[test]
+ fn test_header_deconstruct() {
+ let header = Header::new_unchecked(&BYTES_TYPE2[..]);
+ assert_eq!(header.routing_type(), Type::Type2);
+ assert_eq!(header.segments_left(), 1);
+ assert_eq!(header.home_address(), Address::LOOPBACK);
+
+ let header = Header::new_unchecked(&BYTES_SRH_FULL[..]);
+ assert_eq!(header.routing_type(), Type::Rpl);
+ assert_eq!(header.segments_left(), 2);
+ assert_eq!(header.addresses(), &BYTES_SRH_FULL[6..]);
+
+ let header = Header::new_unchecked(&BYTES_SRH_ELIDED[..]);
+ assert_eq!(header.routing_type(), Type::Rpl);
+ assert_eq!(header.segments_left(), 2);
+ assert_eq!(header.addresses(), &BYTES_SRH_ELIDED[6..]);
+ }
+
+ #[test]
+ fn test_repr_parse_valid() {
+ let header = Header::new_checked(&BYTES_TYPE2[..]).unwrap();
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr, REPR_TYPE2);
+
+ let header = Header::new_checked(&BYTES_SRH_FULL[..]).unwrap();
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr, REPR_SRH_FULL);
+
+ let header = Header::new_checked(&BYTES_SRH_ELIDED[..]).unwrap();
+ let repr = Repr::parse(&header).unwrap();
+ assert_eq!(repr, REPR_SRH_ELIDED);
+ }
+
+ #[test]
+ fn test_repr_emit() {
+ let mut bytes = [0u8; 22];
+ let mut header = Header::new_unchecked(&mut bytes[..]);
+ REPR_TYPE2.emit(&mut header);
+ assert_eq!(header.into_inner(), &BYTES_TYPE2[..]);
+
+ let mut bytes = [0u8; 38];
+ let mut header = Header::new_unchecked(&mut bytes[..]);
+ REPR_SRH_FULL.emit(&mut header);
+ assert_eq!(header.into_inner(), &BYTES_SRH_FULL[..]);
+
+ let mut bytes = [0u8; 14];
+ let mut header = Header::new_unchecked(&mut bytes[..]);
+ REPR_SRH_ELIDED.emit(&mut header);
+ assert_eq!(header.into_inner(), &BYTES_SRH_ELIDED[..]);
+ }
+
+ #[test]
+ fn test_buffer_len() {
+ assert_eq!(REPR_TYPE2.buffer_len(), 22);
+ assert_eq!(REPR_SRH_FULL.buffer_len(), 38);
+ assert_eq!(REPR_SRH_ELIDED.buffer_len(), 14);
+ }
+}
diff --git a/src/wire/mld.rs b/src/wire/mld.rs
new file mode 100644
index 0000000..18872b5
--- /dev/null
+++ b/src/wire/mld.rs
@@ -0,0 +1,578 @@
+// Packet implementation for the Multicast Listener Discovery
+// protocol. See [RFC 3810] and [RFC 2710].
+//
+// [RFC 3810]: https://tools.ietf.org/html/rfc3810
+// [RFC 2710]: https://tools.ietf.org/html/rfc2710
+
+use byteorder::{ByteOrder, NetworkEndian};
+
+use super::{Error, Result};
+use crate::wire::icmpv6::{field, Message, Packet};
+use crate::wire::Ipv6Address;
+
+enum_with_unknown! {
+ /// MLDv2 Multicast Listener Report Record Type. See [RFC 3810 § 5.2.12] for
+ /// more details.
+ ///
+ /// [RFC 3810 § 5.2.12]: https://tools.ietf.org/html/rfc3010#section-5.2.12
+ pub enum RecordType(u8) {
+ /// Interface has a filter mode of INCLUDE for the specified multicast address.
+ ModeIsInclude = 0x01,
+ /// Interface has a filter mode of EXCLUDE for the specified multicast address.
+ ModeIsExclude = 0x02,
+ /// Interface has changed to a filter mode of INCLUDE for the specified
+ /// multicast address.
+ ChangeToInclude = 0x03,
+ /// Interface has changed to a filter mode of EXCLUDE for the specified
+ /// multicast address.
+ ChangeToExclude = 0x04,
+ /// Interface wishes to listen to the sources in the specified list.
+ AllowNewSources = 0x05,
+ /// Interface no longer wishes to listen to the sources in the specified list.
+ BlockOldSources = 0x06
+ }
+}
+
+/// Getters for the Multicast Listener Query message header.
+/// See [RFC 3810 § 5.1].
+///
+/// [RFC 3810 § 5.1]: https://tools.ietf.org/html/rfc3010#section-5.1
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the maximum response code field.
+ #[inline]
+ pub fn max_resp_code(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::MAX_RESP_CODE])
+ }
+
+ /// Return the address being queried.
+ #[inline]
+ pub fn mcast_addr(&self) -> Ipv6Address {
+ let data = self.buffer.as_ref();
+ Ipv6Address::from_bytes(&data[field::QUERY_MCAST_ADDR])
+ }
+
+ /// Return the Suppress Router-Side Processing flag.
+ #[inline]
+ pub fn s_flag(&self) -> bool {
+ let data = self.buffer.as_ref();
+ (data[field::SQRV] & 0x08) != 0
+ }
+
+ /// Return the Querier's Robustness Variable.
+ #[inline]
+ pub fn qrv(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::SQRV] & 0x7
+ }
+
+ /// Return the Querier's Query Interval Code.
+ #[inline]
+ pub fn qqic(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::QQIC]
+ }
+
+ /// Return number of sources.
+ #[inline]
+ pub fn num_srcs(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::QUERY_NUM_SRCS])
+ }
+}
+
+/// Getters for the Multicast Listener Report message header.
+/// See [RFC 3810 § 5.2].
+///
+/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the number of Multicast Address Records.
+ #[inline]
+ pub fn nr_mcast_addr_rcrds(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::NR_MCAST_RCRDS])
+ }
+}
+
+/// Setters for the Multicast Listener Query message header.
+/// See [RFC 3810 § 5.1].
+///
+/// [RFC 3810 § 5.1]: https://tools.ietf.org/html/rfc3010#section-5.1
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the maximum response code field.
+ #[inline]
+ pub fn set_max_resp_code(&mut self, code: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::MAX_RESP_CODE], code);
+ }
+
+ /// Set the address being queried.
+ #[inline]
+ pub fn set_mcast_addr(&mut self, addr: Ipv6Address) {
+ let data = self.buffer.as_mut();
+ data[field::QUERY_MCAST_ADDR].copy_from_slice(addr.as_bytes());
+ }
+
+ /// Set the Suppress Router-Side Processing flag.
+ #[inline]
+ pub fn set_s_flag(&mut self) {
+ let data = self.buffer.as_mut();
+ let current = data[field::SQRV];
+ data[field::SQRV] = 0x8 | (current & 0x7);
+ }
+
+ /// Clear the Suppress Router-Side Processing flag.
+ #[inline]
+ pub fn clear_s_flag(&mut self) {
+ let data = self.buffer.as_mut();
+ data[field::SQRV] &= 0x7;
+ }
+
+ /// Set the Querier's Robustness Variable.
+ #[inline]
+ pub fn set_qrv(&mut self, value: u8) {
+ assert!(value < 8);
+ let data = self.buffer.as_mut();
+ data[field::SQRV] = (data[field::SQRV] & 0x8) | value & 0x7;
+ }
+
+ /// Set the Querier's Query Interval Code.
+ #[inline]
+ pub fn set_qqic(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::QQIC] = value;
+ }
+
+ /// Set number of sources.
+ #[inline]
+ pub fn set_num_srcs(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::QUERY_NUM_SRCS], value);
+ }
+}
+
+/// Setters for the Multicast Listener Report message header.
+/// See [RFC 3810 § 5.2].
+///
+/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the number of Multicast Address Records.
+ #[inline]
+ pub fn set_nr_mcast_addr_rcrds(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::NR_MCAST_RCRDS], value)
+ }
+}
+
+/// A read/write wrapper around an MLDv2 Listener Report Message Address Record.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AddressRecord<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> AddressRecord<T> {
+ /// Imbue a raw octet buffer with a Address Record structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Self { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error::Truncated)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::RECORD_MCAST_ADDR.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+}
+
+/// Getters for a MLDv2 Listener Report Message Address Record.
+/// See [RFC 3810 § 5.2].
+///
+/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2
+impl<T: AsRef<[u8]>> AddressRecord<T> {
+ /// Return the record type for the given sources.
+ #[inline]
+ pub fn record_type(&self) -> RecordType {
+ let data = self.buffer.as_ref();
+ RecordType::from(data[field::RECORD_TYPE])
+ }
+
+ /// Return the length of the auxiliary data.
+ #[inline]
+ pub fn aux_data_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::AUX_DATA_LEN]
+ }
+
+ /// Return the number of sources field.
+ #[inline]
+ pub fn num_srcs(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::RECORD_NUM_SRCS])
+ }
+
+ /// Return the multicast address field.
+ #[inline]
+ pub fn mcast_addr(&self) -> Ipv6Address {
+ let data = self.buffer.as_ref();
+ Ipv6Address::from_bytes(&data[field::RECORD_MCAST_ADDR])
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> AddressRecord<&'a T> {
+ /// Return a pointer to the address records.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let data = self.buffer.as_ref();
+ &data[field::RECORD_MCAST_ADDR.end..]
+ }
+}
+
+/// Setters for a MLDv2 Listener Report Message Address Record.
+/// See [RFC 3810 § 5.2].
+///
+/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2
+impl<T: AsMut<[u8]> + AsRef<[u8]>> AddressRecord<T> {
+ /// Return the record type for the given sources.
+ #[inline]
+ pub fn set_record_type(&mut self, rty: RecordType) {
+ let data = self.buffer.as_mut();
+ data[field::RECORD_TYPE] = rty.into();
+ }
+
+ /// Return the length of the auxiliary data.
+ #[inline]
+ pub fn set_aux_data_len(&mut self, len: u8) {
+ let data = self.buffer.as_mut();
+ data[field::AUX_DATA_LEN] = len;
+ }
+
+ /// Return the number of sources field.
+ #[inline]
+ pub fn set_num_srcs(&mut self, num_srcs: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::RECORD_NUM_SRCS], num_srcs);
+ }
+
+ /// Return the multicast address field.
+ ///
+ /// # Panics
+ /// This function panics if the given address is not a multicast address.
+ #[inline]
+ pub fn set_mcast_addr(&mut self, addr: Ipv6Address) {
+ assert!(addr.is_multicast());
+ let data = self.buffer.as_mut();
+ data[field::RECORD_MCAST_ADDR].copy_from_slice(addr.as_bytes());
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> AddressRecord<T> {
+ /// Return a pointer to the address records.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let data = self.buffer.as_mut();
+ &mut data[field::RECORD_MCAST_ADDR.end..]
+ }
+}
+
+/// A high-level representation of an MLDv2 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr<'a> {
+ Query {
+ max_resp_code: u16,
+ mcast_addr: Ipv6Address,
+ s_flag: bool,
+ qrv: u8,
+ qqic: u8,
+ num_srcs: u16,
+ data: &'a [u8],
+ },
+ Report {
+ nr_mcast_addr_rcrds: u16,
+ data: &'a [u8],
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an MLDv2 packet and return a high-level representation.
+ pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ match packet.msg_type() {
+ Message::MldQuery => Ok(Repr::Query {
+ max_resp_code: packet.max_resp_code(),
+ mcast_addr: packet.mcast_addr(),
+ s_flag: packet.s_flag(),
+ qrv: packet.qrv(),
+ qqic: packet.qqic(),
+ num_srcs: packet.num_srcs(),
+ data: packet.payload(),
+ }),
+ Message::MldReport => Ok(Repr::Report {
+ nr_mcast_addr_rcrds: packet.nr_mcast_addr_rcrds(),
+ data: packet.payload(),
+ }),
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ Repr::Query { data, .. } => field::QUERY_NUM_SRCS.end + data.len(),
+ Repr::Report { data, .. } => field::NR_MCAST_RCRDS.end + data.len(),
+ }
+ }
+
+ /// Emit a high-level representation into an MLDv2 packet.
+ pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ match self {
+ Repr::Query {
+ max_resp_code,
+ mcast_addr,
+ s_flag,
+ qrv,
+ qqic,
+ num_srcs,
+ data,
+ } => {
+ packet.set_msg_type(Message::MldQuery);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_max_resp_code(*max_resp_code);
+ packet.set_mcast_addr(*mcast_addr);
+ if *s_flag {
+ packet.set_s_flag();
+ } else {
+ packet.clear_s_flag();
+ }
+ packet.set_qrv(*qrv);
+ packet.set_qqic(*qqic);
+ packet.set_num_srcs(*num_srcs);
+ packet.payload_mut().copy_from_slice(&data[..]);
+ }
+ Repr::Report {
+ nr_mcast_addr_rcrds,
+ data,
+ } => {
+ packet.set_msg_type(Message::MldReport);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_nr_mcast_addr_rcrds(*nr_mcast_addr_rcrds);
+ packet.payload_mut().copy_from_slice(&data[..]);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::phy::ChecksumCapabilities;
+ use crate::wire::icmpv6::Message;
+ use crate::wire::Icmpv6Repr;
+
+ static QUERY_PACKET_BYTES: [u8; 44] = [
+ 0x82, 0x00, 0x73, 0x74, 0x04, 0x00, 0x00, 0x00, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x12, 0x00, 0x01, 0xff, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ ];
+
+ static QUERY_PACKET_PAYLOAD: [u8; 16] = [
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02,
+ ];
+
+ static REPORT_PACKET_BYTES: [u8; 44] = [
+ 0x8f, 0x00, 0x73, 0x85, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ ];
+
+ static REPORT_PACKET_PAYLOAD: [u8; 36] = [
+ 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ ];
+
+ fn create_repr<'a>(ty: Message) -> Icmpv6Repr<'a> {
+ match ty {
+ Message::MldQuery => Icmpv6Repr::Mld(Repr::Query {
+ max_resp_code: 0x400,
+ mcast_addr: Ipv6Address::LINK_LOCAL_ALL_NODES,
+ s_flag: true,
+ qrv: 0x02,
+ qqic: 0x12,
+ num_srcs: 0x01,
+ data: &QUERY_PACKET_PAYLOAD,
+ }),
+ Message::MldReport => Icmpv6Repr::Mld(Repr::Report {
+ nr_mcast_addr_rcrds: 1,
+ data: &REPORT_PACKET_PAYLOAD,
+ }),
+ _ => {
+ panic!("Message type must be a MLDv2 message type");
+ }
+ }
+ }
+
+ #[test]
+ fn test_query_deconstruct() {
+ let packet = Packet::new_unchecked(&QUERY_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::MldQuery);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.checksum(), 0x7374);
+ assert_eq!(packet.max_resp_code(), 0x0400);
+ assert_eq!(packet.mcast_addr(), Ipv6Address::LINK_LOCAL_ALL_NODES);
+ assert!(packet.s_flag());
+ assert_eq!(packet.qrv(), 0x02);
+ assert_eq!(packet.qqic(), 0x12);
+ assert_eq!(packet.num_srcs(), 0x01);
+ assert_eq!(
+ Ipv6Address::from_bytes(packet.payload()),
+ Ipv6Address::LINK_LOCAL_ALL_ROUTERS
+ );
+ }
+
+ #[test]
+ fn test_query_construct() {
+ let mut bytes = vec![0xff; 44];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ packet.set_msg_type(Message::MldQuery);
+ packet.set_msg_code(0);
+ packet.set_max_resp_code(0x0400);
+ packet.set_mcast_addr(Ipv6Address::LINK_LOCAL_ALL_NODES);
+ packet.set_s_flag();
+ packet.set_qrv(0x02);
+ packet.set_qqic(0x12);
+ packet.set_num_srcs(0x01);
+ packet
+ .payload_mut()
+ .copy_from_slice(Ipv6Address::LINK_LOCAL_ALL_ROUTERS.as_bytes());
+ packet.clear_reserved();
+ packet.fill_checksum(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ );
+ assert_eq!(&*packet.into_inner(), &QUERY_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_record_deconstruct() {
+ let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::MldReport);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.checksum(), 0x7385);
+ assert_eq!(packet.nr_mcast_addr_rcrds(), 0x01);
+ let addr_rcrd = AddressRecord::new_unchecked(packet.payload());
+ assert_eq!(addr_rcrd.record_type(), RecordType::ModeIsInclude);
+ assert_eq!(addr_rcrd.aux_data_len(), 0x00);
+ assert_eq!(addr_rcrd.num_srcs(), 0x01);
+ assert_eq!(addr_rcrd.mcast_addr(), Ipv6Address::LINK_LOCAL_ALL_NODES);
+ assert_eq!(
+ Ipv6Address::from_bytes(addr_rcrd.payload()),
+ Ipv6Address::LINK_LOCAL_ALL_ROUTERS
+ );
+ }
+
+ #[test]
+ fn test_record_construct() {
+ let mut bytes = vec![0xff; 44];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ packet.set_msg_type(Message::MldReport);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_nr_mcast_addr_rcrds(1);
+ {
+ let mut addr_rcrd = AddressRecord::new_unchecked(packet.payload_mut());
+ addr_rcrd.set_record_type(RecordType::ModeIsInclude);
+ addr_rcrd.set_aux_data_len(0);
+ addr_rcrd.set_num_srcs(1);
+ addr_rcrd.set_mcast_addr(Ipv6Address::LINK_LOCAL_ALL_NODES);
+ addr_rcrd
+ .payload_mut()
+ .copy_from_slice(Ipv6Address::LINK_LOCAL_ALL_ROUTERS.as_bytes());
+ }
+ packet.fill_checksum(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ );
+ assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_query_repr_parse() {
+ let packet = Packet::new_unchecked(&QUERY_PACKET_BYTES[..]);
+ let repr = Icmpv6Repr::parse(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ &packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(repr, Ok(create_repr(Message::MldQuery)));
+ }
+
+ #[test]
+ fn test_report_repr_parse() {
+ let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]);
+ let repr = Icmpv6Repr::parse(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ &packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(repr, Ok(create_repr(Message::MldReport)));
+ }
+
+ #[test]
+ fn test_query_repr_emit() {
+ let mut bytes = [0x2a; 44];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ let repr = create_repr(Message::MldQuery);
+ repr.emit(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &QUERY_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_report_repr_emit() {
+ let mut bytes = [0x2a; 44];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ let repr = create_repr(Message::MldReport);
+ repr.emit(
+ &Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
+ &Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]);
+ }
+}
diff --git a/src/wire/mod.rs b/src/wire/mod.rs
new file mode 100644
index 0000000..5aed2d4
--- /dev/null
+++ b/src/wire/mod.rs
@@ -0,0 +1,524 @@
+/*! Low-level packet access and construction.
+
+The `wire` module deals with the packet *representation*. It provides two levels
+of functionality.
+
+ * First, it provides functions to extract fields from sequences of octets,
+ and to insert fields into sequences of octets. This happens `Packet` family of
+ structures, e.g. [EthernetFrame] or [Ipv4Packet].
+ * Second, in cases where the space of valid field values is much smaller than the space
+ of possible field values, it provides a compact, high-level representation
+ of packet data that can be parsed from and emitted into a sequence of octets.
+ This happens through the `Repr` family of structs and enums, e.g. [ArpRepr] or [Ipv4Repr].
+
+[EthernetFrame]: struct.EthernetFrame.html
+[Ipv4Packet]: struct.Ipv4Packet.html
+[ArpRepr]: enum.ArpRepr.html
+[Ipv4Repr]: struct.Ipv4Repr.html
+
+The functions in the `wire` module are designed for use together with `-Cpanic=abort`.
+
+The `Packet` family of data structures guarantees that, if the `Packet::check_len()` method
+returned `Ok(())`, then no accessor or setter method will panic; however, the guarantee
+provided by `Packet::check_len()` may no longer hold after changing certain fields,
+which are listed in the documentation for the specific packet.
+
+The `Packet::new_checked` method is a shorthand for a combination of `Packet::new_unchecked`
+and `Packet::check_len`.
+When parsing untrusted input, it is *necessary* to use `Packet::new_checked()`;
+so long as the buffer is not modified, no accessor will fail.
+When emitting output, though, it is *incorrect* to use `Packet::new_checked()`;
+the length check is likely to succeed on a zeroed buffer, but fail on a buffer
+filled with data from a previous packet, such as when reusing buffers, resulting
+in nondeterministic panics with some network devices but not others.
+The buffer length for emission is not calculated by the `Packet` layer.
+
+In the `Repr` family of data structures, the `Repr::parse()` method never panics
+as long as `Packet::new_checked()` (or `Packet::check_len()`) has succeeded, and
+the `Repr::emit()` method never panics as long as the underlying buffer is exactly
+`Repr::buffer_len()` octets long.
+
+# Examples
+
+To emit an IP packet header into an octet buffer, and then parse it back:
+
+```rust
+# #[cfg(feature = "proto-ipv4")]
+# {
+use smoltcp::phy::ChecksumCapabilities;
+use smoltcp::wire::*;
+let repr = Ipv4Repr {
+ src_addr: Ipv4Address::new(10, 0, 0, 1),
+ dst_addr: Ipv4Address::new(10, 0, 0, 2),
+ next_header: IpProtocol::Tcp,
+ payload_len: 10,
+ hop_limit: 64,
+};
+let mut buffer = vec![0; repr.buffer_len() + repr.payload_len];
+{ // emission
+ let mut packet = Ipv4Packet::new_unchecked(&mut buffer);
+ repr.emit(&mut packet, &ChecksumCapabilities::default());
+}
+{ // parsing
+ let packet = Ipv4Packet::new_checked(&buffer)
+ .expect("truncated packet");
+ let parsed = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default())
+ .expect("malformed packet");
+ assert_eq!(repr, parsed);
+}
+# }
+```
+*/
+
+mod field {
+ pub type Field = ::core::ops::Range<usize>;
+ pub type Rest = ::core::ops::RangeFrom<usize>;
+}
+
+pub mod pretty_print;
+
+#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
+mod arp;
+#[cfg(feature = "proto-dhcpv4")]
+pub(crate) mod dhcpv4;
+#[cfg(feature = "proto-dns")]
+pub(crate) mod dns;
+#[cfg(feature = "medium-ethernet")]
+mod ethernet;
+#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
+mod icmp;
+#[cfg(feature = "proto-ipv4")]
+mod icmpv4;
+#[cfg(feature = "proto-ipv6")]
+mod icmpv6;
+#[cfg(feature = "medium-ieee802154")]
+pub mod ieee802154;
+#[cfg(feature = "proto-igmp")]
+mod igmp;
+pub(crate) mod ip;
+#[cfg(feature = "proto-ipv4")]
+mod ipv4;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6ext_header;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6fragment;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6hbh;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6option;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6routing;
+#[cfg(feature = "proto-ipv6")]
+mod mld;
+#[cfg(all(
+ feature = "proto-ipv6",
+ any(feature = "medium-ethernet", feature = "medium-ieee802154")
+))]
+mod ndisc;
+#[cfg(all(
+ feature = "proto-ipv6",
+ any(feature = "medium-ethernet", feature = "medium-ieee802154")
+))]
+mod ndiscoption;
+#[cfg(feature = "proto-rpl")]
+mod rpl;
+#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
+mod sixlowpan;
+mod tcp;
+mod udp;
+
+#[cfg(feature = "proto-ipsec-ah")]
+mod ipsec_ah;
+
+#[cfg(feature = "proto-ipsec-esp")]
+mod ipsec_esp;
+
+use core::fmt;
+
+use crate::phy::Medium;
+
+pub use self::pretty_print::PrettyPrinter;
+
+#[cfg(feature = "medium-ethernet")]
+pub use self::ethernet::{
+ Address as EthernetAddress, EtherType as EthernetProtocol, Frame as EthernetFrame,
+ Repr as EthernetRepr, HEADER_LEN as ETHERNET_HEADER_LEN,
+};
+
+#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
+pub use self::arp::{
+ Hardware as ArpHardware, Operation as ArpOperation, Packet as ArpPacket, Repr as ArpRepr,
+};
+
+#[cfg(feature = "proto-rpl")]
+pub use self::rpl::{
+ data::HopByHopOption as RplHopByHopRepr, data::Packet as RplHopByHopPacket,
+ options::Packet as RplOptionPacket, options::Repr as RplOptionRepr,
+ InstanceId as RplInstanceId, Repr as RplRepr,
+};
+
+#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
+pub use self::sixlowpan::{
+ frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr},
+ iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr},
+ nhc::{
+ ExtHeaderId as SixlowpanExtHeaderId, ExtHeaderPacket as SixlowpanExtHeaderPacket,
+ ExtHeaderRepr as SixlowpanExtHeaderRepr, NhcPacket as SixlowpanNhcPacket,
+ UdpNhcPacket as SixlowpanUdpNhcPacket, UdpNhcRepr as SixlowpanUdpNhcRepr,
+ },
+ AddressContext as SixlowpanAddressContext, NextHeader as SixlowpanNextHeader, SixlowpanPacket,
+};
+
+#[cfg(feature = "medium-ieee802154")]
+pub use self::ieee802154::{
+ Address as Ieee802154Address, AddressingMode as Ieee802154AddressingMode,
+ Frame as Ieee802154Frame, FrameType as Ieee802154FrameType,
+ FrameVersion as Ieee802154FrameVersion, Pan as Ieee802154Pan, Repr as Ieee802154Repr,
+};
+
+pub use self::ip::{
+ Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint,
+ ListenEndpoint as IpListenEndpoint, Protocol as IpProtocol, Repr as IpRepr,
+ Version as IpVersion,
+};
+
+#[cfg(feature = "proto-ipv4")]
+pub use self::ipv4::{
+ Address as Ipv4Address, Cidr as Ipv4Cidr, Key as Ipv4FragKey, Packet as Ipv4Packet,
+ Repr as Ipv4Repr, HEADER_LEN as IPV4_HEADER_LEN, MIN_MTU as IPV4_MIN_MTU,
+};
+
+#[cfg(feature = "proto-ipv6")]
+pub(crate) use self::ipv6::Scope as Ipv6AddressScope;
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6::{
+ Address as Ipv6Address, Cidr as Ipv6Cidr, Packet as Ipv6Packet, Repr as Ipv6Repr,
+ HEADER_LEN as IPV6_HEADER_LEN, MIN_MTU as IPV6_MIN_MTU,
+};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6option::{
+ FailureType as Ipv6OptionFailureType, Ipv6Option, Ipv6OptionsIterator, Repr as Ipv6OptionRepr,
+ Type as Ipv6OptionType,
+};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::ipv6routing::{
+ Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr, Type as Ipv6RoutingType,
+};
+
+#[cfg(feature = "proto-ipv4")]
+pub use self::icmpv4::{
+ DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet,
+ ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr,
+ TimeExceeded as Icmpv4TimeExceeded,
+};
+
+#[cfg(feature = "proto-igmp")]
+pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::icmpv6::{
+ DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet,
+ ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded,
+};
+
+#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
+pub use self::icmp::Repr as IcmpRepr;
+
+#[cfg(all(
+ feature = "proto-ipv6",
+ any(feature = "medium-ethernet", feature = "medium-ieee802154")
+))]
+pub use self::ndisc::{
+ NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags,
+};
+
+#[cfg(all(
+ feature = "proto-ipv6",
+ any(feature = "medium-ethernet", feature = "medium-ieee802154")
+))]
+pub use self::ndiscoption::{
+ NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags,
+ PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader,
+ Repr as NdiscOptionRepr, Type as NdiscOptionType,
+};
+
+#[cfg(feature = "proto-ipv6")]
+pub use self::mld::{AddressRecord as MldAddressRecord, Repr as MldRepr};
+
+pub use self::udp::{Packet as UdpPacket, Repr as UdpRepr, HEADER_LEN as UDP_HEADER_LEN};
+
+pub use self::tcp::{
+ Control as TcpControl, Packet as TcpPacket, Repr as TcpRepr, SeqNumber as TcpSeqNumber,
+ TcpOption, HEADER_LEN as TCP_HEADER_LEN,
+};
+
+#[cfg(feature = "proto-dhcpv4")]
+pub use self::dhcpv4::{
+ DhcpOption, DhcpOptionWriter, MessageType as DhcpMessageType, Packet as DhcpPacket,
+ Repr as DhcpRepr, CLIENT_PORT as DHCP_CLIENT_PORT,
+ MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, SERVER_PORT as DHCP_SERVER_PORT,
+};
+
+#[cfg(feature = "proto-dns")]
+pub use self::dns::{
+ Flags as DnsFlags, Opcode as DnsOpcode, Packet as DnsPacket, Rcode as DnsRcode,
+ Repr as DnsRepr, Type as DnsQueryType,
+};
+
+#[cfg(feature = "proto-ipsec-ah")]
+pub use self::ipsec_ah::{Packet as IpSecAuthHeaderPacket, Repr as IpSecAuthHeaderRepr};
+
+#[cfg(feature = "proto-ipsec-esp")]
+pub use self::ipsec_esp::{Packet as IpSecEspPacket, Repr as IpSecEspRepr};
+
+/// Parsing a packet failed.
+///
+/// Either it is malformed, or it is not supported by smoltcp.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Error;
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "wire::Error")
+ }
+}
+
+pub type Result<T> = core::result::Result<T, Error>;
+
+/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
+#[cfg(any(
+ feature = "medium-ip",
+ feature = "medium-ethernet",
+ feature = "medium-ieee802154"
+))]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum HardwareAddress {
+ #[cfg(feature = "medium-ip")]
+ Ip,
+ #[cfg(feature = "medium-ethernet")]
+ Ethernet(EthernetAddress),
+ #[cfg(feature = "medium-ieee802154")]
+ Ieee802154(Ieee802154Address),
+}
+
+#[cfg(any(
+ feature = "medium-ip",
+ feature = "medium-ethernet",
+ feature = "medium-ieee802154"
+))]
+impl HardwareAddress {
+ pub const fn as_bytes(&self) -> &[u8] {
+ match self {
+ #[cfg(feature = "medium-ip")]
+ HardwareAddress::Ip => unreachable!(),
+ #[cfg(feature = "medium-ethernet")]
+ HardwareAddress::Ethernet(addr) => addr.as_bytes(),
+ #[cfg(feature = "medium-ieee802154")]
+ HardwareAddress::Ieee802154(addr) => addr.as_bytes(),
+ }
+ }
+
+ /// Query whether the address is an unicast address.
+ pub fn is_unicast(&self) -> bool {
+ match self {
+ #[cfg(feature = "medium-ip")]
+ HardwareAddress::Ip => unreachable!(),
+ #[cfg(feature = "medium-ethernet")]
+ HardwareAddress::Ethernet(addr) => addr.is_unicast(),
+ #[cfg(feature = "medium-ieee802154")]
+ HardwareAddress::Ieee802154(addr) => addr.is_unicast(),
+ }
+ }
+
+ /// Query whether the address is a broadcast address.
+ pub fn is_broadcast(&self) -> bool {
+ match self {
+ #[cfg(feature = "medium-ip")]
+ HardwareAddress::Ip => unreachable!(),
+ #[cfg(feature = "medium-ethernet")]
+ HardwareAddress::Ethernet(addr) => addr.is_broadcast(),
+ #[cfg(feature = "medium-ieee802154")]
+ HardwareAddress::Ieee802154(addr) => addr.is_broadcast(),
+ }
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ pub(crate) fn ethernet_or_panic(&self) -> EthernetAddress {
+ match self {
+ HardwareAddress::Ethernet(addr) => *addr,
+ #[allow(unreachable_patterns)]
+ _ => panic!("HardwareAddress is not Ethernet."),
+ }
+ }
+
+ #[cfg(feature = "medium-ieee802154")]
+ pub(crate) fn ieee802154_or_panic(&self) -> Ieee802154Address {
+ match self {
+ HardwareAddress::Ieee802154(addr) => *addr,
+ #[allow(unreachable_patterns)]
+ _ => panic!("HardwareAddress is not Ethernet."),
+ }
+ }
+
+ #[inline]
+ pub(crate) fn medium(&self) -> Medium {
+ match self {
+ #[cfg(feature = "medium-ip")]
+ HardwareAddress::Ip => Medium::Ip,
+ #[cfg(feature = "medium-ethernet")]
+ HardwareAddress::Ethernet(_) => Medium::Ethernet,
+ #[cfg(feature = "medium-ieee802154")]
+ HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
+ }
+ }
+}
+
+#[cfg(any(
+ feature = "medium-ip",
+ feature = "medium-ethernet",
+ feature = "medium-ieee802154"
+))]
+impl core::fmt::Display for HardwareAddress {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match self {
+ #[cfg(feature = "medium-ip")]
+ HardwareAddress::Ip => write!(f, "no hardware addr"),
+ #[cfg(feature = "medium-ethernet")]
+ HardwareAddress::Ethernet(addr) => write!(f, "{addr}"),
+ #[cfg(feature = "medium-ieee802154")]
+ HardwareAddress::Ieee802154(addr) => write!(f, "{addr}"),
+ }
+ }
+}
+
+#[cfg(feature = "medium-ethernet")]
+impl From<EthernetAddress> for HardwareAddress {
+ fn from(addr: EthernetAddress) -> Self {
+ HardwareAddress::Ethernet(addr)
+ }
+}
+
+#[cfg(feature = "medium-ieee802154")]
+impl From<Ieee802154Address> for HardwareAddress {
+ fn from(addr: Ieee802154Address) -> Self {
+ HardwareAddress::Ieee802154(addr)
+ }
+}
+
+#[cfg(not(feature = "medium-ieee802154"))]
+pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6;
+#[cfg(feature = "medium-ieee802154")]
+pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8;
+
+/// Unparsed hardware address.
+///
+/// Used to make NDISC parsing agnostic of the hardware medium in use.
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct RawHardwareAddress {
+ len: u8,
+ data: [u8; MAX_HARDWARE_ADDRESS_LEN],
+}
+
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+impl RawHardwareAddress {
+ pub fn from_bytes(addr: &[u8]) -> Self {
+ let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN];
+ data[..addr.len()].copy_from_slice(addr);
+
+ Self {
+ len: addr.len() as u8,
+ data,
+ }
+ }
+
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.data[..self.len as usize]
+ }
+
+ pub const fn len(&self) -> usize {
+ self.len as usize
+ }
+
+ pub const fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ pub fn parse(&self, medium: Medium) -> Result<HardwareAddress> {
+ match medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ if self.len() < 6 {
+ return Err(Error);
+ }
+ Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes(
+ self.as_bytes(),
+ )))
+ }
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => {
+ if self.len() < 8 {
+ return Err(Error);
+ }
+ Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
+ self.as_bytes(),
+ )))
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => unreachable!(),
+ }
+ }
+}
+
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+impl core::fmt::Display for RawHardwareAddress {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ for (i, &b) in self.as_bytes().iter().enumerate() {
+ if i != 0 {
+ write!(f, ":")?;
+ }
+ write!(f, "{b:02x}")?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "medium-ethernet")]
+impl From<EthernetAddress> for RawHardwareAddress {
+ fn from(addr: EthernetAddress) -> Self {
+ Self::from_bytes(addr.as_bytes())
+ }
+}
+
+#[cfg(feature = "medium-ieee802154")]
+impl From<Ieee802154Address> for RawHardwareAddress {
+ fn from(addr: Ieee802154Address) -> Self {
+ Self::from_bytes(addr.as_bytes())
+ }
+}
+
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+impl From<HardwareAddress> for RawHardwareAddress {
+ fn from(addr: HardwareAddress) -> Self {
+ Self::from_bytes(addr.as_bytes())
+ }
+}
diff --git a/src/wire/ndisc.rs b/src/wire/ndisc.rs
new file mode 100644
index 0000000..691b69b
--- /dev/null
+++ b/src/wire/ndisc.rs
@@ -0,0 +1,541 @@
+use bitflags::bitflags;
+use byteorder::{ByteOrder, NetworkEndian};
+
+use super::{Error, Result};
+use crate::time::Duration;
+use crate::wire::icmpv6::{field, Message, Packet};
+use crate::wire::Ipv6Address;
+use crate::wire::RawHardwareAddress;
+use crate::wire::{NdiscOption, NdiscOptionRepr};
+use crate::wire::{NdiscPrefixInformation, NdiscRedirectedHeader};
+
+bitflags! {
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct RouterFlags: u8 {
+ const MANAGED = 0b10000000;
+ const OTHER = 0b01000000;
+ }
+}
+
+bitflags! {
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct NeighborFlags: u8 {
+ const ROUTER = 0b10000000;
+ const SOLICITED = 0b01000000;
+ const OVERRIDE = 0b00100000;
+ }
+}
+
+/// Getters for the Router Advertisement message header.
+/// See [RFC 4861 § 4.2].
+///
+/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the current hop limit field.
+ #[inline]
+ pub fn current_hop_limit(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::CUR_HOP_LIMIT]
+ }
+
+ /// Return the Router Advertisement flags.
+ #[inline]
+ pub fn router_flags(&self) -> RouterFlags {
+ let data = self.buffer.as_ref();
+ RouterFlags::from_bits_truncate(data[field::ROUTER_FLAGS])
+ }
+
+ /// Return the router lifetime field.
+ #[inline]
+ pub fn router_lifetime(&self) -> Duration {
+ let data = self.buffer.as_ref();
+ Duration::from_secs(NetworkEndian::read_u16(&data[field::ROUTER_LT]) as u64)
+ }
+
+ /// Return the reachable time field.
+ #[inline]
+ pub fn reachable_time(&self) -> Duration {
+ let data = self.buffer.as_ref();
+ Duration::from_millis(NetworkEndian::read_u32(&data[field::REACHABLE_TM]) as u64)
+ }
+
+ /// Return the retransmit time field.
+ #[inline]
+ pub fn retrans_time(&self) -> Duration {
+ let data = self.buffer.as_ref();
+ Duration::from_millis(NetworkEndian::read_u32(&data[field::RETRANS_TM]) as u64)
+ }
+}
+
+/// Common getters for the [Neighbor Solicitation], [Neighbor Advertisement], and
+/// [Redirect] message types.
+///
+/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3
+/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4
+/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the target address field.
+ #[inline]
+ pub fn target_addr(&self) -> Ipv6Address {
+ let data = self.buffer.as_ref();
+ Ipv6Address::from_bytes(&data[field::TARGET_ADDR])
+ }
+}
+
+/// Getters for the Neighbor Solicitation message header.
+/// See [RFC 4861 § 4.3].
+///
+/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Neighbor Solicitation flags.
+ #[inline]
+ pub fn neighbor_flags(&self) -> NeighborFlags {
+ let data = self.buffer.as_ref();
+ NeighborFlags::from_bits_truncate(data[field::NEIGH_FLAGS])
+ }
+}
+
+/// Getters for the Redirect message header.
+/// See [RFC 4861 § 4.5].
+///
+/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the destination address field.
+ #[inline]
+ pub fn dest_addr(&self) -> Ipv6Address {
+ let data = self.buffer.as_ref();
+ Ipv6Address::from_bytes(&data[field::DEST_ADDR])
+ }
+}
+
+/// Setters for the Router Advertisement message header.
+/// See [RFC 4861 § 4.2].
+///
+/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the current hop limit field.
+ #[inline]
+ pub fn set_current_hop_limit(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::CUR_HOP_LIMIT] = value;
+ }
+
+ /// Set the Router Advertisement flags.
+ #[inline]
+ pub fn set_router_flags(&mut self, flags: RouterFlags) {
+ self.buffer.as_mut()[field::ROUTER_FLAGS] = flags.bits();
+ }
+
+ /// Set the router lifetime field.
+ #[inline]
+ pub fn set_router_lifetime(&mut self, value: Duration) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::ROUTER_LT], value.secs() as u16);
+ }
+
+ /// Set the reachable time field.
+ #[inline]
+ pub fn set_reachable_time(&mut self, value: Duration) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::REACHABLE_TM], value.total_millis() as u32);
+ }
+
+ /// Set the retransmit time field.
+ #[inline]
+ pub fn set_retrans_time(&mut self, value: Duration) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::RETRANS_TM], value.total_millis() as u32);
+ }
+}
+
+/// Common setters for the [Neighbor Solicitation], [Neighbor Advertisement], and
+/// [Redirect] message types.
+///
+/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3
+/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4
+/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the target address field.
+ #[inline]
+ pub fn set_target_addr(&mut self, value: Ipv6Address) {
+ let data = self.buffer.as_mut();
+ data[field::TARGET_ADDR].copy_from_slice(value.as_bytes());
+ }
+}
+
+/// Setters for the Neighbor Solicitation message header.
+/// See [RFC 4861 § 4.3].
+///
+/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Neighbor Solicitation flags.
+ #[inline]
+ pub fn set_neighbor_flags(&mut self, flags: NeighborFlags) {
+ self.buffer.as_mut()[field::NEIGH_FLAGS] = flags.bits();
+ }
+}
+
+/// Setters for the Redirect message header.
+/// See [RFC 4861 § 4.5].
+///
+/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the destination address field.
+ #[inline]
+ pub fn set_dest_addr(&mut self, value: Ipv6Address) {
+ let data = self.buffer.as_mut();
+ data[field::DEST_ADDR].copy_from_slice(value.as_bytes());
+ }
+}
+
+/// A high-level representation of an Neighbor Discovery packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr<'a> {
+ RouterSolicit {
+ lladdr: Option<RawHardwareAddress>,
+ },
+ RouterAdvert {
+ hop_limit: u8,
+ flags: RouterFlags,
+ router_lifetime: Duration,
+ reachable_time: Duration,
+ retrans_time: Duration,
+ lladdr: Option<RawHardwareAddress>,
+ mtu: Option<u32>,
+ prefix_info: Option<NdiscPrefixInformation>,
+ },
+ NeighborSolicit {
+ target_addr: Ipv6Address,
+ lladdr: Option<RawHardwareAddress>,
+ },
+ NeighborAdvert {
+ flags: NeighborFlags,
+ target_addr: Ipv6Address,
+ lladdr: Option<RawHardwareAddress>,
+ },
+ Redirect {
+ target_addr: Ipv6Address,
+ dest_addr: Ipv6Address,
+ lladdr: Option<RawHardwareAddress>,
+ redirected_hdr: Option<NdiscRedirectedHeader<'a>>,
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an NDISC packet and return a high-level representation of the
+ /// packet.
+ #[allow(clippy::single_match)]
+ pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ let (mut src_ll_addr, mut mtu, mut prefix_info, mut target_ll_addr, mut redirected_hdr) =
+ (None, None, None, None, None);
+
+ let mut offset = 0;
+ while packet.payload().len() > offset {
+ let pkt = NdiscOption::new_checked(&packet.payload()[offset..])?;
+
+ // If an option doesn't parse, ignore it and still parse the others.
+ if let Ok(opt) = NdiscOptionRepr::parse(&pkt) {
+ match opt {
+ NdiscOptionRepr::SourceLinkLayerAddr(addr) => src_ll_addr = Some(addr),
+ NdiscOptionRepr::TargetLinkLayerAddr(addr) => target_ll_addr = Some(addr),
+ NdiscOptionRepr::PrefixInformation(prefix) => prefix_info = Some(prefix),
+ NdiscOptionRepr::RedirectedHeader(redirect) => redirected_hdr = Some(redirect),
+ NdiscOptionRepr::Mtu(m) => mtu = Some(m),
+ _ => {}
+ }
+ }
+
+ let len = pkt.data_len() as usize * 8;
+ if len == 0 {
+ return Err(Error);
+ }
+ offset += len;
+ }
+
+ match packet.msg_type() {
+ Message::RouterSolicit => Ok(Repr::RouterSolicit {
+ lladdr: src_ll_addr,
+ }),
+ Message::RouterAdvert => Ok(Repr::RouterAdvert {
+ hop_limit: packet.current_hop_limit(),
+ flags: packet.router_flags(),
+ router_lifetime: packet.router_lifetime(),
+ reachable_time: packet.reachable_time(),
+ retrans_time: packet.retrans_time(),
+ lladdr: src_ll_addr,
+ mtu,
+ prefix_info,
+ }),
+ Message::NeighborSolicit => Ok(Repr::NeighborSolicit {
+ target_addr: packet.target_addr(),
+ lladdr: src_ll_addr,
+ }),
+ Message::NeighborAdvert => Ok(Repr::NeighborAdvert {
+ flags: packet.neighbor_flags(),
+ target_addr: packet.target_addr(),
+ lladdr: target_ll_addr,
+ }),
+ Message::Redirect => Ok(Repr::Redirect {
+ target_addr: packet.target_addr(),
+ dest_addr: packet.dest_addr(),
+ lladdr: src_ll_addr,
+ redirected_hdr,
+ }),
+ _ => Err(Error),
+ }
+ }
+
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ &Repr::RouterSolicit { lladdr } => match lladdr {
+ Some(addr) => {
+ field::UNUSED.end + { NdiscOptionRepr::SourceLinkLayerAddr(addr).buffer_len() }
+ }
+ None => field::UNUSED.end,
+ },
+ &Repr::RouterAdvert {
+ lladdr,
+ mtu,
+ prefix_info,
+ ..
+ } => {
+ let mut offset = 0;
+ if let Some(lladdr) = lladdr {
+ offset += NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len();
+ }
+ if let Some(mtu) = mtu {
+ offset += NdiscOptionRepr::Mtu(mtu).buffer_len();
+ }
+ if let Some(prefix_info) = prefix_info {
+ offset += NdiscOptionRepr::PrefixInformation(prefix_info).buffer_len();
+ }
+ field::RETRANS_TM.end + offset
+ }
+ &Repr::NeighborSolicit { lladdr, .. } | &Repr::NeighborAdvert { lladdr, .. } => {
+ let mut offset = field::TARGET_ADDR.end;
+ if let Some(lladdr) = lladdr {
+ offset += NdiscOptionRepr::SourceLinkLayerAddr(lladdr).buffer_len();
+ }
+ offset
+ }
+ &Repr::Redirect {
+ lladdr,
+ redirected_hdr,
+ ..
+ } => {
+ let mut offset = field::DEST_ADDR.end;
+ if let Some(lladdr) = lladdr {
+ offset += NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len();
+ }
+ if let Some(NdiscRedirectedHeader { header, data }) = redirected_hdr {
+ offset +=
+ NdiscOptionRepr::RedirectedHeader(NdiscRedirectedHeader { header, data })
+ .buffer_len();
+ }
+ offset
+ }
+ }
+ }
+
+ pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ match *self {
+ Repr::RouterSolicit { lladdr } => {
+ packet.set_msg_type(Message::RouterSolicit);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ if let Some(lladdr) = lladdr {
+ let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
+ NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
+ }
+ }
+
+ Repr::RouterAdvert {
+ hop_limit,
+ flags,
+ router_lifetime,
+ reachable_time,
+ retrans_time,
+ lladdr,
+ mtu,
+ prefix_info,
+ } => {
+ packet.set_msg_type(Message::RouterAdvert);
+ packet.set_msg_code(0);
+ packet.set_current_hop_limit(hop_limit);
+ packet.set_router_flags(flags);
+ packet.set_router_lifetime(router_lifetime);
+ packet.set_reachable_time(reachable_time);
+ packet.set_retrans_time(retrans_time);
+ let mut offset = 0;
+ if let Some(lladdr) = lladdr {
+ let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
+ let opt = NdiscOptionRepr::SourceLinkLayerAddr(lladdr);
+ opt.emit(&mut opt_pkt);
+ offset += opt.buffer_len();
+ }
+ if let Some(mtu) = mtu {
+ let mut opt_pkt =
+ NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]);
+ NdiscOptionRepr::Mtu(mtu).emit(&mut opt_pkt);
+ offset += NdiscOptionRepr::Mtu(mtu).buffer_len();
+ }
+ if let Some(prefix_info) = prefix_info {
+ let mut opt_pkt =
+ NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]);
+ NdiscOptionRepr::PrefixInformation(prefix_info).emit(&mut opt_pkt)
+ }
+ }
+
+ Repr::NeighborSolicit {
+ target_addr,
+ lladdr,
+ } => {
+ packet.set_msg_type(Message::NeighborSolicit);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_target_addr(target_addr);
+ if let Some(lladdr) = lladdr {
+ let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
+ NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
+ }
+ }
+
+ Repr::NeighborAdvert {
+ flags,
+ target_addr,
+ lladdr,
+ } => {
+ packet.set_msg_type(Message::NeighborAdvert);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_neighbor_flags(flags);
+ packet.set_target_addr(target_addr);
+ if let Some(lladdr) = lladdr {
+ let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
+ NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
+ }
+ }
+
+ Repr::Redirect {
+ target_addr,
+ dest_addr,
+ lladdr,
+ redirected_hdr,
+ } => {
+ packet.set_msg_type(Message::Redirect);
+ packet.set_msg_code(0);
+ packet.clear_reserved();
+ packet.set_target_addr(target_addr);
+ packet.set_dest_addr(dest_addr);
+ let offset = match lladdr {
+ Some(lladdr) => {
+ let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
+ NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
+ NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len()
+ }
+ None => 0,
+ };
+ if let Some(redirected_hdr) = redirected_hdr {
+ let mut opt_pkt =
+ NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]);
+ NdiscOptionRepr::RedirectedHeader(redirected_hdr).emit(&mut opt_pkt);
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "medium-ethernet")]
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::phy::ChecksumCapabilities;
+ use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
+ use crate::wire::EthernetAddress;
+ use crate::wire::Icmpv6Repr;
+
+ static ROUTER_ADVERT_BYTES: [u8; 24] = [
+ 0x86, 0x00, 0xa9, 0xde, 0x40, 0x80, 0x03, 0x84, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03,
+ 0x84, 0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56,
+ ];
+ static SOURCE_LINK_LAYER_OPT: [u8; 8] = [0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
+
+ fn create_repr<'a>() -> Icmpv6Repr<'a> {
+ Icmpv6Repr::Ndisc(Repr::RouterAdvert {
+ hop_limit: 64,
+ flags: RouterFlags::MANAGED,
+ router_lifetime: Duration::from_secs(900),
+ reachable_time: Duration::from_millis(900),
+ retrans_time: Duration::from_millis(900),
+ lladdr: Some(EthernetAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]).into()),
+ mtu: None,
+ prefix_info: None,
+ })
+ }
+
+ #[test]
+ fn test_router_advert_deconstruct() {
+ let packet = Packet::new_unchecked(&ROUTER_ADVERT_BYTES[..]);
+ assert_eq!(packet.msg_type(), Message::RouterAdvert);
+ assert_eq!(packet.msg_code(), 0);
+ assert_eq!(packet.current_hop_limit(), 64);
+ assert_eq!(packet.router_flags(), RouterFlags::MANAGED);
+ assert_eq!(packet.router_lifetime(), Duration::from_secs(900));
+ assert_eq!(packet.reachable_time(), Duration::from_millis(900));
+ assert_eq!(packet.retrans_time(), Duration::from_millis(900));
+ assert_eq!(packet.payload(), &SOURCE_LINK_LAYER_OPT[..]);
+ }
+
+ #[test]
+ fn test_router_advert_construct() {
+ let mut bytes = vec![0x0; 24];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_msg_type(Message::RouterAdvert);
+ packet.set_msg_code(0);
+ packet.set_current_hop_limit(64);
+ packet.set_router_flags(RouterFlags::MANAGED);
+ packet.set_router_lifetime(Duration::from_secs(900));
+ packet.set_reachable_time(Duration::from_millis(900));
+ packet.set_retrans_time(Duration::from_millis(900));
+ packet
+ .payload_mut()
+ .copy_from_slice(&SOURCE_LINK_LAYER_OPT[..]);
+ packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
+ assert_eq!(&*packet.into_inner(), &ROUTER_ADVERT_BYTES[..]);
+ }
+
+ #[test]
+ fn test_router_advert_repr_parse() {
+ let packet = Packet::new_unchecked(&ROUTER_ADVERT_BYTES[..]);
+ assert_eq!(
+ Icmpv6Repr::parse(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &packet,
+ &ChecksumCapabilities::default()
+ )
+ .unwrap(),
+ create_repr()
+ );
+ }
+
+ #[test]
+ fn test_router_advert_repr_emit() {
+ let mut bytes = vec![0x2a; 24];
+ let mut packet = Packet::new_unchecked(&mut bytes[..]);
+ create_repr().emit(
+ &MOCK_IP_ADDR_1,
+ &MOCK_IP_ADDR_2,
+ &mut packet,
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &ROUTER_ADVERT_BYTES[..]);
+ }
+}
diff --git a/src/wire/ndiscoption.rs b/src/wire/ndiscoption.rs
new file mode 100644
index 0000000..eff7a93
--- /dev/null
+++ b/src/wire/ndiscoption.rs
@@ -0,0 +1,768 @@
+use bitflags::bitflags;
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+use crate::time::Duration;
+use crate::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN};
+
+use crate::wire::RawHardwareAddress;
+
+enum_with_unknown! {
+ /// NDISC Option Type
+ pub enum Type(u8) {
+ /// Source Link-layer Address
+ SourceLinkLayerAddr = 0x1,
+ /// Target Link-layer Address
+ TargetLinkLayerAddr = 0x2,
+ /// Prefix Information
+ PrefixInformation = 0x3,
+ /// Redirected Header
+ RedirectedHeader = 0x4,
+ /// MTU
+ Mtu = 0x5
+ }
+}
+
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Type::SourceLinkLayerAddr => write!(f, "source link-layer address"),
+ Type::TargetLinkLayerAddr => write!(f, "target link-layer address"),
+ Type::PrefixInformation => write!(f, "prefix information"),
+ Type::RedirectedHeader => write!(f, "redirected header"),
+ Type::Mtu => write!(f, "mtu"),
+ Type::Unknown(id) => write!(f, "{id}"),
+ }
+ }
+}
+
+bitflags! {
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct PrefixInfoFlags: u8 {
+ const ON_LINK = 0b10000000;
+ const ADDRCONF = 0b01000000;
+ }
+}
+
+/// A read/write wrapper around an [NDISC Option].
+///
+/// [NDISC Option]: https://tools.ietf.org/html/rfc4861#section-4.6
+#[derive(Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct NdiscOption<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+// Format of an NDISC Option
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | Type | Length | ... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// ~ ... ~
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// See https://tools.ietf.org/html/rfc4861#section-4.6 for details.
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ // 8-bit identifier of the type of option.
+ pub const TYPE: usize = 0;
+ // 8-bit unsigned integer. Length of the option, in units of 8 octets.
+ pub const LENGTH: usize = 1;
+ // Minimum length of an option.
+ pub const MIN_OPT_LEN: usize = 8;
+ // Variable-length field. Option-Type-specific data.
+ pub const fn DATA(length: u8) -> Field {
+ 2..length as usize * 8
+ }
+
+ // Source/Target Link-layer Option fields.
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Type | Length | Link-Layer Address ...
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // Prefix Information Option fields.
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Type | Length | Prefix Length |L|A| Reserved1 |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Valid Lifetime |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Preferred Lifetime |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Reserved2 |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // + +
+ // | |
+ // + Prefix +
+ // | |
+ // + +
+ // | |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // Prefix length.
+ pub const PREFIX_LEN: usize = 2;
+ // Flags field of prefix header.
+ pub const FLAGS: usize = 3;
+ // Valid lifetime.
+ pub const VALID_LT: Field = 4..8;
+ // Preferred lifetime.
+ pub const PREF_LT: Field = 8..12;
+ // Reserved bits
+ pub const PREF_RESERVED: Field = 12..16;
+ // Prefix
+ pub const PREFIX: Field = 16..32;
+
+ // Redirected Header Option fields.
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Type | Length | Reserved |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Reserved |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // ~ IP header + data ~
+ // | |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // Reserved bits.
+ pub const REDIRECTED_RESERVED: Field = 2..8;
+ pub const REDIR_MIN_SZ: usize = 48;
+
+ // MTU Option fields
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Type | Length | Reserved |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | MTU |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ // MTU
+ pub const MTU: Field = 4..8;
+}
+
+/// Core getter methods relevant to any type of NDISC option.
+impl<T: AsRef<[u8]>> NdiscOption<T> {
+ /// Create a raw octet buffer with an NDISC Option structure.
+ pub const fn new_unchecked(buffer: T) -> NdiscOption<T> {
+ NdiscOption { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<NdiscOption<T>> {
+ let opt = Self::new_unchecked(buffer);
+ opt.check_len()?;
+
+ // A data length field of 0 is invalid.
+ if opt.data_len() == 0 {
+ return Err(Error);
+ }
+
+ Ok(opt)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ ///
+ /// The result of this check is invalidated by calling [set_data_len].
+ ///
+ /// [set_data_len]: #method.set_data_len
+ pub fn check_len(&self) -> Result<()> {
+ let data = self.buffer.as_ref();
+ let len = data.len();
+
+ if len < field::MIN_OPT_LEN {
+ Err(Error)
+ } else {
+ let data_range = field::DATA(data[field::LENGTH]);
+ if len < data_range.end {
+ Err(Error)
+ } else {
+ match self.option_type() {
+ Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()),
+ Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()),
+ Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()),
+ Type::Unknown(_) => Ok(()),
+ _ => Err(Error),
+ }
+ }
+ }
+ }
+
+ /// Consume the NDISC option, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the option type.
+ #[inline]
+ pub fn option_type(&self) -> Type {
+ let data = self.buffer.as_ref();
+ Type::from(data[field::TYPE])
+ }
+
+ /// Return the length of the data.
+ #[inline]
+ pub fn data_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ data[field::LENGTH]
+ }
+}
+
+/// Getter methods only relevant for Source/Target Link-layer Address options.
+impl<T: AsRef<[u8]>> NdiscOption<T> {
+ /// Return the Source/Target Link-layer Address.
+ #[inline]
+ pub fn link_layer_addr(&self) -> RawHardwareAddress {
+ let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2);
+ let data = self.buffer.as_ref();
+ RawHardwareAddress::from_bytes(&data[2..len + 2])
+ }
+}
+
+/// Getter methods only relevant for the MTU option.
+impl<T: AsRef<[u8]>> NdiscOption<T> {
+ /// Return the MTU value.
+ #[inline]
+ pub fn mtu(&self) -> u32 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u32(&data[field::MTU])
+ }
+}
+
+/// Getter methods only relevant for the Prefix Information option.
+impl<T: AsRef<[u8]>> NdiscOption<T> {
+ /// Return the prefix length.
+ #[inline]
+ pub fn prefix_len(&self) -> u8 {
+ self.buffer.as_ref()[field::PREFIX_LEN]
+ }
+
+ /// Return the prefix information flags.
+ #[inline]
+ pub fn prefix_flags(&self) -> PrefixInfoFlags {
+ PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS])
+ }
+
+ /// Return the valid lifetime of the prefix.
+ #[inline]
+ pub fn valid_lifetime(&self) -> Duration {
+ let data = self.buffer.as_ref();
+ Duration::from_secs(NetworkEndian::read_u32(&data[field::VALID_LT]) as u64)
+ }
+
+ /// Return the preferred lifetime of the prefix.
+ #[inline]
+ pub fn preferred_lifetime(&self) -> Duration {
+ let data = self.buffer.as_ref();
+ Duration::from_secs(NetworkEndian::read_u32(&data[field::PREF_LT]) as u64)
+ }
+
+ /// Return the prefix.
+ #[inline]
+ pub fn prefix(&self) -> Ipv6Address {
+ let data = self.buffer.as_ref();
+ Ipv6Address::from_bytes(&data[field::PREFIX])
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> NdiscOption<&'a T> {
+ /// Return the option data.
+ #[inline]
+ pub fn data(&self) -> &'a [u8] {
+ let len = self.data_len();
+ let data = self.buffer.as_ref();
+ &data[field::DATA(len)]
+ }
+}
+
+/// Core setter methods relevant to any type of NDISC option.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
+ /// Set the option type.
+ #[inline]
+ pub fn set_option_type(&mut self, value: Type) {
+ let data = self.buffer.as_mut();
+ data[field::TYPE] = value.into();
+ }
+
+ /// Set the option data length.
+ #[inline]
+ pub fn set_data_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ data[field::LENGTH] = value;
+ }
+}
+
+/// Setter methods only relevant for Source/Target Link-layer Address options.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
+ /// Set the Source/Target Link-layer Address.
+ #[inline]
+ pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) {
+ let data = self.buffer.as_mut();
+ data[2..2 + addr.len()].copy_from_slice(addr.as_bytes())
+ }
+}
+
+/// Setter methods only relevant for the MTU option.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
+ /// Set the MTU value.
+ #[inline]
+ pub fn set_mtu(&mut self, value: u32) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::MTU], value);
+ }
+}
+
+/// Setter methods only relevant for the Prefix Information option.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
+ /// Set the prefix length.
+ #[inline]
+ pub fn set_prefix_len(&mut self, value: u8) {
+ self.buffer.as_mut()[field::PREFIX_LEN] = value;
+ }
+
+ /// Set the prefix information flags.
+ #[inline]
+ pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) {
+ self.buffer.as_mut()[field::FLAGS] = flags.bits();
+ }
+
+ /// Set the valid lifetime of the prefix.
+ #[inline]
+ pub fn set_valid_lifetime(&mut self, time: Duration) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::VALID_LT], time.secs() as u32);
+ }
+
+ /// Set the preferred lifetime of the prefix.
+ #[inline]
+ pub fn set_preferred_lifetime(&mut self, time: Duration) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::PREF_LT], time.secs() as u32);
+ }
+
+ /// Clear the reserved bits.
+ #[inline]
+ pub fn clear_prefix_reserved(&mut self) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0);
+ }
+
+ /// Set the prefix.
+ #[inline]
+ pub fn set_prefix(&mut self, addr: Ipv6Address) {
+ let data = self.buffer.as_mut();
+ data[field::PREFIX].copy_from_slice(addr.as_bytes());
+ }
+}
+
+/// Setter methods only relevant for the Redirected Header option.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
+ /// Clear the reserved bits.
+ #[inline]
+ pub fn clear_redirected_reserved(&mut self) {
+ let data = self.buffer.as_mut();
+ data[field::REDIRECTED_RESERVED].fill_with(|| 0);
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&'a mut T> {
+ /// Return a mutable pointer to the option data.
+ #[inline]
+ pub fn data_mut(&mut self) -> &mut [u8] {
+ let len = self.data_len();
+ let data = self.buffer.as_mut();
+ &mut data[field::DATA(len)]
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match Repr::parse(self) {
+ Ok(repr) => write!(f, "{repr}"),
+ Err(err) => {
+ write!(f, "NDISC Option ({err})")?;
+ Ok(())
+ }
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PrefixInformation {
+ pub prefix_len: u8,
+ pub flags: PrefixInfoFlags,
+ pub valid_lifetime: Duration,
+ pub preferred_lifetime: Duration,
+ pub prefix: Ipv6Address,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct RedirectedHeader<'a> {
+ pub header: Ipv6Repr,
+ pub data: &'a [u8],
+}
+
+/// A high-level representation of an NDISC Option.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr<'a> {
+ SourceLinkLayerAddr(RawHardwareAddress),
+ TargetLinkLayerAddr(RawHardwareAddress),
+ PrefixInformation(PrefixInformation),
+ RedirectedHeader(RedirectedHeader<'a>),
+ Mtu(u32),
+ Unknown {
+ type_: u8,
+ length: u8,
+ data: &'a [u8],
+ },
+}
+
+impl<'a> Repr<'a> {
+ /// Parse an NDISC Option and return a high-level representation.
+ pub fn parse<T>(opt: &NdiscOption<&'a T>) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ match opt.option_type() {
+ Type::SourceLinkLayerAddr => {
+ if opt.data_len() >= 1 {
+ Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr()))
+ } else {
+ Err(Error)
+ }
+ }
+ Type::TargetLinkLayerAddr => {
+ if opt.data_len() >= 1 {
+ Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr()))
+ } else {
+ Err(Error)
+ }
+ }
+ Type::PrefixInformation => {
+ if opt.data_len() == 4 {
+ Ok(Repr::PrefixInformation(PrefixInformation {
+ prefix_len: opt.prefix_len(),
+ flags: opt.prefix_flags(),
+ valid_lifetime: opt.valid_lifetime(),
+ preferred_lifetime: opt.preferred_lifetime(),
+ prefix: opt.prefix(),
+ }))
+ } else {
+ Err(Error)
+ }
+ }
+ Type::RedirectedHeader => {
+ // If the options data length is less than 6, the option
+ // does not have enough data to fill out the IP header
+ // and common option fields.
+ if opt.data_len() < 6 {
+ Err(Error)
+ } else {
+ let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..];
+
+ let ip_packet = Ipv6Packet::new_checked(redirected_packet)?;
+ let ip_repr = Ipv6Repr::parse(&ip_packet)?;
+
+ Ok(Repr::RedirectedHeader(RedirectedHeader {
+ header: ip_repr,
+ data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len],
+ }))
+ }
+ }
+ Type::Mtu => {
+ if opt.data_len() == 1 {
+ Ok(Repr::Mtu(opt.mtu()))
+ } else {
+ Err(Error)
+ }
+ }
+ Type::Unknown(id) => {
+ // A length of 0 is invalid.
+ if opt.data_len() != 0 {
+ Ok(Repr::Unknown {
+ type_: id,
+ length: opt.data_len(),
+ data: opt.data(),
+ })
+ } else {
+ Err(Error)
+ }
+ }
+ }
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
+ let len = 2 + addr.len();
+ // Round up to next multiple of 8
+ (len + 7) / 8 * 8
+ }
+ &Repr::PrefixInformation(_) => field::PREFIX.end,
+ &Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
+ (8 + header.buffer_len() + data.len() + 7) / 8 * 8
+ }
+ &Repr::Mtu(_) => field::MTU.end,
+ &Repr::Unknown { length, .. } => field::DATA(length).end,
+ }
+ }
+
+ /// Emit a high-level representation into an NDISC Option.
+ pub fn emit<T>(&self, opt: &mut NdiscOption<&'a mut T>)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ match *self {
+ Repr::SourceLinkLayerAddr(addr) => {
+ opt.set_option_type(Type::SourceLinkLayerAddr);
+ let opt_len = addr.len() + 2;
+ opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
+ opt.set_link_layer_addr(addr);
+ }
+ Repr::TargetLinkLayerAddr(addr) => {
+ opt.set_option_type(Type::TargetLinkLayerAddr);
+ let opt_len = addr.len() + 2;
+ opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
+ opt.set_link_layer_addr(addr);
+ }
+ Repr::PrefixInformation(PrefixInformation {
+ prefix_len,
+ flags,
+ valid_lifetime,
+ preferred_lifetime,
+ prefix,
+ }) => {
+ opt.clear_prefix_reserved();
+ opt.set_option_type(Type::PrefixInformation);
+ opt.set_data_len(4);
+ opt.set_prefix_len(prefix_len);
+ opt.set_prefix_flags(flags);
+ opt.set_valid_lifetime(valid_lifetime);
+ opt.set_preferred_lifetime(preferred_lifetime);
+ opt.set_prefix(prefix);
+ }
+ Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
+ // TODO(thvdveld): I think we need to check if the data we are sending is not
+ // exceeding the MTU.
+ opt.clear_redirected_reserved();
+ opt.set_option_type(Type::RedirectedHeader);
+ opt.set_data_len((((8 + header.buffer_len() + data.len()) + 7) / 8) as u8);
+ let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..];
+ let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet);
+ header.emit(&mut ip_packet);
+ ip_packet.payload_mut().copy_from_slice(data);
+ }
+ Repr::Mtu(mtu) => {
+ opt.set_option_type(Type::Mtu);
+ opt.set_data_len(1);
+ opt.set_mtu(mtu);
+ }
+ Repr::Unknown {
+ type_: id,
+ length,
+ data,
+ } => {
+ opt.set_option_type(Type::Unknown(id));
+ opt.set_data_len(length);
+ opt.data_mut().copy_from_slice(data);
+ }
+ }
+ }
+}
+
+impl<'a> fmt::Display for Repr<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "NDISC Option: ")?;
+ match *self {
+ Repr::SourceLinkLayerAddr(addr) => {
+ write!(f, "SourceLinkLayer addr={addr}")
+ }
+ Repr::TargetLinkLayerAddr(addr) => {
+ write!(f, "TargetLinkLayer addr={addr}")
+ }
+ Repr::PrefixInformation(PrefixInformation {
+ prefix, prefix_len, ..
+ }) => {
+ write!(f, "PrefixInformation prefix={prefix}/{prefix_len}")
+ }
+ Repr::RedirectedHeader(RedirectedHeader { header, .. }) => {
+ write!(f, "RedirectedHeader header={header}")
+ }
+ Repr::Mtu(mtu) => {
+ write!(f, "MTU mtu={mtu}")
+ }
+ Repr::Unknown {
+ type_: id, length, ..
+ } => {
+ write!(f, "Unknown({id}) length={length}")
+ }
+ }
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ match NdiscOption::new_checked(buffer) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(ndisc) => match Repr::parse(&ndisc) {
+ Err(_) => Ok(()),
+ Ok(repr) => {
+ write!(f, "{indent}{repr}")
+ }
+ },
+ }
+ }
+}
+
+#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
+#[cfg(test)]
+mod test {
+ use super::Error;
+ use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type};
+ use crate::time::Duration;
+ use crate::wire::Ipv6Address;
+
+ #[cfg(feature = "medium-ethernet")]
+ use crate::wire::EthernetAddress;
+ #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
+ use crate::wire::Ieee802154Address;
+
+ static PREFIX_OPT_BYTES: [u8; 32] = [
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00,
+ 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ];
+
+ #[test]
+ fn test_deconstruct() {
+ let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]);
+ assert_eq!(opt.option_type(), Type::PrefixInformation);
+ assert_eq!(opt.data_len(), 4);
+ assert_eq!(opt.prefix_len(), 64);
+ assert_eq!(
+ opt.prefix_flags(),
+ PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF
+ );
+ assert_eq!(opt.valid_lifetime(), Duration::from_secs(900));
+ assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000));
+ assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
+ }
+
+ #[test]
+ fn test_construct() {
+ let mut bytes = [0x00; 32];
+ let mut opt = NdiscOption::new_unchecked(&mut bytes[..]);
+ opt.set_option_type(Type::PrefixInformation);
+ opt.set_data_len(4);
+ opt.set_prefix_len(64);
+ opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF);
+ opt.set_valid_lifetime(Duration::from_secs(900));
+ opt.set_preferred_lifetime(Duration::from_secs(1000));
+ opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
+ assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner());
+ }
+
+ #[test]
+ fn test_short_packet() {
+ assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error));
+ let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
+ assert_eq!(NdiscOption::new_checked(&bytes), Err(Error));
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ #[test]
+ fn test_repr_parse_link_layer_opt_ethernet() {
+ let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34];
+ let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]);
+ {
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&bytes)),
+ Ok(Repr::SourceLinkLayerAddr(addr.into()))
+ );
+ }
+ bytes[0] = 0x02;
+ {
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&bytes)),
+ Ok(Repr::TargetLinkLayerAddr(addr.into()))
+ );
+ }
+ }
+
+ #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
+ #[test]
+ fn test_repr_parse_link_layer_opt_ieee802154() {
+ let mut bytes = [
+ 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ ];
+ let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
+ {
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&bytes)),
+ Ok(Repr::SourceLinkLayerAddr(addr.into()))
+ );
+ }
+ bytes[0] = 0x02;
+ {
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&bytes)),
+ Ok(Repr::TargetLinkLayerAddr(addr.into()))
+ );
+ }
+ }
+
+ #[test]
+ fn test_repr_parse_prefix_info() {
+ let repr = Repr::PrefixInformation(PrefixInformation {
+ prefix_len: 64,
+ flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
+ valid_lifetime: Duration::from_secs(900),
+ preferred_lifetime: Duration::from_secs(1000),
+ prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ });
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)),
+ Ok(repr)
+ );
+ }
+
+ #[test]
+ fn test_repr_emit_prefix_info() {
+ let mut bytes = [0x2a; 32];
+ let repr = Repr::PrefixInformation(PrefixInformation {
+ prefix_len: 64,
+ flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
+ valid_lifetime: Duration::from_secs(900),
+ preferred_lifetime: Duration::from_secs(1000),
+ prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ });
+ let mut opt = NdiscOption::new_unchecked(&mut bytes);
+ repr.emit(&mut opt);
+ assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]);
+ }
+
+ #[test]
+ fn test_repr_parse_mtu() {
+ let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc];
+ assert_eq!(
+ Repr::parse(&NdiscOption::new_unchecked(&bytes)),
+ Ok(Repr::Mtu(1500))
+ );
+ }
+}
diff --git a/src/wire/pretty_print.rs b/src/wire/pretty_print.rs
new file mode 100644
index 0000000..fe7d8b8
--- /dev/null
+++ b/src/wire/pretty_print.rs
@@ -0,0 +1,126 @@
+/*! Pretty-printing of packet representation.
+
+The `pretty_print` module provides bits and pieces for printing concise,
+easily human readable packet listings.
+
+# Example
+
+A packet can be formatted using the `PrettyPrinter` wrapper:
+
+```rust
+use smoltcp::wire::*;
+let buffer = vec![
+ // Ethernet II
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x08, 0x00,
+ // IPv4
+ 0x45, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x01, 0xd2, 0x79,
+ 0x11, 0x12, 0x13, 0x14,
+ 0x21, 0x22, 0x23, 0x24,
+ // ICMPv4
+ 0x08, 0x00, 0x8e, 0xfe,
+ 0x12, 0x34, 0xab, 0xcd,
+ 0xaa, 0x00, 0x00, 0xff
+];
+
+let result = "\
+EthernetII src=11-12-13-14-15-16 dst=01-02-03-04-05-06 type=IPv4\n\
+\\ IPv4 src=17.18.19.20 dst=33.34.35.36 proto=ICMP (checksum incorrect)\n \
+ \\ ICMPv4 echo request id=4660 seq=43981 len=4\
+";
+
+#[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))]
+assert_eq!(
+ result,
+ &format!("{}", PrettyPrinter::<EthernetFrame<&'static [u8]>>::new("", &buffer))
+);
+```
+*/
+
+use core::fmt;
+use core::marker::PhantomData;
+
+/// Indentation state.
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PrettyIndent {
+ prefix: &'static str,
+ level: usize,
+}
+
+impl PrettyIndent {
+ /// Create an indentation state. The entire listing will be indented by the width
+ /// of `prefix`, and `prefix` will appear at the start of the first line.
+ pub fn new(prefix: &'static str) -> PrettyIndent {
+ PrettyIndent { prefix, level: 0 }
+ }
+
+ /// Increase indentation level.
+ pub fn increase(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f)?;
+ self.level += 1;
+ Ok(())
+ }
+}
+
+impl fmt::Display for PrettyIndent {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.level == 0 {
+ write!(f, "{}", self.prefix)
+ } else {
+ write!(f, "{0:1$}{0:2$}\\ ", "", self.prefix.len(), self.level - 1)
+ }
+ }
+}
+
+/// Interface for printing listings.
+pub trait PrettyPrint {
+ /// Write a concise, formatted representation of a packet contained in the provided
+ /// buffer, and any nested packets it may contain.
+ ///
+ /// `pretty_print` accepts a buffer and not a packet wrapper because the packet might
+ /// be truncated, and so it might not be possible to create the packet wrapper.
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ fmt: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result;
+}
+
+/// Wrapper for using a `PrettyPrint` where a `Display` is expected.
+pub struct PrettyPrinter<'a, T: PrettyPrint> {
+ prefix: &'static str,
+ buffer: &'a dyn AsRef<[u8]>,
+ phantom: PhantomData<T>,
+}
+
+impl<'a, T: PrettyPrint> PrettyPrinter<'a, T> {
+ /// Format the listing with the recorded parameters when Display::fmt is called.
+ pub fn new(prefix: &'static str, buffer: &'a dyn AsRef<[u8]>) -> PrettyPrinter<'a, T> {
+ PrettyPrinter {
+ prefix: prefix,
+ buffer: buffer,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl<'a, T: PrettyPrint + AsRef<[u8]>> PrettyPrinter<'a, T> {
+ /// Create a `PrettyPrinter` which prints the given object.
+ pub fn print(printable: &'a T) -> PrettyPrinter<'a, T> {
+ PrettyPrinter {
+ prefix: "",
+ buffer: printable,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl<'a, T: PrettyPrint> fmt::Display for PrettyPrinter<'a, T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ T::pretty_print(&self.buffer, f, &mut PrettyIndent::new(self.prefix))
+ }
+}
diff --git a/src/wire/rpl.rs b/src/wire/rpl.rs
new file mode 100644
index 0000000..0a8467c
--- /dev/null
+++ b/src/wire/rpl.rs
@@ -0,0 +1,2721 @@
+//! Implementation of the RPL packet formats. See [RFC 6550 § 6].
+//!
+//! [RFC 6550 § 6]: https://datatracker.ietf.org/doc/html/rfc6550#section-6
+
+use byteorder::{ByteOrder, NetworkEndian};
+
+use super::{Error, Result};
+use crate::wire::icmpv6::Packet;
+use crate::wire::ipv6::Address;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[repr(u8)]
+pub enum InstanceId {
+ Global(u8),
+ Local(u8),
+}
+
+impl From<u8> for InstanceId {
+ fn from(val: u8) -> Self {
+ const MASK: u8 = 0b0111_1111;
+
+ if ((val >> 7) & 0xb1) == 0b0 {
+ Self::Global(val & MASK)
+ } else {
+ Self::Local(val & MASK)
+ }
+ }
+}
+
+impl From<InstanceId> for u8 {
+ fn from(val: InstanceId) -> Self {
+ match val {
+ InstanceId::Global(val) => 0b0000_0000 | val,
+ InstanceId::Local(val) => 0b1000_0000 | val,
+ }
+ }
+}
+
+impl InstanceId {
+ /// Return the real part of the ID.
+ pub fn id(&self) -> u8 {
+ match self {
+ Self::Global(val) => *val,
+ Self::Local(val) => *val,
+ }
+ }
+
+ /// Returns `true` when the DODAG ID is the destination address of the IPv6 packet.
+ #[inline]
+ pub fn dodag_is_destination(&self) -> bool {
+ match self {
+ Self::Global(_) => false,
+ Self::Local(val) => ((val >> 6) & 0b1) == 0b1,
+ }
+ }
+
+ /// Returns `true` when the DODAG ID is the source address of the IPv6 packet.
+ ///
+ /// *NOTE*: this only makes sense when using a local RPL Instance ID and the packet is not a
+ /// RPL control message.
+ #[inline]
+ pub fn dodag_is_source(&self) -> bool {
+ !self.dodag_is_destination()
+ }
+}
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const RPL_INSTANCE_ID: usize = 4;
+
+ // DODAG information solicitation fields (DIS)
+ pub const DIS_FLAGS: usize = 4;
+ pub const DIS_RESERVED: usize = 5;
+
+ // DODAG information object fields (DIO)
+ pub const DIO_VERSION_NUMBER: usize = 5;
+ pub const DIO_RANK: Field = 6..8;
+ pub const DIO_GROUNDED: usize = 8;
+ pub const DIO_MOP: usize = 8;
+ pub const DIO_PRF: usize = 8;
+ pub const DIO_DTSN: usize = 9;
+ //pub const DIO_FLAGS: usize = 10;
+ //pub const DIO_RESERVED: usize = 11;
+ pub const DIO_DODAG_ID: Field = 12..12 + 16;
+
+ // Destination advertisement object (DAO)
+ pub const DAO_K: usize = 5;
+ pub const DAO_D: usize = 5;
+ //pub const DAO_FLAGS: usize = 5;
+ //pub const DAO_RESERVED: usize = 6;
+ pub const DAO_SEQUENCE: usize = 7;
+ pub const DAO_DODAG_ID: Field = 8..8 + 16;
+
+ // Destination advertisement object ack (DAO-ACK)
+ pub const DAO_ACK_D: usize = 5;
+ //pub const DAO_ACK_RESERVED: usize = 5;
+ pub const DAO_ACK_SEQUENCE: usize = 6;
+ pub const DAO_ACK_STATUS: usize = 7;
+ pub const DAO_ACK_DODAG_ID: Field = 8..8 + 16;
+}
+
+enum_with_unknown! {
+ /// RPL Control Message subtypes.
+ pub enum RplControlMessage(u8) {
+ DodagInformationSolicitation = 0x00,
+ DodagInformationObject = 0x01,
+ DestinationAdvertisementObject = 0x02,
+ DestinationAdvertisementObjectAck = 0x03,
+ SecureDodagInformationSolicitation = 0x80,
+ SecureDodagInformationObject = 0x81,
+ SecureDestinationAdvertisementObject = 0x82,
+ SecureDestinationAdvertisementObjectAck = 0x83,
+ ConsistencyCheck = 0x8a,
+ }
+}
+
+impl core::fmt::Display for RplControlMessage {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ RplControlMessage::DodagInformationSolicitation => {
+ write!(f, "DODAG information solicitation (DIS)")
+ }
+ RplControlMessage::DodagInformationObject => {
+ write!(f, "DODAG information object (DIO)")
+ }
+ RplControlMessage::DestinationAdvertisementObject => {
+ write!(f, "destination advertisement object (DAO)")
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck => write!(
+ f,
+ "destination advertisement object acknowledgement (DAO-ACK)"
+ ),
+ RplControlMessage::SecureDodagInformationSolicitation => {
+ write!(f, "secure DODAG information solicitation (DIS)")
+ }
+ RplControlMessage::SecureDodagInformationObject => {
+ write!(f, "secure DODAG information object (DIO)")
+ }
+ RplControlMessage::SecureDestinationAdvertisementObject => {
+ write!(f, "secure destination advertisement object (DAO)")
+ }
+ RplControlMessage::SecureDestinationAdvertisementObjectAck => write!(
+ f,
+ "secure destination advertisement object acknowledgement (DAO-ACK)"
+ ),
+ RplControlMessage::ConsistencyCheck => write!(f, "consistency check (CC)"),
+ RplControlMessage::Unknown(id) => write!(f, "{}", id),
+ }
+ }
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the RPL instance ID.
+ #[inline]
+ pub fn rpl_instance_id(&self) -> InstanceId {
+ get!(self.buffer, into: InstanceId, field: field::RPL_INSTANCE_ID)
+ }
+}
+
+impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
+ /// Return a pointer to the options.
+ pub fn options(&self) -> Result<&'p [u8]> {
+ let len = self.buffer.as_ref().len();
+ match RplControlMessage::from(self.msg_code()) {
+ RplControlMessage::DodagInformationSolicitation if len < field::DIS_RESERVED + 1 => {
+ return Err(Error)
+ }
+ RplControlMessage::DodagInformationObject if len < field::DIO_DODAG_ID.end => {
+ return Err(Error)
+ }
+ RplControlMessage::DestinationAdvertisementObject
+ if self.dao_dodag_id_present() && len < field::DAO_DODAG_ID.end =>
+ {
+ return Err(Error)
+ }
+ RplControlMessage::DestinationAdvertisementObject if len < field::DAO_SEQUENCE + 1 => {
+ return Err(Error)
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck
+ if self.dao_ack_dodag_id_present() && len < field::DAO_ACK_DODAG_ID.end =>
+ {
+ return Err(Error)
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck
+ if len < field::DAO_ACK_STATUS + 1 =>
+ {
+ return Err(Error)
+ }
+ RplControlMessage::SecureDodagInformationSolicitation
+ | RplControlMessage::SecureDodagInformationObject
+ | RplControlMessage::SecureDestinationAdvertisementObject
+ | RplControlMessage::SecureDestinationAdvertisementObjectAck
+ | RplControlMessage::ConsistencyCheck => return Err(Error),
+ RplControlMessage::Unknown(_) => return Err(Error),
+ _ => {}
+ }
+
+ let buffer = &self.buffer.as_ref();
+ Ok(match RplControlMessage::from(self.msg_code()) {
+ RplControlMessage::DodagInformationSolicitation => &buffer[field::DIS_RESERVED + 1..],
+ RplControlMessage::DodagInformationObject => &buffer[field::DIO_DODAG_ID.end..],
+ RplControlMessage::DestinationAdvertisementObject if self.dao_dodag_id_present() => {
+ &buffer[field::DAO_DODAG_ID.end..]
+ }
+ RplControlMessage::DestinationAdvertisementObject => &buffer[field::DAO_SEQUENCE + 1..],
+ RplControlMessage::DestinationAdvertisementObjectAck
+ if self.dao_ack_dodag_id_present() =>
+ {
+ &buffer[field::DAO_ACK_DODAG_ID.end..]
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck => {
+ &buffer[field::DAO_ACK_STATUS + 1..]
+ }
+ RplControlMessage::SecureDodagInformationSolicitation
+ | RplControlMessage::SecureDodagInformationObject
+ | RplControlMessage::SecureDestinationAdvertisementObject
+ | RplControlMessage::SecureDestinationAdvertisementObjectAck
+ | RplControlMessage::ConsistencyCheck => unreachable!(),
+ RplControlMessage::Unknown(_) => unreachable!(),
+ })
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the RPL Instance ID field.
+ #[inline]
+ pub fn set_rpl_instance_id(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::RPL_INSTANCE_ID)
+ }
+}
+
+impl<'p, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'p mut T> {
+ /// Return a pointer to the options.
+ pub fn options_mut(&mut self) -> &mut [u8] {
+ match RplControlMessage::from(self.msg_code()) {
+ RplControlMessage::DodagInformationSolicitation => {
+ &mut self.buffer.as_mut()[field::DIS_RESERVED + 1..]
+ }
+ RplControlMessage::DodagInformationObject => {
+ &mut self.buffer.as_mut()[field::DIO_DODAG_ID.end..]
+ }
+ RplControlMessage::DestinationAdvertisementObject => {
+ if self.dao_dodag_id_present() {
+ &mut self.buffer.as_mut()[field::DAO_DODAG_ID.end..]
+ } else {
+ &mut self.buffer.as_mut()[field::DAO_SEQUENCE + 1..]
+ }
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck => {
+ if self.dao_ack_dodag_id_present() {
+ &mut self.buffer.as_mut()[field::DAO_ACK_DODAG_ID.end..]
+ } else {
+ &mut self.buffer.as_mut()[field::DAO_ACK_STATUS + 1..]
+ }
+ }
+ RplControlMessage::SecureDodagInformationSolicitation
+ | RplControlMessage::SecureDodagInformationObject
+ | RplControlMessage::SecureDestinationAdvertisementObject
+ | RplControlMessage::SecureDestinationAdvertisementObjectAck
+ | RplControlMessage::ConsistencyCheck => todo!("Secure messages not supported"),
+ RplControlMessage::Unknown(_) => todo!(),
+ }
+ }
+}
+
+/// Getters for the DODAG information solicitation (DIS) message.
+///
+/// ```txt
+/// 0 1 2
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | Flags | Reserved | Option(s)...
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// ```
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the DIS flags field.
+ #[inline]
+ pub fn dis_flags(&self) -> u8 {
+ get!(self.buffer, field: field::DIS_FLAGS)
+ }
+
+ /// Return the DIS reserved field.
+ #[inline]
+ pub fn dis_reserved(&self) -> u8 {
+ get!(self.buffer, field: field::DIS_RESERVED)
+ }
+}
+
+/// Setters for the DODAG information solicitation (DIS) message.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the DIS flags field.
+ pub fn clear_dis_flags(&mut self) {
+ self.buffer.as_mut()[field::DIS_FLAGS] = 0;
+ }
+
+ /// Clear the DIS rserved field.
+ pub fn clear_dis_reserved(&mut self) {
+ self.buffer.as_mut()[field::DIS_RESERVED] = 0;
+ }
+}
+
+enum_with_unknown! {
+ pub enum ModeOfOperation(u8) {
+ NoDownwardRoutesMaintained = 0x00,
+ NonStoringMode = 0x01,
+ StoringModeWithoutMulticast = 0x02,
+ StoringModeWithMulticast = 0x03,
+ }
+}
+
+impl Default for ModeOfOperation {
+ fn default() -> Self {
+ Self::StoringModeWithoutMulticast
+ }
+}
+
+/// Getters for the DODAG information object (DIO) message.
+///
+/// ```txt
+/// 0 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | RPLInstanceID |Version Number | Rank |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// |G|0| MOP | Prf | DTSN | Flags | Reserved |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | |
+/// + +
+/// | |
+/// + DODAGID +
+/// | |
+/// + +
+/// | |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | Option(s)...
+/// +-+-+-+-+-+-+-+-+
+/// ```
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Version Number field.
+ #[inline]
+ pub fn dio_version_number(&self) -> u8 {
+ get!(self.buffer, field: field::DIO_VERSION_NUMBER)
+ }
+
+ /// Return the Rank field.
+ #[inline]
+ pub fn dio_rank(&self) -> u16 {
+ get!(self.buffer, u16, field: field::DIO_RANK)
+ }
+
+ /// Return the value of the Grounded flag.
+ #[inline]
+ pub fn dio_grounded(&self) -> bool {
+ get!(self.buffer, bool, field: field::DIO_GROUNDED, shift: 7, mask: 0b01)
+ }
+
+ /// Return the mode of operation field.
+ #[inline]
+ pub fn dio_mode_of_operation(&self) -> ModeOfOperation {
+ get!(self.buffer, into: ModeOfOperation, field: field::DIO_MOP, shift: 3, mask: 0b111)
+ }
+
+ /// Return the DODAG preference field.
+ #[inline]
+ pub fn dio_dodag_preference(&self) -> u8 {
+ get!(self.buffer, field: field::DIO_PRF, mask: 0b111)
+ }
+
+ /// Return the destination advertisement trigger sequence number.
+ #[inline]
+ pub fn dio_dest_adv_trigger_seq_number(&self) -> u8 {
+ get!(self.buffer, field: field::DIO_DTSN)
+ }
+
+ /// Return the DODAG id, which is an IPv6 address.
+ #[inline]
+ pub fn dio_dodag_id(&self) -> Address {
+ get!(
+ self.buffer,
+ into: Address,
+ fun: from_bytes,
+ field: field::DIO_DODAG_ID
+ )
+ }
+}
+
+/// Setters for the DODAG information object (DIO) message.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Version Number field.
+ #[inline]
+ pub fn set_dio_version_number(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DIO_VERSION_NUMBER)
+ }
+
+ /// Set the Rank field.
+ #[inline]
+ pub fn set_dio_rank(&mut self, value: u16) {
+ set!(self.buffer, value, u16, field: field::DIO_RANK)
+ }
+
+ /// Set the value of the Grounded flag.
+ #[inline]
+ pub fn set_dio_grounded(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::DIO_GROUNDED, shift: 7, mask: 0b01)
+ }
+
+ /// Set the mode of operation field.
+ #[inline]
+ pub fn set_dio_mode_of_operation(&mut self, mode: ModeOfOperation) {
+ let raw = (self.buffer.as_ref()[field::DIO_MOP] & !(0b111 << 3)) | (u8::from(mode) << 3);
+ self.buffer.as_mut()[field::DIO_MOP] = raw;
+ }
+
+ /// Set the DODAG preference field.
+ #[inline]
+ pub fn set_dio_dodag_preference(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DIO_PRF, mask: 0b111)
+ }
+
+ /// Set the destination advertisement trigger sequence number.
+ #[inline]
+ pub fn set_dio_dest_adv_trigger_seq_number(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DIO_DTSN)
+ }
+
+ /// Set the DODAG id, which is an IPv6 address.
+ #[inline]
+ pub fn set_dio_dodag_id(&mut self, address: Address) {
+ set!(self.buffer, address: address, field: field::DIO_DODAG_ID)
+ }
+}
+
+/// Getters for the Destination Advertisement Object (DAO) message.
+///
+/// ```txt
+/// 0 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | RPLInstanceID |K|D| Flags | Reserved | DAOSequence |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | |
+/// + +
+/// | |
+/// + DODAGID* +
+/// | |
+/// + +
+/// | |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | Option(s)...
+/// +-+-+-+-+-+-+-+-+
+/// ```
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Returns the Expect DAO-ACK flag.
+ #[inline]
+ pub fn dao_ack_request(&self) -> bool {
+ get!(self.buffer, bool, field: field::DAO_K, shift: 7, mask: 0b1)
+ }
+
+ /// Returns the flag indicating that the DODAG ID is present or not.
+ #[inline]
+ pub fn dao_dodag_id_present(&self) -> bool {
+ get!(self.buffer, bool, field: field::DAO_D, shift: 6, mask: 0b1)
+ }
+
+ /// Returns the DODAG sequence flag.
+ #[inline]
+ pub fn dao_dodag_sequence(&self) -> u8 {
+ get!(self.buffer, field: field::DAO_SEQUENCE)
+ }
+
+ /// Returns the DODAG ID, an IPv6 address, when it is present.
+ #[inline]
+ pub fn dao_dodag_id(&self) -> Option<Address> {
+ if self.dao_dodag_id_present() {
+ Some(Address::from_bytes(
+ &self.buffer.as_ref()[field::DAO_DODAG_ID],
+ ))
+ } else {
+ None
+ }
+ }
+}
+
+/// Setters for the Destination Advertisement Object (DAO) message.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Expect DAO-ACK flag.
+ #[inline]
+ pub fn set_dao_ack_request(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::DAO_K, shift: 7, mask: 0b1,)
+ }
+
+ /// Set the flag indicating that the DODAG ID is present or not.
+ #[inline]
+ pub fn set_dao_dodag_id_present(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::DAO_D, shift: 6, mask: 0b1)
+ }
+
+ /// Set the DODAG sequence flag.
+ #[inline]
+ pub fn set_dao_dodag_sequence(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DAO_SEQUENCE)
+ }
+
+ /// Set the DODAG ID.
+ #[inline]
+ pub fn set_dao_dodag_id(&mut self, address: Option<Address>) {
+ match address {
+ Some(address) => {
+ self.buffer.as_mut()[field::DAO_DODAG_ID].copy_from_slice(address.as_bytes());
+ self.set_dao_dodag_id_present(true);
+ }
+ None => {
+ self.set_dao_dodag_id_present(false);
+ }
+ }
+ }
+}
+
+/// Getters for the Destination Advertisement Object acknowledgement (DAO-ACK) message.
+///
+/// ```txt
+/// 0 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | RPLInstanceID |D| Reserved | DAOSequence | Status |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | |
+/// + +
+/// | |
+/// + DODAGID* +
+/// | |
+/// + +
+/// | |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// | Option(s)...
+/// +-+-+-+-+-+-+-+-+
+/// ```
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Returns the flag indicating that the DODAG ID is present or not.
+ #[inline]
+ pub fn dao_ack_dodag_id_present(&self) -> bool {
+ get!(self.buffer, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1)
+ }
+
+ /// Return the DODAG sequence number.
+ #[inline]
+ pub fn dao_ack_sequence(&self) -> u8 {
+ get!(self.buffer, field: field::DAO_ACK_SEQUENCE)
+ }
+
+ /// Return the DOA status field.
+ #[inline]
+ pub fn dao_ack_status(&self) -> u8 {
+ get!(self.buffer, field: field::DAO_ACK_STATUS)
+ }
+
+ /// Returns the DODAG ID, an IPv6 address, when it is present.
+ #[inline]
+ pub fn dao_ack_dodag_id(&self) -> Option<Address> {
+ if self.dao_ack_dodag_id_present() {
+ Some(Address::from_bytes(
+ &self.buffer.as_ref()[field::DAO_ACK_DODAG_ID],
+ ))
+ } else {
+ None
+ }
+ }
+}
+
+/// Setters for the Destination Advertisement Object acknowledgement (DAO-ACK) message.
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the flag indicating that the DODAG ID is present or not.
+ #[inline]
+ pub fn set_dao_ack_dodag_id_present(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1)
+ }
+
+ /// Set the DODAG sequence number.
+ #[inline]
+ pub fn set_dao_ack_sequence(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DAO_ACK_SEQUENCE)
+ }
+
+ /// Set the DOA status field.
+ #[inline]
+ pub fn set_dao_ack_status(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::DAO_ACK_STATUS)
+ }
+
+ /// Set the DODAG ID.
+ #[inline]
+ pub fn set_dao_ack_dodag_id(&mut self, address: Option<Address>) {
+ match address {
+ Some(address) => {
+ self.buffer.as_mut()[field::DAO_ACK_DODAG_ID].copy_from_slice(address.as_bytes());
+ self.set_dao_ack_dodag_id_present(true);
+ }
+ None => {
+ self.set_dao_ack_dodag_id_present(false);
+ }
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Repr<'p> {
+ DodagInformationSolicitation {
+ options: &'p [u8],
+ },
+ DodagInformationObject {
+ rpl_instance_id: InstanceId,
+ version_number: u8,
+ rank: u16,
+ grounded: bool,
+ mode_of_operation: ModeOfOperation,
+ dodag_preference: u8,
+ dtsn: u8,
+ dodag_id: Address,
+ options: &'p [u8],
+ },
+ DestinationAdvertisementObject {
+ rpl_instance_id: InstanceId,
+ expect_ack: bool,
+ sequence: u8,
+ dodag_id: Option<Address>,
+ options: &'p [u8],
+ },
+ DestinationAdvertisementObjectAck {
+ rpl_instance_id: InstanceId,
+ sequence: u8,
+ status: u8,
+ dodag_id: Option<Address>,
+ },
+}
+
+impl core::fmt::Display for Repr<'_> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Repr::DodagInformationSolicitation { .. } => {
+ write!(f, "DIS")?;
+ }
+ Repr::DodagInformationObject {
+ rpl_instance_id,
+ version_number,
+ rank,
+ grounded,
+ mode_of_operation,
+ dodag_preference,
+ dtsn,
+ dodag_id,
+ ..
+ } => {
+ write!(
+ f,
+ "DIO \
+ IID={rpl_instance_id:?} \
+ V={version_number} \
+ R={rank} \
+ G={grounded} \
+ MOP={mode_of_operation:?} \
+ Pref={dodag_preference} \
+ DTSN={dtsn} \
+ DODAGID={dodag_id}"
+ )?;
+ }
+ Repr::DestinationAdvertisementObject {
+ rpl_instance_id,
+ expect_ack,
+ sequence,
+ dodag_id,
+ ..
+ } => {
+ write!(
+ f,
+ "DAO \
+ IID={rpl_instance_id:?} \
+ Ack={expect_ack} \
+ Seq={sequence} \
+ DODAGID={dodag_id:?}",
+ )?;
+ }
+ Repr::DestinationAdvertisementObjectAck {
+ rpl_instance_id,
+ sequence,
+ status,
+ dodag_id,
+ ..
+ } => {
+ write!(
+ f,
+ "DAO-ACK \
+ IID={rpl_instance_id:?} \
+ Seq={sequence} \
+ Status={status} \
+ DODAGID={dodag_id:?}",
+ )?;
+ }
+ };
+
+ Ok(())
+ }
+}
+
+impl<'p> Repr<'p> {
+ pub fn set_options(&mut self, options: &'p [u8]) {
+ let opts = match self {
+ Repr::DodagInformationSolicitation { options } => options,
+ Repr::DodagInformationObject { options, .. } => options,
+ Repr::DestinationAdvertisementObject { options, .. } => options,
+ Repr::DestinationAdvertisementObjectAck { .. } => unreachable!(),
+ };
+
+ *opts = options;
+ }
+
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&'p T>) -> Result<Self> {
+ let options = packet.options()?;
+ match RplControlMessage::from(packet.msg_code()) {
+ RplControlMessage::DodagInformationSolicitation => {
+ Ok(Repr::DodagInformationSolicitation { options })
+ }
+ RplControlMessage::DodagInformationObject => Ok(Repr::DodagInformationObject {
+ rpl_instance_id: packet.rpl_instance_id(),
+ version_number: packet.dio_version_number(),
+ rank: packet.dio_rank(),
+ grounded: packet.dio_grounded(),
+ mode_of_operation: packet.dio_mode_of_operation(),
+ dodag_preference: packet.dio_dodag_preference(),
+ dtsn: packet.dio_dest_adv_trigger_seq_number(),
+ dodag_id: packet.dio_dodag_id(),
+ options,
+ }),
+ RplControlMessage::DestinationAdvertisementObject => {
+ Ok(Repr::DestinationAdvertisementObject {
+ rpl_instance_id: packet.rpl_instance_id(),
+ expect_ack: packet.dao_ack_request(),
+ sequence: packet.dao_dodag_sequence(),
+ dodag_id: packet.dao_dodag_id(),
+ options,
+ })
+ }
+ RplControlMessage::DestinationAdvertisementObjectAck => {
+ Ok(Repr::DestinationAdvertisementObjectAck {
+ rpl_instance_id: packet.rpl_instance_id(),
+ sequence: packet.dao_ack_sequence(),
+ status: packet.dao_ack_status(),
+ dodag_id: packet.dao_ack_dodag_id(),
+ })
+ }
+ RplControlMessage::SecureDodagInformationSolicitation
+ | RplControlMessage::SecureDodagInformationObject
+ | RplControlMessage::SecureDestinationAdvertisementObject
+ | RplControlMessage::SecureDestinationAdvertisementObjectAck
+ | RplControlMessage::ConsistencyCheck => Err(Error),
+ RplControlMessage::Unknown(_) => Err(Error),
+ }
+ }
+
+ pub fn buffer_len(&self) -> usize {
+ let mut len = 4 + match self {
+ Repr::DodagInformationSolicitation { .. } => 2,
+ Repr::DodagInformationObject { .. } => 24,
+ Repr::DestinationAdvertisementObject { dodag_id, .. } => {
+ if dodag_id.is_some() {
+ 20
+ } else {
+ 4
+ }
+ }
+ Repr::DestinationAdvertisementObjectAck { dodag_id, .. } => {
+ if dodag_id.is_some() {
+ 20
+ } else {
+ 4
+ }
+ }
+ };
+
+ let opts = match self {
+ Repr::DodagInformationSolicitation { options } => &options[..],
+ Repr::DodagInformationObject { options, .. } => &options[..],
+ Repr::DestinationAdvertisementObject { options, .. } => &options[..],
+ Repr::DestinationAdvertisementObjectAck { .. } => &[],
+ };
+
+ len += opts.len();
+
+ len
+ }
+
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&mut T>) {
+ packet.set_msg_type(crate::wire::icmpv6::Message::RplControl);
+
+ match self {
+ Repr::DodagInformationSolicitation { .. } => {
+ packet.set_msg_code(RplControlMessage::DodagInformationSolicitation.into());
+ packet.clear_dis_flags();
+ packet.clear_dis_reserved();
+ }
+ Repr::DodagInformationObject {
+ rpl_instance_id,
+ version_number,
+ rank,
+ grounded,
+ mode_of_operation,
+ dodag_preference,
+ dtsn,
+ dodag_id,
+ ..
+ } => {
+ packet.set_msg_code(RplControlMessage::DodagInformationObject.into());
+ packet.set_rpl_instance_id((*rpl_instance_id).into());
+ packet.set_dio_version_number(*version_number);
+ packet.set_dio_rank(*rank);
+ packet.set_dio_grounded(*grounded);
+ packet.set_dio_mode_of_operation(*mode_of_operation);
+ packet.set_dio_dodag_preference(*dodag_preference);
+ packet.set_dio_dest_adv_trigger_seq_number(*dtsn);
+ packet.set_dio_dodag_id(*dodag_id);
+ }
+ Repr::DestinationAdvertisementObject {
+ rpl_instance_id,
+ expect_ack,
+ sequence,
+ dodag_id,
+ ..
+ } => {
+ packet.set_msg_code(RplControlMessage::DestinationAdvertisementObject.into());
+ packet.set_rpl_instance_id((*rpl_instance_id).into());
+ packet.set_dao_ack_request(*expect_ack);
+ packet.set_dao_dodag_sequence(*sequence);
+ packet.set_dao_dodag_id(*dodag_id);
+ }
+ Repr::DestinationAdvertisementObjectAck {
+ rpl_instance_id,
+ sequence,
+ status,
+ dodag_id,
+ ..
+ } => {
+ packet.set_msg_code(RplControlMessage::DestinationAdvertisementObjectAck.into());
+ packet.set_rpl_instance_id((*rpl_instance_id).into());
+ packet.set_dao_ack_sequence(*sequence);
+ packet.set_dao_ack_status(*status);
+ packet.set_dao_ack_dodag_id(*dodag_id);
+ }
+ }
+
+ let options = match self {
+ Repr::DodagInformationSolicitation { options } => &options[..],
+ Repr::DodagInformationObject { options, .. } => &options[..],
+ Repr::DestinationAdvertisementObject { options, .. } => &options[..],
+ Repr::DestinationAdvertisementObjectAck { .. } => &[],
+ };
+
+ packet.options_mut().copy_from_slice(options);
+ }
+}
+
+pub mod options {
+ use byteorder::{ByteOrder, NetworkEndian};
+
+ use super::{Error, InstanceId, Result};
+ use crate::wire::ipv6::Address;
+
+ /// A read/write wrapper around a RPL Control Message Option.
+ #[derive(Debug, Clone)]
+ pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+ }
+
+ enum_with_unknown! {
+ pub enum OptionType(u8) {
+ Pad1 = 0x00,
+ PadN = 0x01,
+ DagMetricContainer = 0x02,
+ RouteInformation = 0x03,
+ DodagConfiguration = 0x04,
+ RplTarget = 0x05,
+ TransitInformation = 0x06,
+ SolicitedInformation = 0x07,
+ PrefixInformation = 0x08,
+ RplTargetDescriptor = 0x09,
+ }
+ }
+
+ impl From<&Repr<'_>> for OptionType {
+ fn from(repr: &Repr) -> Self {
+ match repr {
+ Repr::Pad1 => Self::Pad1,
+ Repr::PadN(_) => Self::PadN,
+ Repr::DagMetricContainer => Self::DagMetricContainer,
+ Repr::RouteInformation { .. } => Self::RouteInformation,
+ Repr::DodagConfiguration { .. } => Self::DodagConfiguration,
+ Repr::RplTarget { .. } => Self::RplTarget,
+ Repr::TransitInformation { .. } => Self::TransitInformation,
+ Repr::SolicitedInformation { .. } => Self::SolicitedInformation,
+ Repr::PrefixInformation { .. } => Self::PrefixInformation,
+ Repr::RplTargetDescriptor { .. } => Self::RplTargetDescriptor,
+ }
+ }
+ }
+
+ mod field {
+ use crate::wire::field::*;
+
+ // Generic fields.
+ pub const TYPE: usize = 0;
+ pub const LENGTH: usize = 1;
+
+ pub const PADN: Rest = 2..;
+
+ // Route Information fields.
+ pub const ROUTE_INFO_PREFIX_LENGTH: usize = 2;
+ pub const ROUTE_INFO_RESERVED: usize = 3;
+ pub const ROUTE_INFO_PREFERENCE: usize = 3;
+ pub const ROUTE_INFO_LIFETIME: Field = 4..9;
+
+ // DODAG Configuration fields.
+ pub const DODAG_CONF_FLAGS: usize = 2;
+ pub const DODAG_CONF_AUTHENTICATION_ENABLED: usize = 2;
+ pub const DODAG_CONF_PATH_CONTROL_SIZE: usize = 2;
+ pub const DODAG_CONF_DIO_INTERVAL_DOUBLINGS: usize = 3;
+ pub const DODAG_CONF_DIO_INTERVAL_MINIMUM: usize = 4;
+ pub const DODAG_CONF_DIO_REDUNDANCY_CONSTANT: usize = 5;
+ pub const DODAG_CONF_DIO_MAX_RANK_INCREASE: Field = 6..8;
+ pub const DODAG_CONF_MIN_HOP_RANK_INCREASE: Field = 8..10;
+ pub const DODAG_CONF_OBJECTIVE_CODE_POINT: Field = 10..12;
+ pub const DODAG_CONF_DEFAULT_LIFETIME: usize = 13;
+ pub const DODAG_CONF_LIFETIME_UNIT: Field = 14..16;
+
+ // RPL Target fields.
+ pub const RPL_TARGET_FLAGS: usize = 2;
+ pub const RPL_TARGET_PREFIX_LENGTH: usize = 3;
+
+ // Transit Information fields.
+ pub const TRANSIT_INFO_FLAGS: usize = 2;
+ pub const TRANSIT_INFO_EXTERNAL: usize = 2;
+ pub const TRANSIT_INFO_PATH_CONTROL: usize = 3;
+ pub const TRANSIT_INFO_PATH_SEQUENCE: usize = 4;
+ pub const TRANSIT_INFO_PATH_LIFETIME: usize = 5;
+ pub const TRANSIT_INFO_PARENT_ADDRESS: Field = 6..6 + 16;
+
+ // Solicited Information fields.
+ pub const SOLICITED_INFO_RPL_INSTANCE_ID: usize = 2;
+ pub const SOLICITED_INFO_FLAGS: usize = 3;
+ pub const SOLICITED_INFO_VERSION_PREDICATE: usize = 3;
+ pub const SOLICITED_INFO_INSTANCE_ID_PREDICATE: usize = 3;
+ pub const SOLICITED_INFO_DODAG_ID_PREDICATE: usize = 3;
+ pub const SOLICITED_INFO_DODAG_ID: Field = 4..20;
+ pub const SOLICITED_INFO_VERSION_NUMBER: usize = 20;
+
+ // Prefix Information fields.
+ pub const PREFIX_INFO_PREFIX_LENGTH: usize = 2;
+ pub const PREFIX_INFO_RESERVED1: usize = 3;
+ pub const PREFIX_INFO_ON_LINK: usize = 3;
+ pub const PREFIX_INFO_AUTONOMOUS_CONF: usize = 3;
+ pub const PREFIX_INFO_ROUTER_ADDRESS_FLAG: usize = 3;
+ pub const PREFIX_INFO_VALID_LIFETIME: Field = 4..8;
+ pub const PREFIX_INFO_PREFERRED_LIFETIME: Field = 8..12;
+ pub const PREFIX_INFO_RESERVED2: Field = 12..16;
+ pub const PREFIX_INFO_PREFIX: Field = 16..16 + 16;
+
+ // RPL Target Descriptor fields.
+ pub const TARGET_DESCRIPTOR: Field = 2..6;
+ }
+
+ /// Getters for the RPL Control Message Options.
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with RPL Control Message Option structure.
+ #[inline]
+ pub fn new_unchecked(buffer: T) -> Self {
+ Packet { buffer }
+ }
+
+ #[inline]
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ if buffer.as_ref().is_empty() {
+ return Err(Error);
+ }
+
+ Ok(Packet { buffer })
+ }
+
+ /// Return the type field.
+ #[inline]
+ pub fn option_type(&self) -> OptionType {
+ OptionType::from(self.buffer.as_ref()[field::TYPE])
+ }
+
+ /// Return the length field.
+ #[inline]
+ pub fn option_length(&self) -> u8 {
+ get!(self.buffer, field: field::LENGTH)
+ }
+ }
+
+ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
+ /// Return a pointer to the next option.
+ #[inline]
+ pub fn next_option(&self) -> Option<&'p [u8]> {
+ if !self.buffer.as_ref().is_empty() {
+ match self.option_type() {
+ OptionType::Pad1 => Some(&self.buffer.as_ref()[1..]),
+ OptionType::Unknown(_) => unreachable!(),
+ _ => {
+ let len = self.option_length();
+ Some(&self.buffer.as_ref()[2 + len as usize..])
+ }
+ }
+ } else {
+ None
+ }
+ }
+ }
+
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Option Type field.
+ #[inline]
+ pub fn set_option_type(&mut self, option_type: OptionType) {
+ self.buffer.as_mut()[field::TYPE] = option_type.into();
+ }
+
+ /// Set the Option Length field.
+ #[inline]
+ pub fn set_option_length(&mut self, length: u8) {
+ self.buffer.as_mut()[field::LENGTH] = length;
+ }
+ }
+
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ #[inline]
+ pub fn clear_padn(&mut self, size: u8) {
+ for b in &mut self.buffer.as_mut()[field::PADN][..size as usize] {
+ *b = 0;
+ }
+ }
+ }
+
+ /// Getters for the DAG Metric Container Option Message.
+
+ /// Getters for the Route Information Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x03 | Option Length | Prefix Length |Resvd|Prf|Resvd|
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Route Lifetime |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | |
+ /// . Prefix (Variable Length) .
+ /// . .
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Prefix Length field.
+ #[inline]
+ pub fn prefix_length(&self) -> u8 {
+ get!(self.buffer, field: field::ROUTE_INFO_PREFIX_LENGTH)
+ }
+
+ /// Return the Route Preference field.
+ #[inline]
+ pub fn route_preference(&self) -> u8 {
+ (self.buffer.as_ref()[field::ROUTE_INFO_PREFERENCE] & 0b0001_1000) >> 3
+ }
+
+ /// Return the Route Lifetime field.
+ #[inline]
+ pub fn route_lifetime(&self) -> u32 {
+ get!(self.buffer, u32, field: field::ROUTE_INFO_LIFETIME)
+ }
+ }
+
+ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
+ /// Return the Prefix field.
+ #[inline]
+ pub fn prefix(&self) -> &'p [u8] {
+ let option_len = self.option_length();
+ &self.buffer.as_ref()[field::ROUTE_INFO_LIFETIME.end..]
+ [..option_len as usize - field::ROUTE_INFO_LIFETIME.end]
+ }
+ }
+
+ /// Setters for the Route Information Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Prefix Length field.
+ #[inline]
+ pub fn set_route_info_prefix_length(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::ROUTE_INFO_PREFIX_LENGTH)
+ }
+
+ /// Set the Route Preference field.
+ #[inline]
+ pub fn set_route_info_route_preference(&mut self, _value: u8) {
+ todo!();
+ }
+
+ /// Set the Route Lifetime field.
+ #[inline]
+ pub fn set_route_info_route_lifetime(&mut self, value: u32) {
+ set!(self.buffer, value, u32, field: field::ROUTE_INFO_LIFETIME)
+ }
+
+ /// Set the prefix field.
+ #[inline]
+ pub fn set_route_info_prefix(&mut self, _prefix: &[u8]) {
+ todo!();
+ }
+
+ /// Clear the reserved field.
+ #[inline]
+ pub fn clear_route_info_reserved(&mut self) {
+ self.buffer.as_mut()[field::ROUTE_INFO_RESERVED] = 0;
+ }
+ }
+
+ /// Getters for the DODAG Configuration Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x04 |Opt Length = 14| Flags |A| PCS | DIOIntDoubl. |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | DIOIntMin. | DIORedun. | MaxRankIncrease |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | MinHopRankIncrease | OCP |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Reserved | Def. Lifetime | Lifetime Unit |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Authentication Enabled field.
+ #[inline]
+ pub fn authentication_enabled(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::DODAG_CONF_AUTHENTICATION_ENABLED,
+ shift: 3,
+ mask: 0b1
+ )
+ }
+
+ /// Return the Path Control Size field.
+ #[inline]
+ pub fn path_control_size(&self) -> u8 {
+ get!(self.buffer, field: field::DODAG_CONF_PATH_CONTROL_SIZE, mask: 0b111)
+ }
+
+ /// Return the DIO Interval Doublings field.
+ #[inline]
+ pub fn dio_interval_doublings(&self) -> u8 {
+ get!(self.buffer, field: field::DODAG_CONF_DIO_INTERVAL_DOUBLINGS)
+ }
+
+ /// Return the DIO Interval Minimum field.
+ #[inline]
+ pub fn dio_interval_minimum(&self) -> u8 {
+ get!(self.buffer, field: field::DODAG_CONF_DIO_INTERVAL_MINIMUM)
+ }
+
+ /// Return the DIO Redundancy Constant field.
+ #[inline]
+ pub fn dio_redundancy_constant(&self) -> u8 {
+ get!(
+ self.buffer,
+ field: field::DODAG_CONF_DIO_REDUNDANCY_CONSTANT
+ )
+ }
+
+ /// Return the Max Rank Increase field.
+ #[inline]
+ pub fn max_rank_increase(&self) -> u16 {
+ get!(
+ self.buffer,
+ u16,
+ field: field::DODAG_CONF_DIO_MAX_RANK_INCREASE
+ )
+ }
+
+ /// Return the Minimum Hop Rank Increase field.
+ #[inline]
+ pub fn minimum_hop_rank_increase(&self) -> u16 {
+ get!(
+ self.buffer,
+ u16,
+ field: field::DODAG_CONF_MIN_HOP_RANK_INCREASE
+ )
+ }
+
+ /// Return the Objective Code Point field.
+ #[inline]
+ pub fn objective_code_point(&self) -> u16 {
+ get!(
+ self.buffer,
+ u16,
+ field: field::DODAG_CONF_OBJECTIVE_CODE_POINT
+ )
+ }
+
+ /// Return the Default Lifetime field.
+ #[inline]
+ pub fn default_lifetime(&self) -> u8 {
+ get!(self.buffer, field: field::DODAG_CONF_DEFAULT_LIFETIME)
+ }
+
+ /// Return the Lifetime Unit field.
+ #[inline]
+ pub fn lifetime_unit(&self) -> u16 {
+ get!(self.buffer, u16, field: field::DODAG_CONF_LIFETIME_UNIT)
+ }
+ }
+
+ /// Getters for the DODAG Configuration Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the Flags field.
+ #[inline]
+ pub fn clear_dodag_conf_flags(&mut self) {
+ self.buffer.as_mut()[field::DODAG_CONF_FLAGS] = 0;
+ }
+
+ /// Set the Authentication Enabled field.
+ #[inline]
+ pub fn set_dodag_conf_authentication_enabled(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::DODAG_CONF_AUTHENTICATION_ENABLED,
+ shift: 3,
+ mask: 0b1
+ )
+ }
+
+ /// Set the Path Control Size field.
+ #[inline]
+ pub fn set_dodag_conf_path_control_size(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::DODAG_CONF_PATH_CONTROL_SIZE,
+ mask: 0b111
+ )
+ }
+
+ /// Set the DIO Interval Doublings field.
+ #[inline]
+ pub fn set_dodag_conf_dio_interval_doublings(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::DODAG_CONF_DIO_INTERVAL_DOUBLINGS
+ )
+ }
+
+ /// Set the DIO Interval Minimum field.
+ #[inline]
+ pub fn set_dodag_conf_dio_interval_minimum(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::DODAG_CONF_DIO_INTERVAL_MINIMUM
+ )
+ }
+
+ /// Set the DIO Redundancy Constant field.
+ #[inline]
+ pub fn set_dodag_conf_dio_redundancy_constant(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::DODAG_CONF_DIO_REDUNDANCY_CONSTANT
+ )
+ }
+
+ /// Set the Max Rank Increase field.
+ #[inline]
+ pub fn set_dodag_conf_max_rank_increase(&mut self, value: u16) {
+ set!(
+ self.buffer,
+ value,
+ u16,
+ field: field::DODAG_CONF_DIO_MAX_RANK_INCREASE
+ )
+ }
+
+ /// Set the Minimum Hop Rank Increase field.
+ #[inline]
+ pub fn set_dodag_conf_minimum_hop_rank_increase(&mut self, value: u16) {
+ set!(
+ self.buffer,
+ value,
+ u16,
+ field: field::DODAG_CONF_MIN_HOP_RANK_INCREASE
+ )
+ }
+
+ /// Set the Objective Code Point field.
+ #[inline]
+ pub fn set_dodag_conf_objective_code_point(&mut self, value: u16) {
+ set!(
+ self.buffer,
+ value,
+ u16,
+ field: field::DODAG_CONF_OBJECTIVE_CODE_POINT
+ )
+ }
+
+ /// Set the Default Lifetime field.
+ #[inline]
+ pub fn set_dodag_conf_default_lifetime(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::DODAG_CONF_DEFAULT_LIFETIME
+ )
+ }
+
+ /// Set the Lifetime Unit field.
+ #[inline]
+ pub fn set_dodag_conf_lifetime_unit(&mut self, value: u16) {
+ set!(
+ self.buffer,
+ value,
+ u16,
+ field: field::DODAG_CONF_LIFETIME_UNIT
+ )
+ }
+ }
+
+ /// Getters for the RPL Target Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x05 | Option Length | Flags | Prefix Length |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | |
+ /// + +
+ /// | Target Prefix (Variable Length) |
+ /// . .
+ /// . .
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Target Prefix Length field.
+ pub fn target_prefix_length(&self) -> u8 {
+ get!(self.buffer, field: field::RPL_TARGET_PREFIX_LENGTH)
+ }
+ }
+
+ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
+ /// Return the Target Prefix field.
+ #[inline]
+ pub fn target_prefix(&self) -> &'p [u8] {
+ let option_len = self.option_length();
+ &self.buffer.as_ref()[field::RPL_TARGET_PREFIX_LENGTH + 1..]
+ [..option_len as usize - field::RPL_TARGET_PREFIX_LENGTH + 1]
+ }
+ }
+
+ /// Setters for the RPL Target Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the Flags field.
+ #[inline]
+ pub fn clear_rpl_target_flags(&mut self) {
+ self.buffer.as_mut()[field::RPL_TARGET_FLAGS] = 0;
+ }
+
+ /// Set the Target Prefix Length field.
+ #[inline]
+ pub fn set_rpl_target_prefix_length(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::RPL_TARGET_PREFIX_LENGTH)
+ }
+
+ /// Set the Target Prefix field.
+ #[inline]
+ pub fn set_rpl_target_prefix(&mut self, prefix: &[u8]) {
+ self.buffer.as_mut()[field::RPL_TARGET_PREFIX_LENGTH + 1..][..prefix.len()]
+ .copy_from_slice(prefix);
+ }
+ }
+
+ /// Getters for the Transit Information Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x06 | Option Length |E| Flags | Path Control |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Path Sequence | Path Lifetime | |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
+ /// | |
+ /// + +
+ /// | |
+ /// + Parent Address* +
+ /// | |
+ /// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the External flag.
+ #[inline]
+ pub fn is_external(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::TRANSIT_INFO_EXTERNAL,
+ shift: 7,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the Path Control field.
+ #[inline]
+ pub fn path_control(&self) -> u8 {
+ get!(self.buffer, field: field::TRANSIT_INFO_PATH_CONTROL)
+ }
+
+ /// Return the Path Sequence field.
+ #[inline]
+ pub fn path_sequence(&self) -> u8 {
+ get!(self.buffer, field: field::TRANSIT_INFO_PATH_SEQUENCE)
+ }
+
+ /// Return the Path Lifetime field.
+ #[inline]
+ pub fn path_lifetime(&self) -> u8 {
+ get!(self.buffer, field: field::TRANSIT_INFO_PATH_LIFETIME)
+ }
+
+ /// Return the Parent Address field.
+ #[inline]
+ pub fn parent_address(&self) -> Option<Address> {
+ if self.option_length() > 5 {
+ Some(Address::from_bytes(
+ &self.buffer.as_ref()[field::TRANSIT_INFO_PARENT_ADDRESS],
+ ))
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Setters for the Transit Information Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the Flags field.
+ #[inline]
+ pub fn clear_transit_info_flags(&mut self) {
+ self.buffer.as_mut()[field::TRANSIT_INFO_FLAGS] = 0;
+ }
+
+ /// Set the External flag.
+ #[inline]
+ pub fn set_transit_info_is_external(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::TRANSIT_INFO_EXTERNAL,
+ shift: 7,
+ mask: 0b1
+ )
+ }
+
+ /// Set the Path Control field.
+ #[inline]
+ pub fn set_transit_info_path_control(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_CONTROL)
+ }
+
+ /// Set the Path Sequence field.
+ #[inline]
+ pub fn set_transit_info_path_sequence(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_SEQUENCE)
+ }
+
+ /// Set the Path Lifetime field.
+ #[inline]
+ pub fn set_transit_info_path_lifetime(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_LIFETIME)
+ }
+
+ /// Set the Parent Address field.
+ #[inline]
+ pub fn set_transit_info_parent_address(&mut self, address: Address) {
+ self.buffer.as_mut()[field::TRANSIT_INFO_PARENT_ADDRESS]
+ .copy_from_slice(address.as_bytes());
+ }
+ }
+
+ /// Getters for the Solicited Information Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x07 |Opt Length = 19| RPLInstanceID |V|I|D| Flags |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | |
+ /// + +
+ /// | |
+ /// + DODAGID +
+ /// | |
+ /// + +
+ /// | |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// |Version Number |
+ /// +-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the RPL Instance ID field.
+ #[inline]
+ pub fn rpl_instance_id(&self) -> u8 {
+ get!(self.buffer, field: field::SOLICITED_INFO_RPL_INSTANCE_ID)
+ }
+
+ /// Return the Version Predicate flag.
+ #[inline]
+ pub fn version_predicate(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::SOLICITED_INFO_VERSION_PREDICATE,
+ shift: 7,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the Instance ID Predicate flag.
+ #[inline]
+ pub fn instance_id_predicate(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::SOLICITED_INFO_INSTANCE_ID_PREDICATE,
+ shift: 6,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the DODAG Predicate ID flag.
+ #[inline]
+ pub fn dodag_id_predicate(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::SOLICITED_INFO_DODAG_ID_PREDICATE,
+ shift: 5,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the DODAG ID field.
+ #[inline]
+ pub fn dodag_id(&self) -> Address {
+ get!(
+ self.buffer,
+ into: Address,
+ fun: from_bytes,
+ field: field::SOLICITED_INFO_DODAG_ID
+ )
+ }
+
+ /// Return the Version Number field.
+ #[inline]
+ pub fn version_number(&self) -> u8 {
+ get!(self.buffer, field: field::SOLICITED_INFO_VERSION_NUMBER)
+ }
+ }
+
+ /// Setters for the Solicited Information Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the Flags field.
+ #[inline]
+ pub fn clear_solicited_info_flags(&mut self) {
+ self.buffer.as_mut()[field::SOLICITED_INFO_FLAGS] = 0;
+ }
+
+ /// Set the RPL Instance ID field.
+ #[inline]
+ pub fn set_solicited_info_rpl_instance_id(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::SOLICITED_INFO_RPL_INSTANCE_ID
+ )
+ }
+
+ /// Set the Version Predicate flag.
+ #[inline]
+ pub fn set_solicited_info_version_predicate(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::SOLICITED_INFO_VERSION_PREDICATE,
+ shift: 7,
+ mask: 0b1
+ )
+ }
+
+ /// Set the Instance ID Predicate flag.
+ #[inline]
+ pub fn set_solicited_info_instance_id_predicate(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::SOLICITED_INFO_INSTANCE_ID_PREDICATE,
+ shift: 6,
+ mask: 0b1
+ )
+ }
+
+ /// Set the DODAG Predicate ID flag.
+ #[inline]
+ pub fn set_solicited_info_dodag_id_predicate(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::SOLICITED_INFO_DODAG_ID_PREDICATE,
+ shift: 5,
+ mask: 0b1
+ )
+ }
+
+ /// Set the DODAG ID field.
+ #[inline]
+ pub fn set_solicited_info_dodag_id(&mut self, address: Address) {
+ set!(
+ self.buffer,
+ address: address,
+ field: field::SOLICITED_INFO_DODAG_ID
+ )
+ }
+
+ /// Set the Version Number field.
+ #[inline]
+ pub fn set_solicited_info_version_number(&mut self, value: u8) {
+ set!(
+ self.buffer,
+ value,
+ field: field::SOLICITED_INFO_VERSION_NUMBER
+ )
+ }
+ }
+
+ /// Getters for the Prefix Information Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x08 |Opt Length = 30| Prefix Length |L|A|R|Reserved1|
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Valid Lifetime |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Preferred Lifetime |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Reserved2 |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | |
+ /// + +
+ /// | |
+ /// + Prefix +
+ /// | |
+ /// + +
+ /// | |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Prefix Length field.
+ #[inline]
+ pub fn prefix_info_prefix_length(&self) -> u8 {
+ get!(self.buffer, field: field::PREFIX_INFO_PREFIX_LENGTH)
+ }
+
+ /// Return the On-Link flag.
+ #[inline]
+ pub fn on_link(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::PREFIX_INFO_ON_LINK,
+ shift: 7,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the Autonomous Address-Configuration flag.
+ #[inline]
+ pub fn autonomous_address_configuration(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::PREFIX_INFO_AUTONOMOUS_CONF,
+ shift: 6,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the Router Address flag.
+ #[inline]
+ pub fn router_address(&self) -> bool {
+ get!(
+ self.buffer,
+ bool,
+ field: field::PREFIX_INFO_ROUTER_ADDRESS_FLAG,
+ shift: 5,
+ mask: 0b1,
+ )
+ }
+
+ /// Return the Valid Lifetime field.
+ #[inline]
+ pub fn valid_lifetime(&self) -> u32 {
+ get!(self.buffer, u32, field: field::PREFIX_INFO_VALID_LIFETIME)
+ }
+
+ /// Return the Preferred Lifetime field.
+ #[inline]
+ pub fn preferred_lifetime(&self) -> u32 {
+ get!(
+ self.buffer,
+ u32,
+ field: field::PREFIX_INFO_PREFERRED_LIFETIME
+ )
+ }
+ }
+
+ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
+ /// Return the Prefix field.
+ #[inline]
+ pub fn destination_prefix(&self) -> &'p [u8] {
+ &self.buffer.as_ref()[field::PREFIX_INFO_PREFIX]
+ }
+ }
+
+ /// Setters for the Prefix Information Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Clear the reserved fields.
+ #[inline]
+ pub fn clear_prefix_info_reserved(&mut self) {
+ self.buffer.as_mut()[field::PREFIX_INFO_RESERVED1] = 0;
+ self.buffer.as_mut()[field::PREFIX_INFO_RESERVED2].copy_from_slice(&[0; 4]);
+ }
+
+ /// Set the Prefix Length field.
+ #[inline]
+ pub fn set_prefix_info_prefix_length(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::PREFIX_INFO_PREFIX_LENGTH)
+ }
+
+ /// Set the On-Link flag.
+ #[inline]
+ pub fn set_prefix_info_on_link(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::PREFIX_INFO_ON_LINK, shift: 7, mask: 0b1)
+ }
+
+ /// Set the Autonomous Address-Configuration flag.
+ #[inline]
+ pub fn set_prefix_info_autonomous_address_configuration(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::PREFIX_INFO_AUTONOMOUS_CONF,
+ shift: 6,
+ mask: 0b1
+ )
+ }
+
+ /// Set the Router Address flag.
+ #[inline]
+ pub fn set_prefix_info_router_address(&mut self, value: bool) {
+ set!(
+ self.buffer,
+ value,
+ bool,
+ field: field::PREFIX_INFO_ROUTER_ADDRESS_FLAG,
+ shift: 5,
+ mask: 0b1
+ )
+ }
+
+ /// Set the Valid Lifetime field.
+ #[inline]
+ pub fn set_prefix_info_valid_lifetime(&mut self, value: u32) {
+ set!(
+ self.buffer,
+ value,
+ u32,
+ field: field::PREFIX_INFO_VALID_LIFETIME
+ )
+ }
+
+ /// Set the Preferred Lifetime field.
+ #[inline]
+ pub fn set_prefix_info_preferred_lifetime(&mut self, value: u32) {
+ set!(
+ self.buffer,
+ value,
+ u32,
+ field: field::PREFIX_INFO_PREFERRED_LIFETIME
+ )
+ }
+
+ /// Set the Prefix field.
+ #[inline]
+ pub fn set_prefix_info_destination_prefix(&mut self, prefix: &[u8]) {
+ self.buffer.as_mut()[field::PREFIX_INFO_PREFIX].copy_from_slice(prefix);
+ }
+ }
+
+ /// Getters for the RPL Target Descriptor Option Message.
+ ///
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Type = 0x09 |Opt Length = 4 | Descriptor
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// Descriptor (cont.) |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ impl<T: AsRef<[u8]>> Packet<T> {
+ /// Return the Descriptor field.
+ #[inline]
+ pub fn descriptor(&self) -> u32 {
+ get!(self.buffer, u32, field: field::TARGET_DESCRIPTOR)
+ }
+ }
+
+ /// Setters for the RPL Target Descriptor Option Message.
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the Descriptor field.
+ #[inline]
+ pub fn set_rpl_target_descriptor_descriptor(&mut self, value: u32) {
+ set!(self.buffer, value, u32, field: field::TARGET_DESCRIPTOR)
+ }
+ }
+
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub enum Repr<'p> {
+ Pad1,
+ PadN(u8),
+ DagMetricContainer,
+ RouteInformation {
+ prefix_length: u8,
+ preference: u8,
+ lifetime: u32,
+ prefix: &'p [u8],
+ },
+ DodagConfiguration {
+ authentication_enabled: bool,
+ path_control_size: u8,
+ dio_interval_doublings: u8,
+ dio_interval_min: u8,
+ dio_redundancy_constant: u8,
+ max_rank_increase: u16,
+ minimum_hop_rank_increase: u16,
+ objective_code_point: u16,
+ default_lifetime: u8,
+ lifetime_unit: u16,
+ },
+ RplTarget {
+ prefix_length: u8,
+ prefix: crate::wire::Ipv6Address, // FIXME: this is not the correct type, because the
+ // field can be an IPv6 address, a prefix or a
+ // multicast group.
+ },
+ TransitInformation {
+ external: bool,
+ path_control: u8,
+ path_sequence: u8,
+ path_lifetime: u8,
+ parent_address: Option<Address>,
+ },
+ SolicitedInformation {
+ rpl_instance_id: InstanceId,
+ version_predicate: bool,
+ instance_id_predicate: bool,
+ dodag_id_predicate: bool,
+ dodag_id: Address,
+ version_number: u8,
+ },
+ PrefixInformation {
+ prefix_length: u8,
+ on_link: bool,
+ autonomous_address_configuration: bool,
+ router_address: bool,
+ valid_lifetime: u32,
+ preferred_lifetime: u32,
+ destination_prefix: &'p [u8],
+ },
+ RplTargetDescriptor {
+ descriptor: u32,
+ },
+ }
+
+ impl core::fmt::Display for Repr<'_> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Repr::Pad1 => write!(f, "Pad1"),
+ Repr::PadN(n) => write!(f, "PadN({n})"),
+ Repr::DagMetricContainer => todo!(),
+ Repr::RouteInformation {
+ prefix_length,
+ preference,
+ lifetime,
+ prefix,
+ } => {
+ write!(
+ f,
+ "ROUTE INFO \
+ PrefixLength={prefix_length} \
+ Preference={preference} \
+ Lifetime={lifetime} \
+ Prefix={prefix:0x?}"
+ )
+ }
+ Repr::DodagConfiguration {
+ dio_interval_doublings,
+ dio_interval_min,
+ dio_redundancy_constant,
+ max_rank_increase,
+ minimum_hop_rank_increase,
+ objective_code_point,
+ default_lifetime,
+ lifetime_unit,
+ ..
+ } => {
+ write!(
+ f,
+ "DODAG CONF \
+ IntD={dio_interval_doublings} \
+ IntMin={dio_interval_min} \
+ RedCst={dio_redundancy_constant} \
+ MaxRankIncr={max_rank_increase} \
+ MinHopRankIncr={minimum_hop_rank_increase} \
+ OCP={objective_code_point} \
+ DefaultLifetime={default_lifetime} \
+ LifeUnit={lifetime_unit}"
+ )
+ }
+ Repr::RplTarget {
+ prefix_length,
+ prefix,
+ } => {
+ write!(
+ f,
+ "RPL Target \
+ PrefixLength={prefix_length} \
+ Prefix={prefix:0x?}"
+ )
+ }
+ Repr::TransitInformation {
+ external,
+ path_control,
+ path_sequence,
+ path_lifetime,
+ parent_address,
+ } => {
+ write!(
+ f,
+ "Transit Info \
+ External={external} \
+ PathCtrl={path_control} \
+ PathSqnc={path_sequence} \
+ PathLifetime={path_lifetime} \
+ Parent={parent_address:0x?}"
+ )
+ }
+ Repr::SolicitedInformation {
+ rpl_instance_id,
+ version_predicate,
+ instance_id_predicate,
+ dodag_id_predicate,
+ dodag_id,
+ version_number,
+ } => {
+ write!(
+ f,
+ "Solicited Info \
+ I={instance_id_predicate} \
+ IID={rpl_instance_id:0x?} \
+ D={dodag_id_predicate} \
+ DODAGID={dodag_id} \
+ V={version_predicate} \
+ Version={version_number}"
+ )
+ }
+ Repr::PrefixInformation {
+ prefix_length,
+ on_link,
+ autonomous_address_configuration,
+ router_address,
+ valid_lifetime,
+ preferred_lifetime,
+ destination_prefix,
+ } => {
+ write!(
+ f,
+ "Prefix Info \
+ PrefixLength={prefix_length} \
+ L={on_link} A={autonomous_address_configuration} R={router_address} \
+ Valid={valid_lifetime} \
+ Preferred={preferred_lifetime} \
+ Prefix={destination_prefix:0x?}"
+ )
+ }
+ Repr::RplTargetDescriptor { .. } => write!(f, "Target Descriptor"),
+ }
+ }
+ }
+
+ impl<'p> Repr<'p> {
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&'p T>) -> Result<Self> {
+ match packet.option_type() {
+ OptionType::Pad1 => Ok(Repr::Pad1),
+ OptionType::PadN => Ok(Repr::PadN(packet.option_length())),
+ OptionType::DagMetricContainer => todo!(),
+ OptionType::RouteInformation => Ok(Repr::RouteInformation {
+ prefix_length: packet.prefix_length(),
+ preference: packet.route_preference(),
+ lifetime: packet.route_lifetime(),
+ prefix: packet.prefix(),
+ }),
+ OptionType::DodagConfiguration => Ok(Repr::DodagConfiguration {
+ authentication_enabled: packet.authentication_enabled(),
+ path_control_size: packet.path_control_size(),
+ dio_interval_doublings: packet.dio_interval_doublings(),
+ dio_interval_min: packet.dio_interval_minimum(),
+ dio_redundancy_constant: packet.dio_redundancy_constant(),
+ max_rank_increase: packet.max_rank_increase(),
+ minimum_hop_rank_increase: packet.minimum_hop_rank_increase(),
+ objective_code_point: packet.objective_code_point(),
+ default_lifetime: packet.default_lifetime(),
+ lifetime_unit: packet.lifetime_unit(),
+ }),
+ OptionType::RplTarget => Ok(Repr::RplTarget {
+ prefix_length: packet.target_prefix_length(),
+ prefix: crate::wire::Ipv6Address::from_bytes(packet.target_prefix()),
+ }),
+ OptionType::TransitInformation => Ok(Repr::TransitInformation {
+ external: packet.is_external(),
+ path_control: packet.path_control(),
+ path_sequence: packet.path_sequence(),
+ path_lifetime: packet.path_lifetime(),
+ parent_address: packet.parent_address(),
+ }),
+ OptionType::SolicitedInformation => Ok(Repr::SolicitedInformation {
+ rpl_instance_id: InstanceId::from(packet.rpl_instance_id()),
+ version_predicate: packet.version_predicate(),
+ instance_id_predicate: packet.instance_id_predicate(),
+ dodag_id_predicate: packet.dodag_id_predicate(),
+ dodag_id: packet.dodag_id(),
+ version_number: packet.version_number(),
+ }),
+ OptionType::PrefixInformation => Ok(Repr::PrefixInformation {
+ prefix_length: packet.prefix_info_prefix_length(),
+ on_link: packet.on_link(),
+ autonomous_address_configuration: packet.autonomous_address_configuration(),
+ router_address: packet.router_address(),
+ valid_lifetime: packet.valid_lifetime(),
+ preferred_lifetime: packet.preferred_lifetime(),
+ destination_prefix: packet.destination_prefix(),
+ }),
+ OptionType::RplTargetDescriptor => Ok(Repr::RplTargetDescriptor {
+ descriptor: packet.descriptor(),
+ }),
+ OptionType::Unknown(_) => Err(Error),
+ }
+ }
+
+ pub fn buffer_len(&self) -> usize {
+ match self {
+ Repr::Pad1 => 1,
+ Repr::PadN(size) => 2 + *size as usize,
+ Repr::DagMetricContainer => todo!(),
+ Repr::RouteInformation { prefix, .. } => 2 + 6 + prefix.len(),
+ Repr::DodagConfiguration { .. } => 2 + 14,
+ Repr::RplTarget { prefix, .. } => 2 + 2 + prefix.0.len(),
+ Repr::TransitInformation { parent_address, .. } => {
+ 2 + 4 + if parent_address.is_some() { 16 } else { 0 }
+ }
+ Repr::SolicitedInformation { .. } => 2 + 2 + 16 + 1,
+ Repr::PrefixInformation { .. } => 32,
+ Repr::RplTargetDescriptor { .. } => 2 + 4,
+ }
+ }
+
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&'p mut T>) {
+ let mut option_length = self.buffer_len() as u8;
+
+ packet.set_option_type(self.into());
+
+ if !matches!(self, Repr::Pad1) {
+ option_length -= 2;
+ packet.set_option_length(option_length);
+ }
+
+ match self {
+ Repr::Pad1 => {}
+ Repr::PadN(size) => {
+ packet.clear_padn(*size);
+ }
+ Repr::DagMetricContainer => {
+ unimplemented!();
+ }
+ Repr::RouteInformation {
+ prefix_length,
+ preference,
+ lifetime,
+ prefix,
+ } => {
+ packet.clear_route_info_reserved();
+ packet.set_route_info_prefix_length(*prefix_length);
+ packet.set_route_info_route_preference(*preference);
+ packet.set_route_info_route_lifetime(*lifetime);
+ packet.set_route_info_prefix(prefix);
+ }
+ Repr::DodagConfiguration {
+ authentication_enabled,
+ path_control_size,
+ dio_interval_doublings,
+ dio_interval_min,
+ dio_redundancy_constant,
+ max_rank_increase,
+ minimum_hop_rank_increase,
+ objective_code_point,
+ default_lifetime,
+ lifetime_unit,
+ } => {
+ packet.clear_dodag_conf_flags();
+ packet.set_dodag_conf_authentication_enabled(*authentication_enabled);
+ packet.set_dodag_conf_path_control_size(*path_control_size);
+ packet.set_dodag_conf_dio_interval_doublings(*dio_interval_doublings);
+ packet.set_dodag_conf_dio_interval_minimum(*dio_interval_min);
+ packet.set_dodag_conf_dio_redundancy_constant(*dio_redundancy_constant);
+ packet.set_dodag_conf_max_rank_increase(*max_rank_increase);
+ packet.set_dodag_conf_minimum_hop_rank_increase(*minimum_hop_rank_increase);
+ packet.set_dodag_conf_objective_code_point(*objective_code_point);
+ packet.set_dodag_conf_default_lifetime(*default_lifetime);
+ packet.set_dodag_conf_lifetime_unit(*lifetime_unit);
+ }
+ Repr::RplTarget {
+ prefix_length,
+ prefix,
+ } => {
+ packet.clear_rpl_target_flags();
+ packet.set_rpl_target_prefix_length(*prefix_length);
+ packet.set_rpl_target_prefix(prefix.as_bytes());
+ }
+ Repr::TransitInformation {
+ external,
+ path_control,
+ path_sequence,
+ path_lifetime,
+ parent_address,
+ } => {
+ packet.clear_transit_info_flags();
+ packet.set_transit_info_is_external(*external);
+ packet.set_transit_info_path_control(*path_control);
+ packet.set_transit_info_path_sequence(*path_sequence);
+ packet.set_transit_info_path_lifetime(*path_lifetime);
+
+ if let Some(address) = parent_address {
+ packet.set_transit_info_parent_address(*address);
+ }
+ }
+ Repr::SolicitedInformation {
+ rpl_instance_id,
+ version_predicate,
+ instance_id_predicate,
+ dodag_id_predicate,
+ dodag_id,
+ version_number,
+ } => {
+ packet.clear_solicited_info_flags();
+ packet.set_solicited_info_rpl_instance_id((*rpl_instance_id).into());
+ packet.set_solicited_info_version_predicate(*version_predicate);
+ packet.set_solicited_info_instance_id_predicate(*instance_id_predicate);
+ packet.set_solicited_info_dodag_id_predicate(*dodag_id_predicate);
+ packet.set_solicited_info_version_number(*version_number);
+ packet.set_solicited_info_dodag_id(*dodag_id);
+ }
+ Repr::PrefixInformation {
+ prefix_length,
+ on_link,
+ autonomous_address_configuration,
+ router_address,
+ valid_lifetime,
+ preferred_lifetime,
+ destination_prefix,
+ } => {
+ packet.clear_prefix_info_reserved();
+ packet.set_prefix_info_prefix_length(*prefix_length);
+ packet.set_prefix_info_on_link(*on_link);
+ packet.set_prefix_info_autonomous_address_configuration(
+ *autonomous_address_configuration,
+ );
+ packet.set_prefix_info_router_address(*router_address);
+ packet.set_prefix_info_valid_lifetime(*valid_lifetime);
+ packet.set_prefix_info_preferred_lifetime(*preferred_lifetime);
+ packet.set_prefix_info_destination_prefix(destination_prefix);
+ }
+ Repr::RplTargetDescriptor { descriptor } => {
+ packet.set_rpl_target_descriptor_descriptor(*descriptor);
+ }
+ }
+ }
+ }
+}
+
+pub mod data {
+ use super::{InstanceId, Result};
+ use byteorder::{ByteOrder, NetworkEndian};
+
+ mod field {
+ use crate::wire::field::*;
+
+ pub const FLAGS: usize = 0;
+ pub const INSTANCE_ID: usize = 1;
+ pub const SENDER_RANK: Field = 2..4;
+ }
+
+ /// A read/write wrapper around a RPL Packet Information send with
+ /// an IPv6 Hop-by-Hop option, defined in RFC6553.
+ /// ```txt
+ /// 0 1 2 3
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | Option Type | Opt Data Len |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// |O|R|F|0|0|0|0|0| RPLInstanceID | SenderRank |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// | (sub-TLVs) |
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ /// ```
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+ }
+
+ impl<T: AsRef<[u8]>> Packet<T> {
+ #[inline]
+ pub fn new_unchecked(buffer: T) -> Self {
+ Self { buffer }
+ }
+
+ #[inline]
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ #[inline]
+ pub fn check_len(&self) -> Result<()> {
+ if self.buffer.as_ref().len() == 4 {
+ Ok(())
+ } else {
+ Err(crate::wire::Error)
+ }
+ }
+
+ #[inline]
+ pub fn is_down(&self) -> bool {
+ get!(self.buffer, bool, field: field::FLAGS, shift: 7, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn has_rank_error(&self) -> bool {
+ get!(self.buffer, bool, field: field::FLAGS, shift: 6, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn has_forwarding_error(&self) -> bool {
+ get!(self.buffer, bool, field: field::FLAGS, shift: 5, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn rpl_instance_id(&self) -> InstanceId {
+ get!(self.buffer, into: InstanceId, field: field::INSTANCE_ID)
+ }
+
+ #[inline]
+ pub fn sender_rank(&self) -> u16 {
+ get!(self.buffer, u16, field: field::SENDER_RANK)
+ }
+ }
+
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ #[inline]
+ pub fn set_is_down(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::FLAGS, shift: 7, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn set_has_rank_error(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::FLAGS, shift: 6, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn set_has_forwarding_error(&mut self, value: bool) {
+ set!(self.buffer, value, bool, field: field::FLAGS, shift: 5, mask: 0b1)
+ }
+
+ #[inline]
+ pub fn set_rpl_instance_id(&mut self, value: u8) {
+ set!(self.buffer, value, field: field::INSTANCE_ID)
+ }
+
+ #[inline]
+ pub fn set_sender_rank(&mut self, value: u16) {
+ set!(self.buffer, value, u16, field: field::SENDER_RANK)
+ }
+ }
+
+ /// A high-level representation of an IPv6 Extension Header Option.
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub struct HopByHopOption {
+ pub down: bool,
+ pub rank_error: bool,
+ pub forwarding_error: bool,
+ pub instance_id: InstanceId,
+ pub sender_rank: u16,
+ }
+
+ impl HopByHopOption {
+ /// Parse an IPv6 Extension Header Option and return a high-level representation.
+ pub fn parse<T>(opt: &Packet<&T>) -> Self
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ Self {
+ down: opt.is_down(),
+ rank_error: opt.has_rank_error(),
+ forwarding_error: opt.has_forwarding_error(),
+ instance_id: opt.rpl_instance_id(),
+ sender_rank: opt.sender_rank(),
+ }
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub const fn buffer_len(&self) -> usize {
+ 4
+ }
+
+ /// Emit a high-level representation into an IPv6 Extension Header Option.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, opt: &mut Packet<&mut T>) {
+ opt.set_is_down(self.down);
+ opt.set_has_rank_error(self.rank_error);
+ opt.set_has_forwarding_error(self.forwarding_error);
+ opt.set_rpl_instance_id(self.instance_id.into());
+ opt.set_sender_rank(self.sender_rank);
+ }
+ }
+
+ impl core::fmt::Display for HopByHopOption {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ f,
+ "down={} rank_error={} forw_error={} IID={:?} sender_rank={}",
+ self.down,
+ self.rank_error,
+ self.forwarding_error,
+ self.instance_id,
+ self.sender_rank
+ )
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::options::{Packet as OptionPacket, Repr as OptionRepr};
+ use super::Repr as RplRepr;
+ use super::*;
+ use crate::phy::ChecksumCapabilities;
+ use crate::wire::{icmpv6::*, *};
+
+ #[test]
+ fn dis_packet() {
+ let data = [0x7a, 0x3b, 0x3a, 0x1a, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00];
+
+ let ll_src_address =
+ Ieee802154Address::Extended([0x9e, 0xd3, 0xa2, 0x9c, 0x57, 0x1a, 0x4f, 0xe4]);
+ let ll_dst_address = Ieee802154Address::Short([0xff, 0xff]);
+
+ let packet = SixlowpanIphcPacket::new_checked(&data).unwrap();
+ let repr =
+ SixlowpanIphcRepr::parse(&packet, Some(ll_src_address), Some(ll_dst_address), &[])
+ .unwrap();
+
+ let icmp_repr = match repr.next_header {
+ SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6) => {
+ let icmp_packet = Icmpv6Packet::new_checked(packet.payload()).unwrap();
+ match Icmpv6Repr::parse(
+ &IpAddress::Ipv6(repr.src_addr),
+ &IpAddress::Ipv6(repr.dst_addr),
+ &icmp_packet,
+ &ChecksumCapabilities::ignored(),
+ ) {
+ Ok(icmp @ Icmpv6Repr::Rpl(RplRepr::DodagInformationSolicitation { .. })) => {
+ icmp
+ }
+ _ => unreachable!(),
+ }
+ }
+ _ => unreachable!(),
+ };
+
+ // We also try to emit the packet:
+ let mut buffer = vec![0u8; repr.buffer_len() + icmp_repr.buffer_len()];
+ repr.emit(&mut SixlowpanIphcPacket::new_unchecked(
+ &mut buffer[..repr.buffer_len()],
+ ));
+ icmp_repr.emit(
+ &repr.src_addr.into(),
+ &repr.dst_addr.into(),
+ &mut Icmpv6Packet::new_unchecked(
+ &mut buffer[repr.buffer_len()..][..icmp_repr.buffer_len()],
+ ),
+ &ChecksumCapabilities::ignored(),
+ );
+
+ assert_eq!(&data[..], &buffer[..]);
+ }
+
+ /// Parsing of DIO packets.
+ #[test]
+ fn dio_packet() {
+ let data = [
+ 0x9b, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x80, 0x08, 0xf0, 0x00, 0x00, 0xfd, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x04, 0x0e, 0x00, 0x08, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x80, 0x00, 0x01, 0x00, 0x1e,
+ 0x00, 0x3c, 0x08, 0x1e, 0x40, 0x40, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let addr = Address::from_bytes(&[
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01,
+ ]);
+
+ let dest_prefix = [
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ ];
+
+ let packet = Packet::new_checked(&data[..]).unwrap();
+ assert_eq!(packet.msg_type(), Message::RplControl);
+ assert_eq!(
+ RplControlMessage::from(packet.msg_code()),
+ RplControlMessage::DodagInformationObject
+ );
+
+ let mut dio_repr = RplRepr::parse(&packet).unwrap();
+ match dio_repr {
+ RplRepr::DodagInformationObject {
+ rpl_instance_id,
+ version_number,
+ rank,
+ grounded,
+ mode_of_operation,
+ dodag_preference,
+ dtsn,
+ dodag_id,
+ ..
+ } => {
+ assert_eq!(rpl_instance_id, InstanceId::from(0));
+ assert_eq!(version_number, 240);
+ assert_eq!(rank, 128);
+ assert!(!grounded);
+ assert_eq!(mode_of_operation, ModeOfOperation::NonStoringMode);
+ assert_eq!(dodag_preference, 0);
+ assert_eq!(dtsn, 240);
+ assert_eq!(dodag_id, addr);
+ }
+ _ => unreachable!(),
+ }
+
+ let option = OptionPacket::new_unchecked(packet.options().unwrap());
+ let dodag_conf_option = OptionRepr::parse(&option).unwrap();
+ match dodag_conf_option {
+ OptionRepr::DodagConfiguration {
+ authentication_enabled,
+ path_control_size,
+ dio_interval_doublings,
+ dio_interval_min,
+ dio_redundancy_constant,
+ max_rank_increase,
+ minimum_hop_rank_increase,
+ objective_code_point,
+ default_lifetime,
+ lifetime_unit,
+ } => {
+ assert!(!authentication_enabled);
+ assert_eq!(path_control_size, 0);
+ assert_eq!(dio_interval_doublings, 8);
+ assert_eq!(dio_interval_min, 12);
+ assert_eq!(dio_redundancy_constant, 0);
+ assert_eq!(max_rank_increase, 1024);
+ assert_eq!(minimum_hop_rank_increase, 128);
+ assert_eq!(objective_code_point, 1);
+ assert_eq!(default_lifetime, 30);
+ assert_eq!(lifetime_unit, 60);
+ }
+ _ => unreachable!(),
+ }
+
+ let option = OptionPacket::new_unchecked(option.next_option().unwrap());
+ let prefix_info_option = OptionRepr::parse(&option).unwrap();
+ match prefix_info_option {
+ OptionRepr::PrefixInformation {
+ prefix_length,
+ on_link,
+ autonomous_address_configuration,
+ valid_lifetime,
+ preferred_lifetime,
+ destination_prefix,
+ ..
+ } => {
+ assert_eq!(prefix_length, 64);
+ assert!(!on_link);
+ assert!(autonomous_address_configuration);
+ assert_eq!(valid_lifetime, u32::MAX);
+ assert_eq!(preferred_lifetime, u32::MAX);
+ assert_eq!(destination_prefix, &dest_prefix[..]);
+ }
+ _ => unreachable!(),
+ }
+
+ let mut options_buffer =
+ vec![0u8; dodag_conf_option.buffer_len() + prefix_info_option.buffer_len()];
+
+ dodag_conf_option.emit(&mut OptionPacket::new_unchecked(
+ &mut options_buffer[..dodag_conf_option.buffer_len()],
+ ));
+ prefix_info_option.emit(&mut OptionPacket::new_unchecked(
+ &mut options_buffer[dodag_conf_option.buffer_len()..]
+ [..prefix_info_option.buffer_len()],
+ ));
+
+ dio_repr.set_options(&options_buffer[..]);
+
+ let mut buffer = vec![0u8; dio_repr.buffer_len()];
+ dio_repr.emit(&mut Packet::new_unchecked(&mut buffer[..]));
+
+ assert_eq!(&data[..], &buffer[..]);
+ }
+
+ /// Parsing of DAO packets.
+ #[test]
+ fn dao_packet() {
+ let data = [
+ 0x9b, 0x02, 0x00, 0x00, 0x00, 0x80, 0x00, 0xf1, 0x05, 0x12, 0x00, 0x80, 0xfd, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
+ 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ ];
+
+ let target_prefix = [
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02,
+ 0x00, 0x02,
+ ];
+
+ let parent_addr = Address::from_bytes(&[
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01,
+ ]);
+
+ let packet = Packet::new_checked(&data[..]).unwrap();
+ let mut dao_repr = RplRepr::parse(&packet).unwrap();
+ match dao_repr {
+ RplRepr::DestinationAdvertisementObject {
+ rpl_instance_id,
+ expect_ack,
+ sequence,
+ dodag_id,
+ ..
+ } => {
+ assert_eq!(rpl_instance_id, InstanceId::from(0));
+ assert!(expect_ack);
+ assert_eq!(sequence, 241);
+ assert_eq!(dodag_id, None);
+ }
+ _ => unreachable!(),
+ }
+
+ let option = OptionPacket::new_unchecked(packet.options().unwrap());
+
+ let rpl_target_option = OptionRepr::parse(&option).unwrap();
+ match rpl_target_option {
+ OptionRepr::RplTarget {
+ prefix_length,
+ prefix,
+ } => {
+ assert_eq!(prefix_length, 128);
+ assert_eq!(prefix.as_bytes(), &target_prefix[..]);
+ }
+ _ => unreachable!(),
+ }
+
+ let option = OptionPacket::new_unchecked(option.next_option().unwrap());
+ let transit_info_option = OptionRepr::parse(&option).unwrap();
+ match transit_info_option {
+ OptionRepr::TransitInformation {
+ external,
+ path_control,
+ path_sequence,
+ path_lifetime,
+ parent_address,
+ } => {
+ assert!(!external);
+ assert_eq!(path_control, 0);
+ assert_eq!(path_sequence, 0);
+ assert_eq!(path_lifetime, 30);
+ assert_eq!(parent_address, Some(parent_addr));
+ }
+ _ => unreachable!(),
+ }
+
+ let mut options_buffer =
+ vec![0u8; rpl_target_option.buffer_len() + transit_info_option.buffer_len()];
+
+ rpl_target_option.emit(&mut OptionPacket::new_unchecked(
+ &mut options_buffer[..rpl_target_option.buffer_len()],
+ ));
+ transit_info_option.emit(&mut OptionPacket::new_unchecked(
+ &mut options_buffer[rpl_target_option.buffer_len()..]
+ [..transit_info_option.buffer_len()],
+ ));
+
+ dao_repr.set_options(&options_buffer[..]);
+
+ let mut buffer = vec![0u8; dao_repr.buffer_len()];
+ dao_repr.emit(&mut Packet::new_unchecked(&mut buffer[..]));
+
+ assert_eq!(&data[..], &buffer[..]);
+ }
+
+ /// Parsing of DAO-ACK packets.
+ #[test]
+ fn dao_ack_packet() {
+ let data = [0x9b, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x00];
+
+ let packet = Packet::new_checked(&data[..]).unwrap();
+ let dao_ack_repr = RplRepr::parse(&packet).unwrap();
+ match dao_ack_repr {
+ RplRepr::DestinationAdvertisementObjectAck {
+ rpl_instance_id,
+ sequence,
+ status,
+ dodag_id,
+ ..
+ } => {
+ assert_eq!(rpl_instance_id, InstanceId::from(0));
+ assert_eq!(sequence, 241);
+ assert_eq!(status, 0);
+ assert_eq!(dodag_id, None);
+ }
+ _ => unreachable!(),
+ }
+
+ let mut buffer = vec![0u8; dao_ack_repr.buffer_len()];
+ dao_ack_repr.emit(&mut Packet::new_unchecked(&mut buffer[..]));
+
+ assert_eq!(&data[..], &buffer[..]);
+
+ let data = [
+ 0x9b, 0x03, 0x0, 0x0, 0x1e, 0x80, 0xf0, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ ];
+
+ let packet = Packet::new_checked(&data[..]).unwrap();
+ let dao_ack_repr = RplRepr::parse(&packet).unwrap();
+ match dao_ack_repr {
+ RplRepr::DestinationAdvertisementObjectAck {
+ rpl_instance_id,
+ sequence,
+ status,
+ dodag_id,
+ ..
+ } => {
+ assert_eq!(rpl_instance_id, InstanceId::from(30));
+ assert_eq!(sequence, 240);
+ assert_eq!(status, 0x0);
+ assert_eq!(
+ dodag_id,
+ Some(Ipv6Address([
+ 254, 128, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1
+ ]))
+ );
+ }
+ _ => unreachable!(),
+ }
+
+ let mut buffer = vec![0u8; dao_ack_repr.buffer_len()];
+ dao_ack_repr.emit(&mut Packet::new_unchecked(&mut buffer[..]));
+
+ assert_eq!(&data[..], &buffer[..]);
+ }
+}
diff --git a/src/wire/sixlowpan/frag.rs b/src/wire/sixlowpan/frag.rs
new file mode 100644
index 0000000..de45702
--- /dev/null
+++ b/src/wire/sixlowpan/frag.rs
@@ -0,0 +1,275 @@
+//! Implementation of the fragment headers from [RFC 4944 § 5.3].
+//!
+//! [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
+
+use super::{DISPATCH_FIRST_FRAGMENT_HEADER, DISPATCH_FRAGMENT_HEADER};
+use crate::wire::{Error, Result};
+use crate::wire::{Ieee802154Address, Ieee802154Repr};
+use byteorder::{ByteOrder, NetworkEndian};
+
+/// Key used for identifying all the link fragments that belong to the same packet.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Key {
+ pub(crate) ll_src_addr: Ieee802154Address,
+ pub(crate) ll_dst_addr: Ieee802154Address,
+ pub(crate) datagram_size: u16,
+ pub(crate) datagram_tag: u16,
+}
+
+/// A read/write wrapper around a 6LoWPAN Fragment header.
+/// [RFC 4944 § 5.3] specifies the format of the header.
+///
+/// A First Fragment header has the following format:
+/// ```txt
+/// 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// |1 1 0 0 0| datagram_size | datagram_tag |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// ```
+///
+/// Subsequent fragment headers have the following format:
+/// ```txt
+/// 1 2 3
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// |1 1 1 0 0| datagram_size | datagram_tag |
+/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/// |datagram_offset|
+/// +-+-+-+-+-+-+-+-+
+/// ```
+///
+/// [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+pub const FIRST_FRAGMENT_HEADER_SIZE: usize = 4;
+pub const NEXT_FRAGMENT_HEADER_SIZE: usize = 5;
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const DISPATCH: usize = 0;
+ pub const DATAGRAM_SIZE: Field = 0..2;
+ pub const DATAGRAM_TAG: Field = 2..4;
+ pub const DATAGRAM_OFFSET: usize = 4;
+
+ pub const FIRST_FRAGMENT_REST: Rest = super::FIRST_FRAGMENT_HEADER_SIZE..;
+ pub const NEXT_FRAGMENT_REST: Rest = super::NEXT_FRAGMENT_HEADER_SIZE..;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Input a raw octet buffer with a 6LoWPAN Fragment header structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Self { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+
+ let dispatch = packet.dispatch();
+
+ if dispatch != DISPATCH_FIRST_FRAGMENT_HEADER && dispatch != DISPATCH_FRAGMENT_HEADER {
+ return Err(Error);
+ }
+
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let buffer = self.buffer.as_ref();
+ if buffer.is_empty() {
+ return Err(Error);
+ }
+
+ match self.dispatch() {
+ DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() >= FIRST_FRAGMENT_HEADER_SIZE => Ok(()),
+ DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() < FIRST_FRAGMENT_HEADER_SIZE => {
+ Err(Error)
+ }
+ DISPATCH_FRAGMENT_HEADER if buffer.len() >= NEXT_FRAGMENT_HEADER_SIZE => Ok(()),
+ DISPATCH_FRAGMENT_HEADER if buffer.len() < NEXT_FRAGMENT_HEADER_SIZE => Err(Error),
+ _ => Err(Error),
+ }
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the dispatch field.
+ pub fn dispatch(&self) -> u8 {
+ let raw = self.buffer.as_ref();
+ raw[field::DISPATCH] >> 3
+ }
+
+ /// Return the total datagram size.
+ pub fn datagram_size(&self) -> u16 {
+ let raw = self.buffer.as_ref();
+ NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]) & 0b111_1111_1111
+ }
+
+ /// Return the datagram tag.
+ pub fn datagram_tag(&self) -> u16 {
+ let raw = self.buffer.as_ref();
+ NetworkEndian::read_u16(&raw[field::DATAGRAM_TAG])
+ }
+
+ /// Return the datagram offset.
+ pub fn datagram_offset(&self) -> u8 {
+ match self.dispatch() {
+ DISPATCH_FIRST_FRAGMENT_HEADER => 0,
+ DISPATCH_FRAGMENT_HEADER => {
+ let raw = self.buffer.as_ref();
+ raw[field::DATAGRAM_OFFSET]
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ /// Returns `true` when this header is from the first fragment of a link.
+ pub fn is_first_fragment(&self) -> bool {
+ self.dispatch() == DISPATCH_FIRST_FRAGMENT_HEADER
+ }
+
+ /// Returns the key for identifying the packet it belongs to.
+ pub fn get_key(&self, ieee802154_repr: &Ieee802154Repr) -> Key {
+ Key {
+ ll_src_addr: ieee802154_repr.src_addr.unwrap(),
+ ll_dst_addr: ieee802154_repr.dst_addr.unwrap(),
+ datagram_size: self.datagram_size(),
+ datagram_tag: self.datagram_tag(),
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return the payload.
+ pub fn payload(&self) -> &'a [u8] {
+ match self.dispatch() {
+ DISPATCH_FIRST_FRAGMENT_HEADER => {
+ let raw = self.buffer.as_ref();
+ &raw[field::FIRST_FRAGMENT_REST]
+ }
+ DISPATCH_FRAGMENT_HEADER => {
+ let raw = self.buffer.as_ref();
+ &raw[field::NEXT_FRAGMENT_REST]
+ }
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ fn set_dispatch_field(&mut self, value: u8) {
+ let raw = self.buffer.as_mut();
+ raw[field::DISPATCH] = (raw[field::DISPATCH] & !(0b11111 << 3)) | (value << 3);
+ }
+
+ fn set_datagram_size(&mut self, size: u16) {
+ let raw = self.buffer.as_mut();
+ let mut v = NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]);
+ v = (v & !0b111_1111_1111) | size;
+
+ NetworkEndian::write_u16(&mut raw[field::DATAGRAM_SIZE], v);
+ }
+
+ fn set_datagram_tag(&mut self, tag: u16) {
+ let raw = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut raw[field::DATAGRAM_TAG], tag);
+ }
+
+ fn set_datagram_offset(&mut self, offset: u8) {
+ let raw = self.buffer.as_mut();
+ raw[field::DATAGRAM_OFFSET] = offset;
+ }
+}
+
+/// A high-level representation of a 6LoWPAN Fragment header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Repr {
+ FirstFragment { size: u16, tag: u16 },
+ Fragment { size: u16, tag: u16, offset: u8 },
+}
+
+impl core::fmt::Display for Repr {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Repr::FirstFragment { size, tag } => {
+ write!(f, "FirstFrag size={size} tag={tag}")
+ }
+ Repr::Fragment { size, tag, offset } => {
+ write!(f, "NthFrag size={size} tag={tag} offset={offset}")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Repr {
+ fn format(&self, fmt: defmt::Formatter) {
+ match self {
+ Repr::FirstFragment { size, tag } => {
+ defmt::write!(fmt, "FirstFrag size={} tag={}", size, tag);
+ }
+ Repr::Fragment { size, tag, offset } => {
+ defmt::write!(fmt, "NthFrag size={} tag={} offset={}", size, tag, offset);
+ }
+ }
+ }
+}
+
+impl Repr {
+ /// Parse a 6LoWPAN Fragment header.
+ pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Self> {
+ let size = packet.datagram_size();
+ let tag = packet.datagram_tag();
+
+ match packet.dispatch() {
+ DISPATCH_FIRST_FRAGMENT_HEADER => Ok(Self::FirstFragment { size, tag }),
+ DISPATCH_FRAGMENT_HEADER => Ok(Self::Fragment {
+ size,
+ tag,
+ offset: packet.datagram_offset(),
+ }),
+ _ => Err(Error),
+ }
+ }
+
+ /// Returns the length of the Fragment header.
+ pub const fn buffer_len(&self) -> usize {
+ match self {
+ Self::FirstFragment { .. } => field::FIRST_FRAGMENT_REST.start,
+ Self::Fragment { .. } => field::NEXT_FRAGMENT_REST.start,
+ }
+ }
+
+ /// Emit a high-level representation into a 6LoWPAN Fragment header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+ match self {
+ Self::FirstFragment { size, tag } => {
+ packet.set_dispatch_field(DISPATCH_FIRST_FRAGMENT_HEADER);
+ packet.set_datagram_size(*size);
+ packet.set_datagram_tag(*tag);
+ }
+ Self::Fragment { size, tag, offset } => {
+ packet.set_dispatch_field(DISPATCH_FRAGMENT_HEADER);
+ packet.set_datagram_size(*size);
+ packet.set_datagram_tag(*tag);
+ packet.set_datagram_offset(*offset);
+ }
+ }
+ }
+}
diff --git a/src/wire/sixlowpan/iphc.rs b/src/wire/sixlowpan/iphc.rs
new file mode 100644
index 0000000..f9dcc2b
--- /dev/null
+++ b/src/wire/sixlowpan/iphc.rs
@@ -0,0 +1,948 @@
+//! Implementation of IP Header Compression from [RFC 6282 § 3.1].
+//! It defines the compression of IPv6 headers.
+//!
+//! [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1
+
+use super::{
+ AddressContext, AddressMode, Error, NextHeader, Result, UnresolvedAddress, DISPATCH_IPHC_HEADER,
+};
+use crate::wire::{ieee802154::Address as LlAddress, ipv6, IpProtocol};
+use byteorder::{ByteOrder, NetworkEndian};
+
+mod field {
+ use crate::wire::field::*;
+
+ pub const IPHC_FIELD: Field = 0..2;
+}
+
+macro_rules! get_field {
+ ($name:ident, $mask:expr, $shift:expr) => {
+ fn $name(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::IPHC_FIELD]);
+ ((raw >> $shift) & $mask) as u8
+ }
+ };
+}
+
+macro_rules! set_field {
+ ($name:ident, $mask:expr, $shift:expr) => {
+ fn $name(&mut self, val: u8) {
+ let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
+ let mut raw = NetworkEndian::read_u16(data);
+
+ raw = (raw & !($mask << $shift)) | ((val as u16) << $shift);
+ NetworkEndian::write_u16(data, raw);
+ }
+ };
+}
+
+/// A read/write wrapper around a 6LoWPAN IPHC header.
+/// [RFC 6282 § 3.1] specifies the format of the header.
+///
+/// The header always start with the following base format (from [RFC 6282 § 3.1.1]):
+/// ```txt
+/// 0 1
+/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+/// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+/// | 0 | 1 | 1 | TF |NH | HLIM |CID|SAC| SAM | M |DAC| DAM |
+/// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+/// ```
+/// With:
+/// - TF: Traffic Class and Flow Label
+/// - NH: Next Header
+/// - HLIM: Hop Limit
+/// - CID: Context Identifier Extension
+/// - SAC: Source Address Compression
+/// - SAM: Source Address Mode
+/// - M: Multicast Compression
+/// - DAC: Destination Address Compression
+/// - DAM: Destination Address Mode
+///
+/// Depending on the flags in the base format, the following fields are added to the header:
+/// - Traffic Class and Flow Label
+/// - Next Header
+/// - Hop Limit
+/// - IPv6 source address
+/// - IPv6 destination address
+///
+/// [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1
+/// [RFC 6282 § 3.1.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1.1
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Input a raw octet buffer with a 6LoWPAN IPHC header structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let buffer = self.buffer.as_ref();
+ if buffer.len() < 2 {
+ return Err(Error);
+ }
+
+ let mut offset = self.ip_fields_start()
+ + self.traffic_class_size()
+ + self.next_header_size()
+ + self.hop_limit_size();
+ offset += self.src_address_size();
+ offset += self.dst_address_size();
+
+ if offset as usize > buffer.len() {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the Next Header field.
+ pub fn next_header(&self) -> NextHeader {
+ let nh = self.nh_field();
+
+ if nh == 1 {
+ // The next header field is compressed.
+ // It is also encoded using LOWPAN_NHC.
+ NextHeader::Compressed
+ } else {
+ // The full 8 bits for Next Header are carried in-line.
+ let start = (self.ip_fields_start() + self.traffic_class_size()) as usize;
+
+ let data = self.buffer.as_ref();
+ let nh = data[start..start + 1][0];
+ NextHeader::Uncompressed(IpProtocol::from(nh))
+ }
+ }
+
+ /// Return the Hop Limit.
+ pub fn hop_limit(&self) -> u8 {
+ match self.hlim_field() {
+ 0b00 => {
+ let start = (self.ip_fields_start()
+ + self.traffic_class_size()
+ + self.next_header_size()) as usize;
+
+ let data = self.buffer.as_ref();
+ data[start..start + 1][0]
+ }
+ 0b01 => 1,
+ 0b10 => 64,
+ 0b11 => 255,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the Source Context Identifier.
+ pub fn src_context_id(&self) -> Option<u8> {
+ if self.cid_field() == 1 {
+ let data = self.buffer.as_ref();
+ Some(data[2] >> 4)
+ } else {
+ None
+ }
+ }
+
+ /// Return the Destination Context Identifier.
+ pub fn dst_context_id(&self) -> Option<u8> {
+ if self.cid_field() == 1 {
+ let data = self.buffer.as_ref();
+ Some(data[2] & 0x0f)
+ } else {
+ None
+ }
+ }
+
+ /// Return the ECN field (when it is inlined).
+ pub fn ecn_field(&self) -> Option<u8> {
+ match self.tf_field() {
+ 0b00 | 0b01 | 0b10 => {
+ let start = self.ip_fields_start() as usize;
+ Some(self.buffer.as_ref()[start..][0] & 0b1100_0000)
+ }
+ 0b11 => None,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the DSCP field (when it is inlined).
+ pub fn dscp_field(&self) -> Option<u8> {
+ match self.tf_field() {
+ 0b00 | 0b10 => {
+ let start = self.ip_fields_start() as usize;
+ Some(self.buffer.as_ref()[start..][0] & 0b111111)
+ }
+ 0b01 | 0b11 => None,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the flow label field (when it is inlined).
+ pub fn flow_label_field(&self) -> Option<u16> {
+ match self.tf_field() {
+ 0b00 => {
+ let start = self.ip_fields_start() as usize;
+ Some(NetworkEndian::read_u16(
+ &self.buffer.as_ref()[start..][2..4],
+ ))
+ }
+ 0b01 => {
+ let start = self.ip_fields_start() as usize;
+ Some(NetworkEndian::read_u16(
+ &self.buffer.as_ref()[start..][1..3],
+ ))
+ }
+ 0b10 | 0b11 => None,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the Source Address.
+ pub fn src_addr(&self) -> Result<UnresolvedAddress> {
+ let start = (self.ip_fields_start()
+ + self.traffic_class_size()
+ + self.next_header_size()
+ + self.hop_limit_size()) as usize;
+
+ let data = self.buffer.as_ref();
+ match (self.sac_field(), self.sam_field()) {
+ (0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
+ &data[start..][..16],
+ ))),
+ (0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::InLine64bits(&data[start..][..8]),
+ )),
+ (0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::InLine16bits(&data[start..][..2]),
+ )),
+ (0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)),
+ (1, 0b00) => Ok(UnresolvedAddress::WithContext((
+ 0,
+ AddressMode::Unspecified,
+ ))),
+ (1, 0b01) => {
+ if let Some(id) = self.src_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::InLine64bits(&data[start..][..8]),
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ (1, 0b10) => {
+ if let Some(id) = self.src_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::InLine16bits(&data[start..][..2]),
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ (1, 0b11) => {
+ if let Some(id) = self.src_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::FullyElided,
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ _ => Err(Error),
+ }
+ }
+
+ /// Return the Destination Address.
+ pub fn dst_addr(&self) -> Result<UnresolvedAddress> {
+ let start = (self.ip_fields_start()
+ + self.traffic_class_size()
+ + self.next_header_size()
+ + self.hop_limit_size()
+ + self.src_address_size()) as usize;
+
+ let data = self.buffer.as_ref();
+ match (self.m_field(), self.dac_field(), self.dam_field()) {
+ (0, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
+ &data[start..][..16],
+ ))),
+ (0, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::InLine64bits(&data[start..][..8]),
+ )),
+ (0, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::InLine16bits(&data[start..][..2]),
+ )),
+ (0, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)),
+ (0, 1, 0b00) => Ok(UnresolvedAddress::Reserved),
+ (0, 1, 0b01) => {
+ if let Some(id) = self.dst_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::InLine64bits(&data[start..][..8]),
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ (0, 1, 0b10) => {
+ if let Some(id) = self.dst_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::InLine16bits(&data[start..][..2]),
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ (0, 1, 0b11) => {
+ if let Some(id) = self.dst_context_id() {
+ Ok(UnresolvedAddress::WithContext((
+ id as usize,
+ AddressMode::FullyElided,
+ )))
+ } else {
+ Err(Error)
+ }
+ }
+ (1, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
+ &data[start..][..16],
+ ))),
+ (1, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::Multicast48bits(&data[start..][..6]),
+ )),
+ (1, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::Multicast32bits(&data[start..][..4]),
+ )),
+ (1, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext(
+ AddressMode::Multicast8bits(&data[start..][..1]),
+ )),
+ (1, 1, 0b00) => Ok(UnresolvedAddress::WithContext((
+ 0,
+ AddressMode::NotSupported,
+ ))),
+ (1, 1, 0b01 | 0b10 | 0b11) => Ok(UnresolvedAddress::Reserved),
+ _ => Err(Error),
+ }
+ }
+
+ get_field!(dispatch_field, 0b111, 13);
+ get_field!(tf_field, 0b11, 11);
+ get_field!(nh_field, 0b1, 10);
+ get_field!(hlim_field, 0b11, 8);
+ get_field!(cid_field, 0b1, 7);
+ get_field!(sac_field, 0b1, 6);
+ get_field!(sam_field, 0b11, 4);
+ get_field!(m_field, 0b1, 3);
+ get_field!(dac_field, 0b1, 2);
+ get_field!(dam_field, 0b11, 0);
+
+ /// Return the start for the IP fields.
+ fn ip_fields_start(&self) -> u8 {
+ 2 + self.cid_size()
+ }
+
+ /// Get the size in octets of the traffic class field.
+ fn traffic_class_size(&self) -> u8 {
+ match self.tf_field() {
+ 0b00 => 4,
+ 0b01 => 3,
+ 0b10 => 1,
+ 0b11 => 0,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Get the size in octets of the next header field.
+ fn next_header_size(&self) -> u8 {
+ (self.nh_field() != 1) as u8
+ }
+
+ /// Get the size in octets of the hop limit field.
+ fn hop_limit_size(&self) -> u8 {
+ (self.hlim_field() == 0b00) as u8
+ }
+
+ /// Get the size in octets of the CID field.
+ fn cid_size(&self) -> u8 {
+ (self.cid_field() == 1) as u8
+ }
+
+ /// Get the size in octets of the source address.
+ fn src_address_size(&self) -> u8 {
+ match (self.sac_field(), self.sam_field()) {
+ (0, 0b00) => 16, // The full address is carried in-line.
+ (0, 0b01) => 8, // The first 64 bits are elided.
+ (0, 0b10) => 2, // The first 112 bits are elided.
+ (0, 0b11) => 0, // The address is fully elided.
+ (1, 0b00) => 0, // The UNSPECIFIED address.
+ (1, 0b01) => 8, // Address derived using context information.
+ (1, 0b10) => 2, // Address derived using context information.
+ (1, 0b11) => 0, // Address derived using context information.
+ _ => unreachable!(),
+ }
+ }
+
+ /// Get the size in octets of the address address.
+ fn dst_address_size(&self) -> u8 {
+ match (self.m_field(), self.dac_field(), self.dam_field()) {
+ (0, 0, 0b00) => 16, // The full address is carried in-line.
+ (0, 0, 0b01) => 8, // The first 64 bits are elided.
+ (0, 0, 0b10) => 2, // The first 112 bits are elided.
+ (0, 0, 0b11) => 0, // The address is fully elided.
+ (0, 1, 0b00) => 0, // Reserved.
+ (0, 1, 0b01) => 8, // Address derived using context information.
+ (0, 1, 0b10) => 2, // Address derived using context information.
+ (0, 1, 0b11) => 0, // Address derived using context information.
+ (1, 0, 0b00) => 16, // The full address is carried in-line.
+ (1, 0, 0b01) => 6, // The address takes the form ffXX::00XX:XXXX:XXXX.
+ (1, 0, 0b10) => 4, // The address takes the form ffXX::00XX:XXXX.
+ (1, 0, 0b11) => 1, // The address takes the form ff02::00XX.
+ (1, 1, 0b00) => 6, // Match Unicast-Prefix-based IPv6.
+ (1, 1, 0b01) => 0, // Reserved.
+ (1, 1, 0b10) => 0, // Reserved.
+ (1, 1, 0b11) => 0, // Reserved.
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the length of the header.
+ pub fn header_len(&self) -> usize {
+ let mut len = self.ip_fields_start();
+ len += self.traffic_class_size();
+ len += self.next_header_size();
+ len += self.hop_limit_size();
+ len += self.src_address_size();
+ len += self.dst_address_size();
+
+ len as usize
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the payload.
+ pub fn payload(&self) -> &'a [u8] {
+ let mut len = self.ip_fields_start();
+ len += self.traffic_class_size();
+ len += self.next_header_size();
+ len += self.hop_limit_size();
+ len += self.src_address_size();
+ len += self.dst_address_size();
+
+ let len = len as usize;
+
+ let data = self.buffer.as_ref();
+ &data[len..]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the dispatch field to `0b011`.
+ fn set_dispatch_field(&mut self) {
+ let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
+ let mut raw = NetworkEndian::read_u16(data);
+
+ raw = (raw & !(0b111 << 13)) | (0b11 << 13);
+ NetworkEndian::write_u16(data, raw);
+ }
+
+ set_field!(set_tf_field, 0b11, 11);
+ set_field!(set_nh_field, 0b1, 10);
+ set_field!(set_hlim_field, 0b11, 8);
+ set_field!(set_cid_field, 0b1, 7);
+ set_field!(set_sac_field, 0b1, 6);
+ set_field!(set_sam_field, 0b11, 4);
+ set_field!(set_m_field, 0b1, 3);
+ set_field!(set_dac_field, 0b1, 2);
+ set_field!(set_dam_field, 0b11, 0);
+
+ fn set_field(&mut self, idx: usize, value: &[u8]) {
+ let raw = self.buffer.as_mut();
+ raw[idx..idx + value.len()].copy_from_slice(value);
+ }
+
+ /// Set the Next Header.
+ ///
+ /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
+ fn set_next_header(&mut self, nh: NextHeader, mut idx: usize) -> usize {
+ match nh {
+ NextHeader::Uncompressed(nh) => {
+ self.set_nh_field(0);
+ self.set_field(idx, &[nh.into()]);
+ idx += 1;
+ }
+ NextHeader::Compressed => self.set_nh_field(1),
+ }
+
+ idx
+ }
+
+ /// Set the Hop Limit.
+ ///
+ /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
+ fn set_hop_limit(&mut self, hl: u8, mut idx: usize) -> usize {
+ match hl {
+ 255 => self.set_hlim_field(0b11),
+ 64 => self.set_hlim_field(0b10),
+ 1 => self.set_hlim_field(0b01),
+ _ => {
+ self.set_hlim_field(0b00);
+ self.set_field(idx, &[hl]);
+ idx += 1;
+ }
+ }
+
+ idx
+ }
+
+ /// Set the Source Address based on the IPv6 address and the Link-Local address.
+ ///
+ /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
+ fn set_src_address(
+ &mut self,
+ src_addr: ipv6::Address,
+ ll_src_addr: Option<LlAddress>,
+ mut idx: usize,
+ ) -> usize {
+ self.set_cid_field(0);
+ self.set_sac_field(0);
+ let src = src_addr.as_bytes();
+ if src_addr == ipv6::Address::UNSPECIFIED {
+ self.set_sac_field(1);
+ self.set_sam_field(0b00);
+ } else if src_addr.is_link_local() {
+ // We have a link local address.
+ // The remainder of the address can be elided when the context contains
+ // a 802.15.4 short address or a 802.15.4 extended address which can be
+ // converted to a eui64 address.
+ let is_eui_64 = ll_src_addr
+ .map(|addr| {
+ addr.as_eui_64()
+ .map(|addr| addr[..] == src[8..])
+ .unwrap_or(false)
+ })
+ .unwrap_or(false);
+
+ if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
+ let ll = [src[14], src[15]];
+
+ if ll_src_addr == Some(LlAddress::Short(ll)) {
+ // We have the context from the 802.15.4 frame.
+ // The context contains the short address.
+ // We can elide the source address.
+ self.set_sam_field(0b11);
+ } else {
+ // We don't have the context from the 802.15.4 frame.
+ // We cannot elide the source address, however we can elide 112 bits.
+ self.set_sam_field(0b10);
+
+ self.set_field(idx, &src[14..]);
+ idx += 2;
+ }
+ } else if is_eui_64 {
+ // We have the context from the 802.15.4 frame.
+ // The context contains the extended address.
+ // We can elide the source address.
+ self.set_sam_field(0b11);
+ } else {
+ // We cannot elide the source address, however we can elide 64 bits.
+ self.set_sam_field(0b01);
+
+ self.set_field(idx, &src[8..]);
+ idx += 8;
+ }
+ } else {
+ // We cannot elide anything.
+ self.set_sam_field(0b00);
+ self.set_field(idx, src);
+ idx += 16;
+ }
+
+ idx
+ }
+
+ /// Set the Destination Address based on the IPv6 address and the Link-Local address.
+ ///
+ /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
+ fn set_dst_address(
+ &mut self,
+ dst_addr: ipv6::Address,
+ ll_dst_addr: Option<LlAddress>,
+ mut idx: usize,
+ ) -> usize {
+ self.set_dac_field(0);
+ self.set_dam_field(0);
+ self.set_m_field(0);
+ let dst = dst_addr.as_bytes();
+ if dst_addr.is_multicast() {
+ self.set_m_field(1);
+
+ if dst[1] == 0x02 && dst[2..15] == [0; 13] {
+ self.set_dam_field(0b11);
+
+ self.set_field(idx, &[dst[15]]);
+ idx += 1;
+ } else if dst[2..13] == [0; 11] {
+ self.set_dam_field(0b10);
+
+ self.set_field(idx, &[dst[1]]);
+ idx += 1;
+ self.set_field(idx, &dst[13..]);
+ idx += 3;
+ } else if dst[2..11] == [0; 9] {
+ self.set_dam_field(0b01);
+
+ self.set_field(idx, &[dst[1]]);
+ idx += 1;
+ self.set_field(idx, &dst[11..]);
+ idx += 5;
+ } else {
+ self.set_dam_field(0b11);
+
+ self.set_field(idx, dst);
+ idx += 16;
+ }
+ } else if dst_addr.is_link_local() {
+ let is_eui_64 = ll_dst_addr
+ .map(|addr| {
+ addr.as_eui_64()
+ .map(|addr| addr[..] == dst[8..])
+ .unwrap_or(false)
+ })
+ .unwrap_or(false);
+
+ if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
+ let ll = [dst[14], dst[15]];
+
+ if ll_dst_addr == Some(LlAddress::Short(ll)) {
+ self.set_dam_field(0b11);
+ } else {
+ self.set_dam_field(0b10);
+
+ self.set_field(idx, &dst[14..]);
+ idx += 2;
+ }
+ } else if is_eui_64 {
+ self.set_dam_field(0b11);
+ } else {
+ self.set_dam_field(0b01);
+
+ self.set_field(idx, &dst[8..]);
+ idx += 8;
+ }
+ } else {
+ self.set_dam_field(0b00);
+
+ self.set_field(idx, dst);
+ idx += 16;
+ }
+
+ idx
+ }
+
+ /// Return a mutable pointer to the payload.
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let mut len = self.ip_fields_start();
+
+ len += self.traffic_class_size();
+ len += self.next_header_size();
+ len += self.hop_limit_size();
+ len += self.src_address_size();
+ len += self.dst_address_size();
+
+ let len = len as usize;
+
+ let data = self.buffer.as_mut();
+ &mut data[len..]
+ }
+}
+
+/// A high-level representation of a 6LoWPAN IPHC header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr {
+ pub src_addr: ipv6::Address,
+ pub ll_src_addr: Option<LlAddress>,
+ pub dst_addr: ipv6::Address,
+ pub ll_dst_addr: Option<LlAddress>,
+ pub next_header: NextHeader,
+ pub hop_limit: u8,
+ // TODO(thvdveld): refactor the following fields into something else
+ pub ecn: Option<u8>,
+ pub dscp: Option<u8>,
+ pub flow_label: Option<u16>,
+}
+
+impl core::fmt::Display for Repr {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ f,
+ "IPHC src={} dst={} nxt-hdr={} hop-limit={}",
+ self.src_addr, self.dst_addr, self.next_header, self.hop_limit
+ )
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Repr {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(
+ fmt,
+ "IPHC src={} dst={} nxt-hdr={} hop-limit={}",
+ self.src_addr,
+ self.dst_addr,
+ self.next_header,
+ self.hop_limit
+ );
+ }
+}
+
+impl Repr {
+ /// Parse a 6LoWPAN IPHC header and return a high-level representation.
+ ///
+ /// The `ll_src_addr` and `ll_dst_addr` are the link-local addresses used for resolving the
+ /// IPv6 packets.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(
+ packet: &Packet<&T>,
+ ll_src_addr: Option<LlAddress>,
+ ll_dst_addr: Option<LlAddress>,
+ addr_context: &[AddressContext],
+ ) -> Result<Self> {
+ // Ensure basic accessors will work.
+ packet.check_len()?;
+
+ if packet.dispatch_field() != DISPATCH_IPHC_HEADER {
+ // This is not an LOWPAN_IPHC packet.
+ return Err(Error);
+ }
+
+ let src_addr = packet.src_addr()?.resolve(ll_src_addr, addr_context)?;
+ let dst_addr = packet.dst_addr()?.resolve(ll_dst_addr, addr_context)?;
+
+ Ok(Self {
+ src_addr,
+ ll_src_addr,
+ dst_addr,
+ ll_dst_addr,
+ next_header: packet.next_header(),
+ hop_limit: packet.hop_limit(),
+ ecn: packet.ecn_field(),
+ dscp: packet.dscp_field(),
+ flow_label: packet.flow_label_field(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub fn buffer_len(&self) -> usize {
+ let mut len = 0;
+ len += 2; // The minimal header length
+
+ len += match self.next_header {
+ NextHeader::Compressed => 0, // The next header is compressed (we don't need to inline what the next header is)
+ NextHeader::Uncompressed(_) => 1, // The next header field is inlined
+ };
+
+ // Hop Limit size
+ len += match self.hop_limit {
+ 255 | 64 | 1 => 0, // We can inline the hop limit
+ _ => 1,
+ };
+
+ // Add the length of the source address
+ len += if self.src_addr == ipv6::Address::UNSPECIFIED {
+ 0
+ } else if self.src_addr.is_link_local() {
+ let src = self.src_addr.as_bytes();
+ let ll = [src[14], src[15]];
+
+ let is_eui_64 = self
+ .ll_src_addr
+ .map(|addr| {
+ addr.as_eui_64()
+ .map(|addr| addr[..] == src[8..])
+ .unwrap_or(false)
+ })
+ .unwrap_or(false);
+
+ if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
+ if self.ll_src_addr == Some(LlAddress::Short(ll)) {
+ 0
+ } else {
+ 2
+ }
+ } else if is_eui_64 {
+ 0
+ } else {
+ 8
+ }
+ } else {
+ 16
+ };
+
+ // Add the size of the destination header
+ let dst = self.dst_addr.as_bytes();
+ len += if self.dst_addr.is_multicast() {
+ if dst[1] == 0x02 && dst[2..15] == [0; 13] {
+ 1
+ } else if dst[2..13] == [0; 11] {
+ 4
+ } else if dst[2..11] == [0; 9] {
+ 6
+ } else {
+ 16
+ }
+ } else if self.dst_addr.is_link_local() {
+ let is_eui_64 = self
+ .ll_dst_addr
+ .map(|addr| {
+ addr.as_eui_64()
+ .map(|addr| addr[..] == dst[8..])
+ .unwrap_or(false)
+ })
+ .unwrap_or(false);
+
+ if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
+ let ll = [dst[14], dst[15]];
+
+ if self.ll_dst_addr == Some(LlAddress::Short(ll)) {
+ 0
+ } else {
+ 2
+ }
+ } else if is_eui_64 {
+ 0
+ } else {
+ 8
+ }
+ } else {
+ 16
+ };
+
+ len += match (self.ecn, self.dscp, self.flow_label) {
+ (Some(_), Some(_), Some(_)) => 4,
+ (Some(_), None, Some(_)) => 3,
+ (Some(_), Some(_), None) => 1,
+ (None, None, None) => 0,
+ _ => unreachable!(),
+ };
+
+ len
+ }
+
+ /// Emit a high-level representation into a 6LoWPAN IPHC header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+ let idx = 2;
+
+ packet.set_dispatch_field();
+
+ // FIXME(thvdveld): we don't set anything from the traffic flow.
+ packet.set_tf_field(0b11);
+
+ let idx = packet.set_next_header(self.next_header, idx);
+ let idx = packet.set_hop_limit(self.hop_limit, idx);
+ let idx = packet.set_src_address(self.src_addr, self.ll_src_addr, idx);
+ packet.set_dst_address(self.dst_addr, self.ll_dst_addr, idx);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn iphc_fields() {
+ let bytes = [
+ 0x7a, 0x33, // IPHC
+ 0x3a, // Next header
+ ];
+
+ let packet = Packet::new_unchecked(bytes);
+
+ assert_eq!(packet.dispatch_field(), 0b011);
+ assert_eq!(packet.tf_field(), 0b11);
+ assert_eq!(packet.nh_field(), 0b0);
+ assert_eq!(packet.hlim_field(), 0b10);
+ assert_eq!(packet.cid_field(), 0b0);
+ assert_eq!(packet.sac_field(), 0b0);
+ assert_eq!(packet.sam_field(), 0b11);
+ assert_eq!(packet.m_field(), 0b0);
+ assert_eq!(packet.dac_field(), 0b0);
+ assert_eq!(packet.dam_field(), 0b11);
+
+ assert_eq!(
+ packet.next_header(),
+ NextHeader::Uncompressed(IpProtocol::Icmpv6)
+ );
+
+ assert_eq!(packet.src_address_size(), 0);
+ assert_eq!(packet.dst_address_size(), 0);
+ assert_eq!(packet.hop_limit(), 64);
+
+ assert_eq!(
+ packet.src_addr(),
+ Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided))
+ );
+ assert_eq!(
+ packet.dst_addr(),
+ Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided))
+ );
+
+ let bytes = [
+ 0x7e, 0xf7, // IPHC,
+ 0x00, // CID
+ ];
+
+ let packet = Packet::new_unchecked(bytes);
+
+ assert_eq!(packet.dispatch_field(), 0b011);
+ assert_eq!(packet.tf_field(), 0b11);
+ assert_eq!(packet.nh_field(), 0b1);
+ assert_eq!(packet.hlim_field(), 0b10);
+ assert_eq!(packet.cid_field(), 0b1);
+ assert_eq!(packet.sac_field(), 0b1);
+ assert_eq!(packet.sam_field(), 0b11);
+ assert_eq!(packet.m_field(), 0b0);
+ assert_eq!(packet.dac_field(), 0b1);
+ assert_eq!(packet.dam_field(), 0b11);
+
+ assert_eq!(packet.next_header(), NextHeader::Compressed);
+
+ assert_eq!(packet.src_address_size(), 0);
+ assert_eq!(packet.dst_address_size(), 0);
+ assert_eq!(packet.hop_limit(), 64);
+
+ assert_eq!(
+ packet.src_addr(),
+ Ok(UnresolvedAddress::WithContext((
+ 0,
+ AddressMode::FullyElided
+ )))
+ );
+ assert_eq!(
+ packet.dst_addr(),
+ Ok(UnresolvedAddress::WithContext((
+ 0,
+ AddressMode::FullyElided
+ )))
+ );
+ }
+}
diff --git a/src/wire/sixlowpan/mod.rs b/src/wire/sixlowpan/mod.rs
new file mode 100644
index 0000000..03a5218
--- /dev/null
+++ b/src/wire/sixlowpan/mod.rs
@@ -0,0 +1,371 @@
+//! Implementation of [RFC 6282] which specifies a compression format for IPv6 datagrams over
+//! IEEE802.154-based networks.
+//!
+//! [RFC 6282]: https://datatracker.ietf.org/doc/html/rfc6282
+
+use super::{Error, Result};
+use crate::wire::ieee802154::Address as LlAddress;
+use crate::wire::ipv6;
+use crate::wire::IpProtocol;
+
+pub mod frag;
+pub mod iphc;
+pub mod nhc;
+
+const ADDRESS_CONTEXT_LENGTH: usize = 8;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AddressContext(pub [u8; ADDRESS_CONTEXT_LENGTH]);
+
+/// The representation of an unresolved address. 6LoWPAN compression of IPv6 addresses can be with
+/// and without context information. The decompression with context information is not yet
+/// implemented.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum UnresolvedAddress<'a> {
+ WithoutContext(AddressMode<'a>),
+ WithContext((usize, AddressMode<'a>)),
+ Reserved,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum AddressMode<'a> {
+ /// The full address is carried in-line.
+ FullInline(&'a [u8]),
+ /// The first 64-bits of the address are elided. The value of those bits
+ /// is the link-local prefix padded with zeros. The remaining 64 bits are
+ /// carried in-line.
+ InLine64bits(&'a [u8]),
+ /// The first 112 bits of the address are elided. The value of the first
+ /// 64 bits is the link-local prefix padded with zeros. The following 64 bits
+ /// are 0000:00ff:fe00:XXXX, where XXXX are the 16 bits carried in-line.
+ InLine16bits(&'a [u8]),
+ /// The address is fully elided. The first 64 bits of the address are
+ /// the link-local prefix padded with zeros. The remaining 64 bits are
+ /// computed from the encapsulating header (e.g., 802.15.4 or IPv6 source address)
+ /// as specified in Section 3.2.2.
+ FullyElided,
+ /// The address takes the form ffXX::00XX:XXXX:XXXX
+ Multicast48bits(&'a [u8]),
+ /// The address takes the form ffXX::00XX:XXXX.
+ Multicast32bits(&'a [u8]),
+ /// The address takes the form ff02::00XX.
+ Multicast8bits(&'a [u8]),
+ /// The unspecified address.
+ Unspecified,
+ NotSupported,
+}
+
+const LINK_LOCAL_PREFIX: [u8; 2] = [0xfe, 0x80];
+const EUI64_MIDDLE_VALUE: [u8; 2] = [0xff, 0xfe];
+
+impl<'a> UnresolvedAddress<'a> {
+ pub fn resolve(
+ self,
+ ll_address: Option<LlAddress>,
+ addr_context: &[AddressContext],
+ ) -> Result<ipv6::Address> {
+ let mut bytes = [0; 16];
+
+ let copy_context = |index: usize, bytes: &mut [u8]| -> Result<()> {
+ if index >= addr_context.len() {
+ return Err(Error);
+ }
+
+ let context = addr_context[index];
+ bytes[..ADDRESS_CONTEXT_LENGTH].copy_from_slice(&context.0);
+
+ Ok(())
+ };
+
+ match self {
+ UnresolvedAddress::WithoutContext(mode) => match mode {
+ AddressMode::FullInline(addr) => Ok(ipv6::Address::from_bytes(addr)),
+ AddressMode::InLine64bits(inline) => {
+ bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
+ bytes[8..].copy_from_slice(inline);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ AddressMode::InLine16bits(inline) => {
+ bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
+ bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
+ bytes[14..].copy_from_slice(inline);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ AddressMode::FullyElided => {
+ bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
+ match ll_address {
+ Some(LlAddress::Short(ll)) => {
+ bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
+ bytes[14..].copy_from_slice(&ll);
+ }
+ Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
+ Some(addr) => bytes[8..].copy_from_slice(&addr),
+ None => return Err(Error),
+ },
+ Some(LlAddress::Absent) => return Err(Error),
+ None => return Err(Error),
+ }
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ AddressMode::Multicast48bits(inline) => {
+ bytes[0] = 0xff;
+ bytes[1] = inline[0];
+ bytes[11..].copy_from_slice(&inline[1..][..5]);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ AddressMode::Multicast32bits(inline) => {
+ bytes[0] = 0xff;
+ bytes[1] = inline[0];
+ bytes[13..].copy_from_slice(&inline[1..][..3]);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ AddressMode::Multicast8bits(inline) => {
+ bytes[0] = 0xff;
+ bytes[1] = 0x02;
+ bytes[15] = inline[0];
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ _ => Err(Error),
+ },
+ UnresolvedAddress::WithContext(mode) => match mode {
+ (_, AddressMode::Unspecified) => Ok(ipv6::Address::UNSPECIFIED),
+ (index, AddressMode::InLine64bits(inline)) => {
+ copy_context(index, &mut bytes[..])?;
+ bytes[16 - inline.len()..].copy_from_slice(inline);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ (index, AddressMode::InLine16bits(inline)) => {
+ copy_context(index, &mut bytes[..])?;
+ bytes[16 - inline.len()..].copy_from_slice(inline);
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ (index, AddressMode::FullyElided) => {
+ match ll_address {
+ Some(LlAddress::Short(ll)) => {
+ bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
+ bytes[14..].copy_from_slice(&ll);
+ }
+ Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
+ Some(addr) => bytes[8..].copy_from_slice(&addr),
+ None => return Err(Error),
+ },
+ Some(LlAddress::Absent) => return Err(Error),
+ None => return Err(Error),
+ }
+
+ copy_context(index, &mut bytes[..])?;
+
+ Ok(ipv6::Address::from_bytes(&bytes[..]))
+ }
+ _ => Err(Error),
+ },
+ UnresolvedAddress::Reserved => Err(Error),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SixlowpanPacket {
+ FragmentHeader,
+ IphcHeader,
+}
+
+const DISPATCH_FIRST_FRAGMENT_HEADER: u8 = 0b11000;
+const DISPATCH_FRAGMENT_HEADER: u8 = 0b11100;
+const DISPATCH_IPHC_HEADER: u8 = 0b011;
+const DISPATCH_UDP_HEADER: u8 = 0b11110;
+const DISPATCH_EXT_HEADER: u8 = 0b1110;
+
+impl SixlowpanPacket {
+ /// Returns the type of the 6LoWPAN header.
+ /// This can either be a fragment header or an IPHC header.
+ ///
+ /// # Errors
+ /// Returns `[Error::Unrecognized]` when neither the Fragment Header dispatch or the IPHC
+ /// dispatch is recognized.
+ pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result<Self> {
+ let raw = buffer.as_ref();
+
+ if raw.is_empty() {
+ return Err(Error);
+ }
+
+ if raw[0] >> 3 == DISPATCH_FIRST_FRAGMENT_HEADER || raw[0] >> 3 == DISPATCH_FRAGMENT_HEADER
+ {
+ Ok(Self::FragmentHeader)
+ } else if raw[0] >> 5 == DISPATCH_IPHC_HEADER {
+ Ok(Self::IphcHeader)
+ } else {
+ Err(Error)
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum NextHeader {
+ Compressed,
+ Uncompressed(IpProtocol),
+}
+
+impl core::fmt::Display for NextHeader {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ NextHeader::Compressed => write!(f, "compressed"),
+ NextHeader::Uncompressed(protocol) => write!(f, "{protocol}"),
+ }
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for NextHeader {
+ fn format(&self, fmt: defmt::Formatter) {
+ match self {
+ NextHeader::Compressed => defmt::write!(fmt, "compressed"),
+ NextHeader::Uncompressed(protocol) => defmt::write!(fmt, "{}", protocol),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn sixlowpan_fragment_emit() {
+ let repr = frag::Repr::FirstFragment {
+ size: 0xff,
+ tag: 0xabcd,
+ };
+ let buffer = [0u8; 4];
+ let mut packet = frag::Packet::new_unchecked(buffer);
+
+ assert_eq!(repr.buffer_len(), 4);
+ repr.emit(&mut packet);
+
+ assert_eq!(packet.datagram_size(), 0xff);
+ assert_eq!(packet.datagram_tag(), 0xabcd);
+ assert_eq!(packet.into_inner(), [0xc0, 0xff, 0xab, 0xcd]);
+
+ let repr = frag::Repr::Fragment {
+ size: 0xff,
+ tag: 0xabcd,
+ offset: 0xcc,
+ };
+ let buffer = [0u8; 5];
+ let mut packet = frag::Packet::new_unchecked(buffer);
+
+ assert_eq!(repr.buffer_len(), 5);
+ repr.emit(&mut packet);
+
+ assert_eq!(packet.datagram_size(), 0xff);
+ assert_eq!(packet.datagram_tag(), 0xabcd);
+ assert_eq!(packet.into_inner(), [0xe0, 0xff, 0xab, 0xcd, 0xcc]);
+ }
+
+ #[test]
+ fn sixlowpan_three_fragments() {
+ use crate::wire::ieee802154::Frame as Ieee802154Frame;
+ use crate::wire::ieee802154::Repr as Ieee802154Repr;
+ use crate::wire::Ieee802154Address;
+
+ let key = frag::Key {
+ ll_src_addr: Ieee802154Address::Extended([50, 147, 130, 47, 40, 8, 62, 217]),
+ ll_dst_addr: Ieee802154Address::Extended([26, 11, 66, 66, 66, 66, 66, 66]),
+ datagram_size: 307,
+ datagram_tag: 63,
+ };
+
+ let frame1: &[u8] = &[
+ 0x41, 0xcc, 0x92, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
+ 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xc1, 0x33, 0x00, 0x3f, 0x6e, 0x33, 0x02,
+ 0x35, 0x3d, 0xf0, 0xd2, 0x5f, 0x1b, 0x39, 0xb4, 0x6b, 0x4c, 0x6f, 0x72, 0x65, 0x6d,
+ 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73,
+ 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65,
+ 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63,
+ 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x41, 0x6c, 0x69, 0x71,
+ 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64, 0x69, 0x6f, 0x2c, 0x20,
+ 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65, 0x6c, 0x20, 0x72,
+ ];
+
+ let ieee802154_frame = Ieee802154Frame::new_checked(frame1).unwrap();
+ let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
+
+ let sixlowpan_frame =
+ SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
+
+ let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
+ frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
+ } else {
+ unreachable!()
+ };
+
+ assert_eq!(frag.datagram_size(), 307);
+ assert_eq!(frag.datagram_tag(), 0x003f);
+ assert_eq!(frag.datagram_offset(), 0);
+
+ assert_eq!(frag.get_key(&ieee802154_repr), key);
+
+ let frame2: &[u8] = &[
+ 0x41, 0xcc, 0x93, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
+ 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x11, 0x75, 0x74,
+ 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74, 0x72, 0x69, 0x73, 0x74, 0x69,
+ 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6e, 0x63, 0x20, 0x65,
+ 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65, 0x2e, 0x20, 0x4c, 0x6f, 0x72,
+ 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72,
+ 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e,
+ 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69,
+ 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74,
+ ];
+
+ let ieee802154_frame = Ieee802154Frame::new_checked(frame2).unwrap();
+ let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
+
+ let sixlowpan_frame =
+ SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
+
+ let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
+ frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
+ } else {
+ unreachable!()
+ };
+
+ assert_eq!(frag.datagram_size(), 307);
+ assert_eq!(frag.datagram_tag(), 0x003f);
+ assert_eq!(frag.datagram_offset(), 136 / 8);
+
+ assert_eq!(frag.get_key(&ieee802154_repr), key);
+
+ let frame3: &[u8] = &[
+ 0x41, 0xcc, 0x94, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
+ 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x1d, 0x2e, 0x20,
+ 0x41, 0x6c, 0x69, 0x71, 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64,
+ 0x69, 0x6f, 0x2c, 0x20, 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65,
+ 0x6c, 0x20, 0x72, 0x75, 0x74, 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74,
+ 0x72, 0x69, 0x73, 0x74, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e,
+ 0x75, 0x6e, 0x63, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65,
+ 0x2e, 0x20, 0x0a,
+ ];
+
+ let ieee802154_frame = Ieee802154Frame::new_checked(frame3).unwrap();
+ let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
+
+ let sixlowpan_frame =
+ SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
+
+ let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
+ frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
+ } else {
+ unreachable!()
+ };
+
+ assert_eq!(frag.datagram_size(), 307);
+ assert_eq!(frag.datagram_tag(), 0x003f);
+ assert_eq!(frag.datagram_offset(), 232 / 8);
+
+ assert_eq!(frag.get_key(&ieee802154_repr), key);
+ }
+}
diff --git a/src/wire/sixlowpan/nhc.rs b/src/wire/sixlowpan/nhc.rs
new file mode 100644
index 0000000..5539ce4
--- /dev/null
+++ b/src/wire/sixlowpan/nhc.rs
@@ -0,0 +1,890 @@
+//! Implementation of Next Header Compression from [RFC 6282 § 4].
+//!
+//! [RFC 6282 § 4]: https://datatracker.ietf.org/doc/html/rfc6282#section-4
+use super::{Error, NextHeader, Result, DISPATCH_EXT_HEADER, DISPATCH_UDP_HEADER};
+use crate::{
+ phy::ChecksumCapabilities,
+ wire::{
+ ip::{checksum, Address as IpAddress},
+ ipv6,
+ udp::Repr as UdpRepr,
+ IpProtocol,
+ },
+};
+use byteorder::{ByteOrder, NetworkEndian};
+use ipv6::Address;
+
+macro_rules! get_field {
+ ($name:ident, $mask:expr, $shift:expr) => {
+ fn $name(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ let raw = &data[0];
+ ((raw >> $shift) & $mask) as u8
+ }
+ };
+}
+
+macro_rules! set_field {
+ ($name:ident, $mask:expr, $shift:expr) => {
+ fn $name(&mut self, val: u8) {
+ let data = self.buffer.as_mut();
+ let mut raw = data[0];
+ raw = (raw & !($mask << $shift)) | (val << $shift);
+ data[0] = raw;
+ }
+ };
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+/// A read/write wrapper around a 6LoWPAN_NHC Header.
+/// [RFC 6282 § 4.2] specifies the format of the header.
+///
+/// The header has the following format:
+/// ```txt
+/// 0 1 2 3 4 5 6 7
+/// +---+---+---+---+---+---+---+---+
+/// | 1 | 1 | 1 | 0 | EID |NH |
+/// +---+---+---+---+---+---+---+---+
+/// ```
+///
+/// With:
+/// - EID: the extension header ID
+/// - NH: Next Header
+///
+/// [RFC 6282 § 4.2]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.2
+pub enum NhcPacket {
+ ExtHeader,
+ UdpHeader,
+}
+
+impl NhcPacket {
+ /// Returns the type of the Next Header header.
+ /// This can either be an Extension header or an 6LoWPAN Udp header.
+ ///
+ /// # Errors
+ /// Returns `[Error::Unrecognized]` when neither the Extension Header dispatch or the Udp
+ /// dispatch is recognized.
+ pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result<Self> {
+ let raw = buffer.as_ref();
+ if raw.is_empty() {
+ return Err(Error);
+ }
+
+ if raw[0] >> 4 == DISPATCH_EXT_HEADER {
+ // We have a compressed IPv6 Extension Header.
+ Ok(Self::ExtHeader)
+ } else if raw[0] >> 3 == DISPATCH_UDP_HEADER {
+ // We have a compressed UDP header.
+ Ok(Self::UdpHeader)
+ } else {
+ Err(Error)
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum ExtHeaderId {
+ HopByHopHeader,
+ RoutingHeader,
+ FragmentHeader,
+ DestinationOptionsHeader,
+ MobilityHeader,
+ Header,
+ Reserved,
+}
+
+impl From<ExtHeaderId> for IpProtocol {
+ fn from(val: ExtHeaderId) -> Self {
+ match val {
+ ExtHeaderId::HopByHopHeader => Self::HopByHop,
+ ExtHeaderId::RoutingHeader => Self::Ipv6Route,
+ ExtHeaderId::FragmentHeader => Self::Ipv6Frag,
+ ExtHeaderId::DestinationOptionsHeader => Self::Ipv6Opts,
+ ExtHeaderId::MobilityHeader => Self::Unknown(0),
+ ExtHeaderId::Header => Self::Unknown(0),
+ ExtHeaderId::Reserved => Self::Unknown(0),
+ }
+ }
+}
+
+/// A read/write wrapper around a 6LoWPAN NHC Extension header.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ExtHeaderPacket<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> ExtHeaderPacket<T> {
+ /// Input a raw octet buffer with a 6LoWPAN NHC Extension Header structure.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ ExtHeaderPacket { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+
+ if packet.eid_field() > 7 {
+ return Err(Error);
+ }
+
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let buffer = self.buffer.as_ref();
+
+ if buffer.is_empty() {
+ return Err(Error);
+ }
+
+ let mut len = 2;
+ len += self.next_header_size();
+
+ if len <= buffer.len() {
+ Ok(())
+ } else {
+ Err(Error)
+ }
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ get_field!(dispatch_field, 0b1111, 4);
+ get_field!(eid_field, 0b111, 1);
+ get_field!(nh_field, 0b1, 0);
+
+ /// Return the Extension Header ID.
+ pub fn extension_header_id(&self) -> ExtHeaderId {
+ match self.eid_field() {
+ 0 => ExtHeaderId::HopByHopHeader,
+ 1 => ExtHeaderId::RoutingHeader,
+ 2 => ExtHeaderId::FragmentHeader,
+ 3 => ExtHeaderId::DestinationOptionsHeader,
+ 4 => ExtHeaderId::MobilityHeader,
+ 5 | 6 => ExtHeaderId::Reserved,
+ 7 => ExtHeaderId::Header,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the length field.
+ pub fn length(&self) -> u8 {
+ self.buffer.as_ref()[1 + self.next_header_size()]
+ }
+
+ /// Parse the next header field.
+ pub fn next_header(&self) -> NextHeader {
+ if self.nh_field() == 1 {
+ NextHeader::Compressed
+ } else {
+ // The full 8 bits for Next Header are carried in-line.
+ NextHeader::Uncompressed(IpProtocol::from(self.buffer.as_ref()[1]))
+ }
+ }
+
+ /// Return the size of the Next Header field.
+ fn next_header_size(&self) -> usize {
+ // If nh is set, then the Next Header is compressed using LOWPAN_NHC
+ match self.nh_field() {
+ 0 => 1,
+ 1 => 0,
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> ExtHeaderPacket<&'a T> {
+ /// Return a pointer to the payload.
+ pub fn payload(&self) -> &'a [u8] {
+ let start = 2 + self.next_header_size();
+ let len = self.length() as usize;
+ &self.buffer.as_ref()[start..][..len]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> ExtHeaderPacket<T> {
+ /// Return a mutable pointer to the payload.
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let start = 2 + self.next_header_size();
+ let len = self.length() as usize;
+ &mut self.buffer.as_mut()[start..][..len]
+ }
+
+ /// Set the dispatch field to `0b1110`.
+ fn set_dispatch_field(&mut self) {
+ let data = self.buffer.as_mut();
+ data[0] = (data[0] & !(0b1111 << 4)) | (DISPATCH_EXT_HEADER << 4);
+ }
+
+ set_field!(set_eid_field, 0b111, 1);
+ set_field!(set_nh_field, 0b1, 0);
+
+ /// Set the Extension Header ID field.
+ fn set_extension_header_id(&mut self, ext_header_id: ExtHeaderId) {
+ let id = match ext_header_id {
+ ExtHeaderId::HopByHopHeader => 0,
+ ExtHeaderId::RoutingHeader => 1,
+ ExtHeaderId::FragmentHeader => 2,
+ ExtHeaderId::DestinationOptionsHeader => 3,
+ ExtHeaderId::MobilityHeader => 4,
+ ExtHeaderId::Reserved => 5,
+ ExtHeaderId::Header => 7,
+ };
+
+ self.set_eid_field(id);
+ }
+
+ /// Set the Next Header.
+ fn set_next_header(&mut self, next_header: NextHeader) {
+ match next_header {
+ NextHeader::Compressed => self.set_nh_field(0b1),
+ NextHeader::Uncompressed(nh) => {
+ self.set_nh_field(0b0);
+
+ let start = 1;
+ let data = self.buffer.as_mut();
+ data[start] = nh.into();
+ }
+ }
+ }
+
+ /// Set the length.
+ fn set_length(&mut self, length: u8) {
+ let start = 1 + self.next_header_size();
+
+ let data = self.buffer.as_mut();
+ data[start] = length;
+ }
+}
+
+/// A high-level representation of an 6LoWPAN NHC Extension header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ExtHeaderRepr {
+ pub ext_header_id: ExtHeaderId,
+ pub next_header: NextHeader,
+ pub length: u8,
+}
+
+impl ExtHeaderRepr {
+ /// Parse a 6LoWPAN NHC Extension Header packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &ExtHeaderPacket<&T>) -> Result<Self> {
+ // Ensure basic accessors will work.
+ packet.check_len()?;
+
+ if packet.dispatch_field() != DISPATCH_EXT_HEADER {
+ return Err(Error);
+ }
+
+ Ok(Self {
+ ext_header_id: packet.extension_header_id(),
+ next_header: packet.next_header(),
+ length: packet.length(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ pub fn buffer_len(&self) -> usize {
+ let mut len = 1; // The minimal header size
+
+ if self.next_header != NextHeader::Compressed {
+ len += 1;
+ }
+
+ len += 1; // The length
+
+ len
+ }
+
+ /// Emit a high-level representation into a 6LoWPAN NHC Extension Header packet.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut ExtHeaderPacket<T>) {
+ packet.set_dispatch_field();
+ packet.set_extension_header_id(self.ext_header_id);
+ packet.set_next_header(self.next_header);
+ packet.set_length(self.length);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::wire::{Ipv6RoutingHeader, Ipv6RoutingRepr};
+
+ #[cfg(feature = "proto-rpl")]
+ use crate::wire::{
+ Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, RplHopByHopRepr, RplInstanceId,
+ };
+
+ #[cfg(feature = "proto-rpl")]
+ const RPL_HOP_BY_HOP_PACKET: [u8; 9] = [0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, 0x1e, 0x03, 0x00];
+
+ const ROUTING_SR_PACKET: [u8; 32] = [
+ 0xe3, 0x1e, 0x03, 0x03, 0x99, 0x30, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05,
+ 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
+ 0x00, 0x00,
+ ];
+
+ #[test]
+ #[cfg(feature = "proto-rpl")]
+ fn test_rpl_hop_by_hop_option_deconstruct() {
+ let header = ExtHeaderPacket::new_checked(&RPL_HOP_BY_HOP_PACKET).unwrap();
+ assert_eq!(
+ header.next_header(),
+ NextHeader::Uncompressed(IpProtocol::Icmpv6)
+ );
+ assert_eq!(header.extension_header_id(), ExtHeaderId::HopByHopHeader);
+
+ let options = header.payload();
+ let mut options = Ipv6OptionsIterator::new(options);
+ let rpl_repr = options.next().unwrap();
+ let rpl_repr = rpl_repr.unwrap();
+
+ match rpl_repr {
+ Ipv6OptionRepr::Rpl(rpl) => {
+ assert_eq!(
+ rpl,
+ RplHopByHopRepr {
+ down: false,
+ rank_error: false,
+ forwarding_error: false,
+ instance_id: RplInstanceId::from(0x1e),
+ sender_rank: 0x0300,
+ }
+ );
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "proto-rpl")]
+ fn test_rpl_hop_by_hop_option_emit() {
+ let repr = Ipv6OptionRepr::Rpl(RplHopByHopRepr {
+ down: false,
+ rank_error: false,
+ forwarding_error: false,
+ instance_id: RplInstanceId::from(0x1e),
+ sender_rank: 0x0300,
+ });
+
+ let ext_hdr = ExtHeaderRepr {
+ ext_header_id: ExtHeaderId::HopByHopHeader,
+ next_header: NextHeader::Uncompressed(IpProtocol::Icmpv6),
+ length: repr.buffer_len() as u8,
+ };
+
+ let mut buffer = vec![0u8; ext_hdr.buffer_len() + repr.buffer_len()];
+ ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
+ &mut buffer[..ext_hdr.buffer_len()],
+ ));
+ repr.emit(&mut Ipv6Option::new_unchecked(
+ &mut buffer[ext_hdr.buffer_len()..],
+ ));
+
+ assert_eq!(&buffer[..], RPL_HOP_BY_HOP_PACKET);
+ }
+
+ #[test]
+ fn test_source_routing_deconstruct() {
+ let header = ExtHeaderPacket::new_checked(&ROUTING_SR_PACKET).unwrap();
+ assert_eq!(header.next_header(), NextHeader::Compressed);
+ assert_eq!(header.extension_header_id(), ExtHeaderId::RoutingHeader);
+
+ let routing_hdr = Ipv6RoutingHeader::new_checked(header.payload()).unwrap();
+ let repr = Ipv6RoutingRepr::parse(&routing_hdr).unwrap();
+ assert_eq!(
+ repr,
+ Ipv6RoutingRepr::Rpl {
+ segments_left: 3,
+ cmpr_i: 9,
+ cmpr_e: 9,
+ pad: 3,
+ addresses: &[
+ 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,
+ 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00
+ ],
+ }
+ );
+ }
+
+ #[test]
+ fn test_source_routing_emit() {
+ let routing_hdr = Ipv6RoutingRepr::Rpl {
+ segments_left: 3,
+ cmpr_i: 9,
+ cmpr_e: 9,
+ pad: 3,
+ addresses: &[
+ 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06,
+ 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+ ],
+ };
+
+ let ext_hdr = ExtHeaderRepr {
+ ext_header_id: ExtHeaderId::RoutingHeader,
+ next_header: NextHeader::Compressed,
+ length: routing_hdr.buffer_len() as u8,
+ };
+
+ let mut buffer = vec![0u8; ext_hdr.buffer_len() + routing_hdr.buffer_len()];
+ ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
+ &mut buffer[..ext_hdr.buffer_len()],
+ ));
+ routing_hdr.emit(&mut Ipv6RoutingHeader::new_unchecked(
+ &mut buffer[ext_hdr.buffer_len()..],
+ ));
+
+ assert_eq!(&buffer[..], ROUTING_SR_PACKET);
+ }
+}
+
+/// A read/write wrapper around a 6LoWPAN_NHC UDP frame.
+/// [RFC 6282 § 4.3] specifies the format of the header.
+///
+/// The base header has the following format:
+/// ```txt
+/// 0 1 2 3 4 5 6 7
+/// +---+---+---+---+---+---+---+---+
+/// | 1 | 1 | 1 | 1 | 0 | C | P |
+/// +---+---+---+---+---+---+---+---+
+/// With:
+/// - C: checksum, specifies if the checksum is elided.
+/// - P: ports, specifies if the ports are elided.
+/// ```
+///
+/// [RFC 6282 § 4.3]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.3
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct UdpNhcPacket<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+impl<T: AsRef<[u8]>> UdpNhcPacket<T> {
+ /// Input a raw octet buffer with a LOWPAN_NHC frame structure for UDP.
+ pub const fn new_unchecked(buffer: T) -> Self {
+ Self { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Self> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error::Truncated)` if the buffer is too short.
+ pub fn check_len(&self) -> Result<()> {
+ let buffer = self.buffer.as_ref();
+
+ if buffer.is_empty() {
+ return Err(Error);
+ }
+
+ let index = 1 + self.ports_size() + self.checksum_size();
+ if index > buffer.len() {
+ return Err(Error);
+ }
+
+ Ok(())
+ }
+
+ /// Consumes the frame, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ get_field!(dispatch_field, 0b11111, 3);
+ get_field!(checksum_field, 0b1, 2);
+ get_field!(ports_field, 0b11, 0);
+
+ /// Returns the index of the start of the next header compressed fields.
+ const fn nhc_fields_start(&self) -> usize {
+ 1
+ }
+
+ /// Return the source port number.
+ pub fn src_port(&self) -> u16 {
+ match self.ports_field() {
+ 0b00 | 0b01 => {
+ // The full 16 bits are carried in-line.
+ let data = self.buffer.as_ref();
+ let start = self.nhc_fields_start();
+
+ NetworkEndian::read_u16(&data[start..start + 2])
+ }
+ 0b10 => {
+ // The first 8 bits are elided.
+ let data = self.buffer.as_ref();
+ let start = self.nhc_fields_start();
+
+ 0xf000 + data[start] as u16
+ }
+ 0b11 => {
+ // The first 12 bits are elided.
+ let data = self.buffer.as_ref();
+ let start = self.nhc_fields_start();
+
+ 0xf0b0 + (data[start] >> 4) as u16
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the destination port number.
+ pub fn dst_port(&self) -> u16 {
+ match self.ports_field() {
+ 0b00 => {
+ // The full 16 bits are carried in-line.
+ let data = self.buffer.as_ref();
+ let idx = self.nhc_fields_start();
+
+ NetworkEndian::read_u16(&data[idx + 2..idx + 4])
+ }
+ 0b01 => {
+ // The first 8 bits are elided.
+ let data = self.buffer.as_ref();
+ let idx = self.nhc_fields_start();
+
+ 0xf000 + data[idx] as u16
+ }
+ 0b10 => {
+ // The full 16 bits are carried in-line.
+ let data = self.buffer.as_ref();
+ let idx = self.nhc_fields_start();
+
+ NetworkEndian::read_u16(&data[idx + 1..idx + 1 + 2])
+ }
+ 0b11 => {
+ // The first 12 bits are elided.
+ let data = self.buffer.as_ref();
+ let start = self.nhc_fields_start();
+
+ 0xf0b0 + (data[start] & 0xff) as u16
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ /// Return the checksum.
+ pub fn checksum(&self) -> Option<u16> {
+ if self.checksum_field() == 0b0 {
+ // The first 12 bits are elided.
+ let data = self.buffer.as_ref();
+ let start = self.nhc_fields_start() + self.ports_size();
+ Some(NetworkEndian::read_u16(&data[start..start + 2]))
+ } else {
+ // The checksum is elided and needs to be recomputed on the 6LoWPAN termination point.
+ None
+ }
+ }
+
+ // Return the size of the checksum field.
+ pub(crate) fn checksum_size(&self) -> usize {
+ match self.checksum_field() {
+ 0b0 => 2,
+ 0b1 => 0,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Returns the total size of both port numbers.
+ pub(crate) fn ports_size(&self) -> usize {
+ match self.ports_field() {
+ 0b00 => 4, // 16 bits + 16 bits
+ 0b01 => 3, // 16 bits + 8 bits
+ 0b10 => 3, // 8 bits + 16 bits
+ 0b11 => 1, // 4 bits + 4 bits
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> UdpNhcPacket<&'a T> {
+ /// Return a pointer to the payload.
+ pub fn payload(&self) -> &'a [u8] {
+ let start = 1 + self.ports_size() + self.checksum_size();
+ &self.buffer.as_ref()[start..]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> UdpNhcPacket<T> {
+ /// Return a mutable pointer to the payload.
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let start = 1 + self.ports_size() + 2; // XXX(thvdveld): we assume we put the checksum inlined.
+ &mut self.buffer.as_mut()[start..]
+ }
+
+ /// Set the dispatch field to `0b11110`.
+ fn set_dispatch_field(&mut self) {
+ let data = self.buffer.as_mut();
+ data[0] = (data[0] & !(0b11111 << 3)) | (DISPATCH_UDP_HEADER << 3);
+ }
+
+ set_field!(set_checksum_field, 0b1, 2);
+ set_field!(set_ports_field, 0b11, 0);
+
+ fn set_ports(&mut self, src_port: u16, dst_port: u16) {
+ let mut idx = 1;
+
+ match (src_port, dst_port) {
+ (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => {
+ // We can compress both the source and destination ports.
+ self.set_ports_field(0b11);
+ let data = self.buffer.as_mut();
+ data[idx] = (((src_port - 0xf0b0) as u8) << 4) & ((dst_port - 0xf0b0) as u8);
+ }
+ (0xf000..=0xf0ff, _) => {
+ // We can compress the source port, but not the destination port.
+ self.set_ports_field(0b10);
+ let data = self.buffer.as_mut();
+ data[idx] = (src_port - 0xf000) as u8;
+ idx += 1;
+
+ NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
+ }
+ (_, 0xf000..=0xf0ff) => {
+ // We can compress the destination port, but not the source port.
+ self.set_ports_field(0b01);
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
+ idx += 2;
+ data[idx] = (dst_port - 0xf000) as u8;
+ }
+ (_, _) => {
+ // We cannot compress any port.
+ self.set_ports_field(0b00);
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
+ idx += 2;
+ NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
+ }
+ };
+ }
+
+ fn set_checksum(&mut self, checksum: u16) {
+ self.set_checksum_field(0b0);
+ let idx = 1 + self.ports_size();
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[idx..idx + 2], checksum);
+ }
+}
+
+/// A high-level representation of a 6LoWPAN NHC UDP header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct UdpNhcRepr(pub UdpRepr);
+
+impl<'a> UdpNhcRepr {
+ /// Parse a 6LoWPAN NHC UDP packet and return a high-level representation.
+ pub fn parse<T: AsRef<[u8]> + ?Sized>(
+ packet: &UdpNhcPacket<&'a T>,
+ src_addr: &ipv6::Address,
+ dst_addr: &ipv6::Address,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Self> {
+ packet.check_len()?;
+
+ if packet.dispatch_field() != DISPATCH_UDP_HEADER {
+ return Err(Error);
+ }
+
+ if checksum_caps.udp.rx() {
+ let payload_len = packet.payload().len();
+ let chk_sum = !checksum::combine(&[
+ checksum::pseudo_header(
+ &IpAddress::Ipv6(*src_addr),
+ &IpAddress::Ipv6(*dst_addr),
+ crate::wire::ip::Protocol::Udp,
+ payload_len as u32 + 8,
+ ),
+ packet.src_port(),
+ packet.dst_port(),
+ payload_len as u16 + 8,
+ checksum::data(packet.payload()),
+ ]);
+
+ if let Some(checksum) = packet.checksum() {
+ if chk_sum != checksum {
+ return Err(Error);
+ }
+ }
+ }
+
+ Ok(Self(UdpRepr {
+ src_port: packet.src_port(),
+ dst_port: packet.dst_port(),
+ }))
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub fn header_len(&self) -> usize {
+ let mut len = 1; // The minimal header size
+
+ len += 2; // XXX We assume we will add the checksum at the end
+
+ // Check if we can compress the source and destination ports
+ match (self.src_port, self.dst_port) {
+ (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => len + 1,
+ (0xf000..=0xf0ff, _) | (_, 0xf000..=0xf0ff) => len + 3,
+ (_, _) => len + 4,
+ }
+ }
+
+ /// Emit a high-level representation into a LOWPAN_NHC UDP header.
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
+ &self,
+ packet: &mut UdpNhcPacket<T>,
+ src_addr: &Address,
+ dst_addr: &Address,
+ payload_len: usize,
+ emit_payload: impl FnOnce(&mut [u8]),
+ checksum_caps: &ChecksumCapabilities,
+ ) {
+ packet.set_dispatch_field();
+ packet.set_ports(self.src_port, self.dst_port);
+ emit_payload(packet.payload_mut());
+
+ if checksum_caps.udp.tx() {
+ let chk_sum = !checksum::combine(&[
+ checksum::pseudo_header(
+ &IpAddress::Ipv6(*src_addr),
+ &IpAddress::Ipv6(*dst_addr),
+ crate::wire::ip::Protocol::Udp,
+ payload_len as u32 + 8,
+ ),
+ self.src_port,
+ self.dst_port,
+ payload_len as u16 + 8,
+ checksum::data(packet.payload_mut()),
+ ]);
+
+ packet.set_checksum(chk_sum);
+ }
+ }
+}
+
+impl core::ops::Deref for UdpNhcRepr {
+ type Target = UdpRepr;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl core::ops::DerefMut for UdpNhcRepr {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn ext_header_nh_inlined() {
+ let bytes = [0xe2, 0x3a, 0x6, 0x3, 0x0, 0xff, 0x0, 0x0, 0x0];
+
+ let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap();
+ assert_eq!(packet.next_header_size(), 1);
+ assert_eq!(packet.length(), 6);
+ assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
+ assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
+ assert_eq!(
+ packet.next_header(),
+ NextHeader::Uncompressed(IpProtocol::Icmpv6)
+ );
+
+ assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
+ }
+
+ #[test]
+ fn ext_header_nh_elided() {
+ let bytes = [0xe3, 0x06, 0x03, 0x00, 0xff, 0x00, 0x00, 0x00];
+
+ let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap();
+ assert_eq!(packet.next_header_size(), 0);
+ assert_eq!(packet.length(), 6);
+ assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
+ assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
+ assert_eq!(packet.next_header(), NextHeader::Compressed);
+
+ assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
+ }
+
+ #[test]
+ fn ext_header_emit() {
+ let ext_header = ExtHeaderRepr {
+ ext_header_id: ExtHeaderId::RoutingHeader,
+ next_header: NextHeader::Compressed,
+ length: 6,
+ };
+
+ let len = ext_header.buffer_len();
+ let mut buffer = [0u8; 127];
+ let mut packet = ExtHeaderPacket::new_unchecked(&mut buffer[..len]);
+ ext_header.emit(&mut packet);
+
+ assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
+ assert_eq!(packet.next_header(), NextHeader::Compressed);
+ assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
+ }
+
+ #[test]
+ fn udp_nhc_fields() {
+ let bytes = [0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4];
+
+ let packet = UdpNhcPacket::new_checked(&bytes[..]).unwrap();
+ assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
+ assert_eq!(packet.checksum(), Some(0x28c4));
+ assert_eq!(packet.src_port(), 5678);
+ assert_eq!(packet.dst_port(), 8765);
+ }
+
+ #[test]
+ fn udp_emit() {
+ let udp = UdpNhcRepr(UdpRepr {
+ src_port: 0xf0b1,
+ dst_port: 0xf001,
+ });
+
+ let payload = b"Hello World!";
+
+ let src_addr = ipv6::Address::default();
+ let dst_addr = ipv6::Address::default();
+
+ let len = udp.header_len() + payload.len();
+ let mut buffer = [0u8; 127];
+ let mut packet = UdpNhcPacket::new_unchecked(&mut buffer[..len]);
+ udp.emit(
+ &mut packet,
+ &src_addr,
+ &dst_addr,
+ payload.len(),
+ |buf| buf.copy_from_slice(&payload[..]),
+ &ChecksumCapabilities::default(),
+ );
+
+ assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
+ assert_eq!(packet.src_port(), 0xf0b1);
+ assert_eq!(packet.dst_port(), 0xf001);
+ assert_eq!(packet.payload_mut(), b"Hello World!");
+ }
+}
diff --git a/src/wire/tcp.rs b/src/wire/tcp.rs
new file mode 100644
index 0000000..2482143
--- /dev/null
+++ b/src/wire/tcp.rs
@@ -0,0 +1,1331 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::{cmp, fmt, i32, ops};
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+use crate::wire::ip::checksum;
+use crate::wire::{IpAddress, IpProtocol};
+
+/// A TCP sequence number.
+///
+/// A sequence number is a monotonically advancing integer modulo 2<sup>32</sup>.
+/// Sequence numbers do not have a discontiguity when compared pairwise across a signed overflow.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
+pub struct SeqNumber(pub i32);
+
+impl SeqNumber {
+ pub fn max(self, rhs: Self) -> Self {
+ if self > rhs {
+ self
+ } else {
+ rhs
+ }
+ }
+
+ pub fn min(self, rhs: Self) -> Self {
+ if self < rhs {
+ self
+ } else {
+ rhs
+ }
+ }
+}
+
+impl fmt::Display for SeqNumber {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0 as u32)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for SeqNumber {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(fmt, "{}", self.0 as u32);
+ }
+}
+
+impl ops::Add<usize> for SeqNumber {
+ type Output = SeqNumber;
+
+ fn add(self, rhs: usize) -> SeqNumber {
+ if rhs > i32::MAX as usize {
+ panic!("attempt to add to sequence number with unsigned overflow")
+ }
+ SeqNumber(self.0.wrapping_add(rhs as i32))
+ }
+}
+
+impl ops::Sub<usize> for SeqNumber {
+ type Output = SeqNumber;
+
+ fn sub(self, rhs: usize) -> SeqNumber {
+ if rhs > i32::MAX as usize {
+ panic!("attempt to subtract to sequence number with unsigned overflow")
+ }
+ SeqNumber(self.0.wrapping_sub(rhs as i32))
+ }
+}
+
+impl ops::AddAssign<usize> for SeqNumber {
+ fn add_assign(&mut self, rhs: usize) {
+ *self = *self + rhs;
+ }
+}
+
+impl ops::Sub for SeqNumber {
+ type Output = usize;
+
+ fn sub(self, rhs: SeqNumber) -> usize {
+ let result = self.0.wrapping_sub(rhs.0);
+ if result < 0 {
+ panic!("attempt to subtract sequence numbers with underflow")
+ }
+ result as usize
+ }
+}
+
+impl cmp::PartialOrd for SeqNumber {
+ fn partial_cmp(&self, other: &SeqNumber) -> Option<cmp::Ordering> {
+ self.0.wrapping_sub(other.0).partial_cmp(&0)
+ }
+}
+
+/// A read/write wrapper around a Transmission Control Protocol packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ pub const SRC_PORT: Field = 0..2;
+ pub const DST_PORT: Field = 2..4;
+ pub const SEQ_NUM: Field = 4..8;
+ pub const ACK_NUM: Field = 8..12;
+ pub const FLAGS: Field = 12..14;
+ pub const WIN_SIZE: Field = 14..16;
+ pub const CHECKSUM: Field = 16..18;
+ pub const URGENT: Field = 18..20;
+
+ pub const fn OPTIONS(length: u8) -> Field {
+ URGENT.end..(length as usize)
+ }
+
+ pub const FLG_FIN: u16 = 0x001;
+ pub const FLG_SYN: u16 = 0x002;
+ pub const FLG_RST: u16 = 0x004;
+ pub const FLG_PSH: u16 = 0x008;
+ pub const FLG_ACK: u16 = 0x010;
+ pub const FLG_URG: u16 = 0x020;
+ pub const FLG_ECE: u16 = 0x040;
+ pub const FLG_CWR: u16 = 0x080;
+ pub const FLG_NS: u16 = 0x100;
+
+ pub const OPT_END: u8 = 0x00;
+ pub const OPT_NOP: u8 = 0x01;
+ pub const OPT_MSS: u8 = 0x02;
+ pub const OPT_WS: u8 = 0x03;
+ pub const OPT_SACKPERM: u8 = 0x04;
+ pub const OPT_SACKRNG: u8 = 0x05;
+}
+
+pub const HEADER_LEN: usize = field::URGENT.end;
+
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with TCP packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ /// Returns `Err(Error)` if the header length field has a value smaller
+ /// than the minimal header length.
+ ///
+ /// The result of this check is invalidated by calling [set_header_len].
+ ///
+ /// [set_header_len]: #method.set_header_len
+ pub fn check_len(&self) -> Result<()> {
+ let len = self.buffer.as_ref().len();
+ if len < field::URGENT.end {
+ Err(Error)
+ } else {
+ let header_len = self.header_len() as usize;
+ if len < header_len || header_len < field::URGENT.end {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the source port field.
+ #[inline]
+ pub fn src_port(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::SRC_PORT])
+ }
+
+ /// Return the destination port field.
+ #[inline]
+ pub fn dst_port(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::DST_PORT])
+ }
+
+ /// Return the sequence number field.
+ #[inline]
+ pub fn seq_number(&self) -> SeqNumber {
+ let data = self.buffer.as_ref();
+ SeqNumber(NetworkEndian::read_i32(&data[field::SEQ_NUM]))
+ }
+
+ /// Return the acknowledgement number field.
+ #[inline]
+ pub fn ack_number(&self) -> SeqNumber {
+ let data = self.buffer.as_ref();
+ SeqNumber(NetworkEndian::read_i32(&data[field::ACK_NUM]))
+ }
+
+ /// Return the FIN flag.
+ #[inline]
+ pub fn fin(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_FIN != 0
+ }
+
+ /// Return the SYN flag.
+ #[inline]
+ pub fn syn(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_SYN != 0
+ }
+
+ /// Return the RST flag.
+ #[inline]
+ pub fn rst(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_RST != 0
+ }
+
+ /// Return the PSH flag.
+ #[inline]
+ pub fn psh(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_PSH != 0
+ }
+
+ /// Return the ACK flag.
+ #[inline]
+ pub fn ack(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_ACK != 0
+ }
+
+ /// Return the URG flag.
+ #[inline]
+ pub fn urg(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_URG != 0
+ }
+
+ /// Return the ECE flag.
+ #[inline]
+ pub fn ece(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_ECE != 0
+ }
+
+ /// Return the CWR flag.
+ #[inline]
+ pub fn cwr(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_CWR != 0
+ }
+
+ /// Return the NS flag.
+ #[inline]
+ pub fn ns(&self) -> bool {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ raw & field::FLG_NS != 0
+ }
+
+ /// Return the header length, in octets.
+ #[inline]
+ pub fn header_len(&self) -> u8 {
+ let data = self.buffer.as_ref();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ ((raw >> 12) * 4) as u8
+ }
+
+ /// Return the window size field.
+ #[inline]
+ pub fn window_len(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::WIN_SIZE])
+ }
+
+ /// Return the checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Return the urgent pointer field.
+ #[inline]
+ pub fn urgent_at(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::URGENT])
+ }
+
+ /// Return the length of the segment, in terms of sequence space.
+ pub fn segment_len(&self) -> usize {
+ let data = self.buffer.as_ref();
+ let mut length = data.len() - self.header_len() as usize;
+ if self.syn() {
+ length += 1
+ }
+ if self.fin() {
+ length += 1
+ }
+ length
+ }
+
+ /// Returns whether the selective acknowledgement SYN flag is set or not.
+ pub fn selective_ack_permitted(&self) -> Result<bool> {
+ let data = self.buffer.as_ref();
+ let mut options = &data[field::OPTIONS(self.header_len())];
+ while !options.is_empty() {
+ let (next_options, option) = TcpOption::parse(options)?;
+ if option == TcpOption::SackPermitted {
+ return Ok(true);
+ }
+ options = next_options;
+ }
+ Ok(false)
+ }
+
+ /// Return the selective acknowledgement ranges, if any. If there are none in the packet, an
+ /// array of ``None`` values will be returned.
+ ///
+ pub fn selective_ack_ranges(&self) -> Result<[Option<(u32, u32)>; 3]> {
+ let data = self.buffer.as_ref();
+ let mut options = &data[field::OPTIONS(self.header_len())];
+ while !options.is_empty() {
+ let (next_options, option) = TcpOption::parse(options)?;
+ if let TcpOption::SackRange(slice) = option {
+ return Ok(slice);
+ }
+ options = next_options;
+ }
+ Ok([None, None, None])
+ }
+
+ /// Validate the packet checksum.
+ ///
+ /// # Panics
+ /// This function panics unless `src_addr` and `dst_addr` belong to the same family,
+ /// and that family is IPv4 or IPv6.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32),
+ checksum::data(data),
+ ]) == !0
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the options.
+ #[inline]
+ pub fn options(&self) -> &'a [u8] {
+ let header_len = self.header_len();
+ let data = self.buffer.as_ref();
+ &data[field::OPTIONS(header_len)]
+ }
+
+ /// Return a pointer to the payload.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let header_len = self.header_len() as usize;
+ let data = self.buffer.as_ref();
+ &data[header_len..]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the source port field.
+ #[inline]
+ pub fn set_src_port(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::SRC_PORT], value)
+ }
+
+ /// Set the destination port field.
+ #[inline]
+ pub fn set_dst_port(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::DST_PORT], value)
+ }
+
+ /// Set the sequence number field.
+ #[inline]
+ pub fn set_seq_number(&mut self, value: SeqNumber) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_i32(&mut data[field::SEQ_NUM], value.0)
+ }
+
+ /// Set the acknowledgement number field.
+ #[inline]
+ pub fn set_ack_number(&mut self, value: SeqNumber) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_i32(&mut data[field::ACK_NUM], value.0)
+ }
+
+ /// Clear the entire flags field.
+ #[inline]
+ pub fn clear_flags(&mut self) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = raw & !0x0fff;
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the FIN flag.
+ #[inline]
+ pub fn set_fin(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_FIN
+ } else {
+ raw & !field::FLG_FIN
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the SYN flag.
+ #[inline]
+ pub fn set_syn(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_SYN
+ } else {
+ raw & !field::FLG_SYN
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the RST flag.
+ #[inline]
+ pub fn set_rst(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_RST
+ } else {
+ raw & !field::FLG_RST
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the PSH flag.
+ #[inline]
+ pub fn set_psh(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_PSH
+ } else {
+ raw & !field::FLG_PSH
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the ACK flag.
+ #[inline]
+ pub fn set_ack(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_ACK
+ } else {
+ raw & !field::FLG_ACK
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the URG flag.
+ #[inline]
+ pub fn set_urg(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_URG
+ } else {
+ raw & !field::FLG_URG
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the ECE flag.
+ #[inline]
+ pub fn set_ece(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_ECE
+ } else {
+ raw & !field::FLG_ECE
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the CWR flag.
+ #[inline]
+ pub fn set_cwr(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_CWR
+ } else {
+ raw & !field::FLG_CWR
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the NS flag.
+ #[inline]
+ pub fn set_ns(&mut self, value: bool) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = if value {
+ raw | field::FLG_NS
+ } else {
+ raw & !field::FLG_NS
+ };
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the header length, in octets.
+ #[inline]
+ pub fn set_header_len(&mut self, value: u8) {
+ let data = self.buffer.as_mut();
+ let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
+ let raw = (raw & !0xf000) | ((value as u16) / 4) << 12;
+ NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
+ }
+
+ /// Set the window size field.
+ #[inline]
+ pub fn set_window_len(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::WIN_SIZE], value)
+ }
+
+ /// Set the checksum field.
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Set the urgent pointer field.
+ #[inline]
+ pub fn set_urgent_at(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::URGENT], value)
+ }
+
+ /// Compute and fill in the header checksum.
+ ///
+ /// # Panics
+ /// This function panics unless `src_addr` and `dst_addr` belong to the same family,
+ /// and that family is IPv4 or IPv6.
+ pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32),
+ checksum::data(data),
+ ])
+ };
+ self.set_checksum(checksum)
+ }
+
+ /// Return a pointer to the options.
+ #[inline]
+ pub fn options_mut(&mut self) -> &mut [u8] {
+ let header_len = self.header_len();
+ let data = self.buffer.as_mut();
+ &mut data[field::OPTIONS(header_len)]
+ }
+
+ /// Return a mutable pointer to the payload data.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let header_len = self.header_len() as usize;
+ let data = self.buffer.as_mut();
+ &mut data[header_len..]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A representation of a single TCP option.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum TcpOption<'a> {
+ EndOfList,
+ NoOperation,
+ MaxSegmentSize(u16),
+ WindowScale(u8),
+ SackPermitted,
+ SackRange([Option<(u32, u32)>; 3]),
+ Unknown { kind: u8, data: &'a [u8] },
+}
+
+impl<'a> TcpOption<'a> {
+ pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], TcpOption<'a>)> {
+ let (length, option);
+ match *buffer.first().ok_or(Error)? {
+ field::OPT_END => {
+ length = 1;
+ option = TcpOption::EndOfList;
+ }
+ field::OPT_NOP => {
+ length = 1;
+ option = TcpOption::NoOperation;
+ }
+ kind => {
+ length = *buffer.get(1).ok_or(Error)? as usize;
+ let data = buffer.get(2..length).ok_or(Error)?;
+ match (kind, length) {
+ (field::OPT_END, _) | (field::OPT_NOP, _) => unreachable!(),
+ (field::OPT_MSS, 4) => {
+ option = TcpOption::MaxSegmentSize(NetworkEndian::read_u16(data))
+ }
+ (field::OPT_MSS, _) => return Err(Error),
+ (field::OPT_WS, 3) => option = TcpOption::WindowScale(data[0]),
+ (field::OPT_WS, _) => return Err(Error),
+ (field::OPT_SACKPERM, 2) => option = TcpOption::SackPermitted,
+ (field::OPT_SACKPERM, _) => return Err(Error),
+ (field::OPT_SACKRNG, n) => {
+ if n < 10 || (n - 2) % 8 != 0 {
+ return Err(Error);
+ }
+ if n > 26 {
+ // It's possible for a remote to send 4 SACK blocks, but extremely rare.
+ // Better to "lose" that 4th block and save the extra RAM and CPU
+ // cycles in the vastly more common case.
+ //
+ // RFC 2018: SACK option that specifies n blocks will have a length of
+ // 8*n+2 bytes, so the 40 bytes available for TCP options can specify a
+ // maximum of 4 blocks. It is expected that SACK will often be used in
+ // conjunction with the Timestamp option used for RTTM [...] thus a
+ // maximum of 3 SACK blocks will be allowed in this case.
+ net_debug!("sACK with >3 blocks, truncating to 3");
+ }
+ let mut sack_ranges: [Option<(u32, u32)>; 3] = [None; 3];
+
+ // RFC 2018: Each contiguous block of data queued at the data receiver is
+ // defined in the SACK option by two 32-bit unsigned integers in network
+ // byte order[...]
+ sack_ranges.iter_mut().enumerate().for_each(|(i, nmut)| {
+ let left = i * 8;
+ *nmut = if left < data.len() {
+ let mid = left + 4;
+ let right = mid + 4;
+ let range_left = NetworkEndian::read_u32(&data[left..mid]);
+ let range_right = NetworkEndian::read_u32(&data[mid..right]);
+ Some((range_left, range_right))
+ } else {
+ None
+ };
+ });
+ option = TcpOption::SackRange(sack_ranges);
+ }
+ (_, _) => option = TcpOption::Unknown { kind, data },
+ }
+ }
+ }
+ Ok((&buffer[length..], option))
+ }
+
+ pub fn buffer_len(&self) -> usize {
+ match *self {
+ TcpOption::EndOfList => 1,
+ TcpOption::NoOperation => 1,
+ TcpOption::MaxSegmentSize(_) => 4,
+ TcpOption::WindowScale(_) => 3,
+ TcpOption::SackPermitted => 2,
+ TcpOption::SackRange(s) => s.iter().filter(|s| s.is_some()).count() * 8 + 2,
+ TcpOption::Unknown { data, .. } => 2 + data.len(),
+ }
+ }
+
+ pub fn emit<'b>(&self, buffer: &'b mut [u8]) -> &'b mut [u8] {
+ let length;
+ match *self {
+ TcpOption::EndOfList => {
+ length = 1;
+ // There may be padding space which also should be initialized.
+ for p in buffer.iter_mut() {
+ *p = field::OPT_END;
+ }
+ }
+ TcpOption::NoOperation => {
+ length = 1;
+ buffer[0] = field::OPT_NOP;
+ }
+ _ => {
+ length = self.buffer_len();
+ buffer[1] = length as u8;
+ match self {
+ &TcpOption::EndOfList | &TcpOption::NoOperation => unreachable!(),
+ &TcpOption::MaxSegmentSize(value) => {
+ buffer[0] = field::OPT_MSS;
+ NetworkEndian::write_u16(&mut buffer[2..], value)
+ }
+ &TcpOption::WindowScale(value) => {
+ buffer[0] = field::OPT_WS;
+ buffer[2] = value;
+ }
+ &TcpOption::SackPermitted => {
+ buffer[0] = field::OPT_SACKPERM;
+ }
+ &TcpOption::SackRange(slice) => {
+ buffer[0] = field::OPT_SACKRNG;
+ slice
+ .iter()
+ .filter(|s| s.is_some())
+ .enumerate()
+ .for_each(|(i, s)| {
+ let (first, second) = *s.as_ref().unwrap();
+ let pos = i * 8 + 2;
+ NetworkEndian::write_u32(&mut buffer[pos..], first);
+ NetworkEndian::write_u32(&mut buffer[pos + 4..], second);
+ });
+ }
+ &TcpOption::Unknown {
+ kind,
+ data: provided,
+ } => {
+ buffer[0] = kind;
+ buffer[2..].copy_from_slice(provided)
+ }
+ }
+ }
+ }
+ &mut buffer[length..]
+ }
+}
+
+/// The possible control flags of a Transmission Control Protocol packet.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Control {
+ None,
+ Psh,
+ Syn,
+ Fin,
+ Rst,
+}
+
+#[allow(clippy::len_without_is_empty)]
+impl Control {
+ /// Return the length of a control flag, in terms of sequence space.
+ pub const fn len(self) -> usize {
+ match self {
+ Control::Syn | Control::Fin => 1,
+ _ => 0,
+ }
+ }
+
+ /// Turn the PSH flag into no flag, and keep the rest as-is.
+ pub const fn quash_psh(self) -> Control {
+ match self {
+ Control::Psh => Control::None,
+ _ => self,
+ }
+ }
+}
+
+/// A high-level representation of a Transmission Control Protocol packet.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr<'a> {
+ pub src_port: u16,
+ pub dst_port: u16,
+ pub control: Control,
+ pub seq_number: SeqNumber,
+ pub ack_number: Option<SeqNumber>,
+ pub window_len: u16,
+ pub window_scale: Option<u8>,
+ pub max_seg_size: Option<u16>,
+ pub sack_permitted: bool,
+ pub sack_ranges: [Option<(u32, u32)>; 3],
+ pub payload: &'a [u8],
+}
+
+impl<'a> Repr<'a> {
+ /// Parse a Transmission Control Protocol packet and return a high-level representation.
+ pub fn parse<T>(
+ packet: &Packet<&'a T>,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Repr<'a>>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ // Source and destination ports must be present.
+ if packet.src_port() == 0 {
+ return Err(Error);
+ }
+ if packet.dst_port() == 0 {
+ return Err(Error);
+ }
+ // Valid checksum is expected.
+ if checksum_caps.tcp.rx() && !packet.verify_checksum(src_addr, dst_addr) {
+ return Err(Error);
+ }
+
+ let control = match (packet.syn(), packet.fin(), packet.rst(), packet.psh()) {
+ (false, false, false, false) => Control::None,
+ (false, false, false, true) => Control::Psh,
+ (true, false, false, _) => Control::Syn,
+ (false, true, false, _) => Control::Fin,
+ (false, false, true, _) => Control::Rst,
+ _ => return Err(Error),
+ };
+ let ack_number = match packet.ack() {
+ true => Some(packet.ack_number()),
+ false => None,
+ };
+ // The PSH flag is ignored.
+ // The URG flag and the urgent field is ignored. This behavior is standards-compliant,
+ // however, most deployed systems (e.g. Linux) are *not* standards-compliant, and would
+ // cut the byte at the urgent pointer from the stream.
+
+ let mut max_seg_size = None;
+ let mut window_scale = None;
+ let mut options = packet.options();
+ let mut sack_permitted = false;
+ let mut sack_ranges = [None, None, None];
+ while !options.is_empty() {
+ let (next_options, option) = TcpOption::parse(options)?;
+ match option {
+ TcpOption::EndOfList => break,
+ TcpOption::NoOperation => (),
+ TcpOption::MaxSegmentSize(value) => max_seg_size = Some(value),
+ TcpOption::WindowScale(value) => {
+ // RFC 1323: Thus, the shift count must be limited to 14 (which allows windows
+ // of 2**30 = 1 Gigabyte). If a Window Scale option is received with a shift.cnt
+ // value exceeding 14, the TCP should log the error but use 14 instead of the
+ // specified value.
+ window_scale = if value > 14 {
+ net_debug!(
+ "{}:{}:{}:{}: parsed window scaling factor >14, setting to 14",
+ src_addr,
+ packet.src_port(),
+ dst_addr,
+ packet.dst_port()
+ );
+ Some(14)
+ } else {
+ Some(value)
+ };
+ }
+ TcpOption::SackPermitted => sack_permitted = true,
+ TcpOption::SackRange(slice) => sack_ranges = slice,
+ _ => (),
+ }
+ options = next_options;
+ }
+
+ Ok(Repr {
+ src_port: packet.src_port(),
+ dst_port: packet.dst_port(),
+ control: control,
+ seq_number: packet.seq_number(),
+ ack_number: ack_number,
+ window_len: packet.window_len(),
+ window_scale: window_scale,
+ max_seg_size: max_seg_size,
+ sack_permitted: sack_permitted,
+ sack_ranges: sack_ranges,
+ payload: packet.payload(),
+ })
+ }
+
+ /// Return the length of a header that will be emitted from this high-level representation.
+ ///
+ /// This should be used for buffer space calculations.
+ /// The TCP header length is a multiple of 4.
+ pub fn header_len(&self) -> usize {
+ let mut length = field::URGENT.end;
+ if self.max_seg_size.is_some() {
+ length += 4
+ }
+ if self.window_scale.is_some() {
+ length += 3
+ }
+ if self.sack_permitted {
+ length += 2;
+ }
+ let sack_range_len: usize = self
+ .sack_ranges
+ .iter()
+ .map(|o| o.map(|_| 8).unwrap_or(0))
+ .sum();
+ if sack_range_len > 0 {
+ length += sack_range_len + 2;
+ }
+ if length % 4 != 0 {
+ length += 4 - length % 4;
+ }
+ length
+ }
+
+ /// Return the length of a packet that will be emitted from this high-level representation.
+ pub fn buffer_len(&self) -> usize {
+ self.header_len() + self.payload.len()
+ }
+
+ /// Emit a high-level representation into a Transmission Control Protocol packet.
+ pub fn emit<T>(
+ &self,
+ packet: &mut Packet<&mut T>,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ checksum_caps: &ChecksumCapabilities,
+ ) where
+ T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
+ {
+ packet.set_src_port(self.src_port);
+ packet.set_dst_port(self.dst_port);
+ packet.set_seq_number(self.seq_number);
+ packet.set_ack_number(self.ack_number.unwrap_or(SeqNumber(0)));
+ packet.set_window_len(self.window_len);
+ packet.set_header_len(self.header_len() as u8);
+ packet.clear_flags();
+ match self.control {
+ Control::None => (),
+ Control::Psh => packet.set_psh(true),
+ Control::Syn => packet.set_syn(true),
+ Control::Fin => packet.set_fin(true),
+ Control::Rst => packet.set_rst(true),
+ }
+ packet.set_ack(self.ack_number.is_some());
+ {
+ let mut options = packet.options_mut();
+ if let Some(value) = self.max_seg_size {
+ let tmp = options;
+ options = TcpOption::MaxSegmentSize(value).emit(tmp);
+ }
+ if let Some(value) = self.window_scale {
+ let tmp = options;
+ options = TcpOption::WindowScale(value).emit(tmp);
+ }
+ if self.sack_permitted {
+ let tmp = options;
+ options = TcpOption::SackPermitted.emit(tmp);
+ } else if self.ack_number.is_some() && self.sack_ranges.iter().any(|s| s.is_some()) {
+ let tmp = options;
+ options = TcpOption::SackRange(self.sack_ranges).emit(tmp);
+ }
+
+ if !options.is_empty() {
+ TcpOption::EndOfList.emit(options);
+ }
+ }
+ packet.set_urgent_at(0);
+ packet.payload_mut()[..self.payload.len()].copy_from_slice(self.payload);
+
+ if checksum_caps.tcp.tx() {
+ packet.fill_checksum(src_addr, dst_addr)
+ } else {
+ // make sure we get a consistently zeroed checksum,
+ // since implementations might rely on it
+ packet.set_checksum(0);
+ }
+ }
+
+ /// Return the length of the segment, in terms of sequence space.
+ pub const fn segment_len(&self) -> usize {
+ self.payload.len() + self.control.len()
+ }
+
+ /// Return whether the segment has no flags set (except PSH) and no data.
+ pub const fn is_empty(&self) -> bool {
+ match self.control {
+ _ if !self.payload.is_empty() => false,
+ Control::Syn | Control::Fin | Control::Rst => false,
+ Control::None | Control::Psh => true,
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Cannot use Repr::parse because we don't have the IP addresses.
+ write!(f, "TCP src={} dst={}", self.src_port(), self.dst_port())?;
+ if self.syn() {
+ write!(f, " syn")?
+ }
+ if self.fin() {
+ write!(f, " fin")?
+ }
+ if self.rst() {
+ write!(f, " rst")?
+ }
+ if self.psh() {
+ write!(f, " psh")?
+ }
+ if self.ece() {
+ write!(f, " ece")?
+ }
+ if self.cwr() {
+ write!(f, " cwr")?
+ }
+ if self.ns() {
+ write!(f, " ns")?
+ }
+ write!(f, " seq={}", self.seq_number())?;
+ if self.ack() {
+ write!(f, " ack={}", self.ack_number())?;
+ }
+ write!(f, " win={}", self.window_len())?;
+ if self.urg() {
+ write!(f, " urg={}", self.urgent_at())?;
+ }
+ write!(f, " len={}", self.payload().len())?;
+
+ let mut options = self.options();
+ while !options.is_empty() {
+ let (next_options, option) = match TcpOption::parse(options) {
+ Ok(res) => res,
+ Err(err) => return write!(f, " ({err})"),
+ };
+ match option {
+ TcpOption::EndOfList => break,
+ TcpOption::NoOperation => (),
+ TcpOption::MaxSegmentSize(value) => write!(f, " mss={value}")?,
+ TcpOption::WindowScale(value) => write!(f, " ws={value}")?,
+ TcpOption::SackPermitted => write!(f, " sACK")?,
+ TcpOption::SackRange(slice) => write!(f, " sACKr{slice:?}")?, // debug print conveniently includes the []s
+ TcpOption::Unknown { kind, .. } => write!(f, " opt({kind})")?,
+ }
+ options = next_options;
+ }
+ Ok(())
+ }
+}
+
+impl<'a> fmt::Display for Repr<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TCP src={} dst={}", self.src_port, self.dst_port)?;
+ match self.control {
+ Control::Syn => write!(f, " syn")?,
+ Control::Fin => write!(f, " fin")?,
+ Control::Rst => write!(f, " rst")?,
+ Control::Psh => write!(f, " psh")?,
+ Control::None => (),
+ }
+ write!(f, " seq={}", self.seq_number)?;
+ if let Some(ack_number) = self.ack_number {
+ write!(f, " ack={ack_number}")?;
+ }
+ write!(f, " win={}", self.window_len)?;
+ write!(f, " len={}", self.payload.len())?;
+ if let Some(max_seg_size) = self.max_seg_size {
+ write!(f, " mss={max_seg_size}")?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl<'a> defmt::Format for Repr<'a> {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(fmt, "TCP src={} dst={}", self.src_port, self.dst_port);
+ match self.control {
+ Control::Syn => defmt::write!(fmt, " syn"),
+ Control::Fin => defmt::write!(fmt, " fin"),
+ Control::Rst => defmt::write!(fmt, " rst"),
+ Control::Psh => defmt::write!(fmt, " psh"),
+ Control::None => (),
+ }
+ defmt::write!(fmt, " seq={}", self.seq_number);
+ if let Some(ack_number) = self.ack_number {
+ defmt::write!(fmt, " ack={}", ack_number);
+ }
+ defmt::write!(fmt, " win={}", self.window_len);
+ defmt::write!(fmt, " len={}", self.payload.len());
+ if let Some(max_seg_size) = self.max_seg_size {
+ defmt::write!(fmt, " mss={}", max_seg_size);
+ }
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ match Packet::new_checked(buffer) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(packet) => write!(f, "{indent}{packet}"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::Ipv4Address;
+
+ #[cfg(feature = "proto-ipv4")]
+ const SRC_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
+ #[cfg(feature = "proto-ipv4")]
+ const DST_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 2]);
+
+ #[cfg(feature = "proto-ipv4")]
+ static PACKET_BYTES: [u8; 28] = [
+ 0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x60, 0x35, 0x01,
+ 0x23, 0x01, 0xb6, 0x02, 0x01, 0x03, 0x03, 0x0c, 0x01, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ #[cfg(feature = "proto-ipv4")]
+ static OPTION_BYTES: [u8; 4] = [0x03, 0x03, 0x0c, 0x01];
+
+ #[cfg(feature = "proto-ipv4")]
+ static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(packet.src_port(), 48896);
+ assert_eq!(packet.dst_port(), 80);
+ assert_eq!(packet.seq_number(), SeqNumber(0x01234567));
+ assert_eq!(packet.ack_number(), SeqNumber(0x89abcdefu32 as i32));
+ assert_eq!(packet.header_len(), 24);
+ assert!(packet.fin());
+ assert!(!packet.syn());
+ assert!(packet.rst());
+ assert!(!packet.psh());
+ assert!(packet.ack());
+ assert!(packet.urg());
+ assert_eq!(packet.window_len(), 0x0123);
+ assert_eq!(packet.urgent_at(), 0x0201);
+ assert_eq!(packet.checksum(), 0x01b6);
+ assert_eq!(packet.options(), &OPTION_BYTES[..]);
+ assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
+ assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; PACKET_BYTES.len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_src_port(48896);
+ packet.set_dst_port(80);
+ packet.set_seq_number(SeqNumber(0x01234567));
+ packet.set_ack_number(SeqNumber(0x89abcdefu32 as i32));
+ packet.set_header_len(24);
+ packet.clear_flags();
+ packet.set_fin(true);
+ packet.set_syn(false);
+ packet.set_rst(true);
+ packet.set_psh(false);
+ packet.set_ack(true);
+ packet.set_urg(true);
+ packet.set_window_len(0x0123);
+ packet.set_urgent_at(0x0201);
+ packet.set_checksum(0xEEEE);
+ packet.options_mut().copy_from_slice(&OPTION_BYTES[..]);
+ packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
+ packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into());
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_truncated() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..23]);
+ assert_eq!(packet.check_len(), Err(Error));
+ }
+
+ #[test]
+ fn test_impossible_len() {
+ let mut bytes = vec![0; 20];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_header_len(10);
+ assert_eq!(packet.check_len(), Err(Error));
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ static SYN_PACKET_BYTES: [u8; 24] = [
+ 0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x01,
+ 0x23, 0x7a, 0x8d, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ #[cfg(feature = "proto-ipv4")]
+ fn packet_repr() -> Repr<'static> {
+ Repr {
+ src_port: 48896,
+ dst_port: 80,
+ seq_number: SeqNumber(0x01234567),
+ ack_number: None,
+ window_len: 0x0123,
+ window_scale: None,
+ control: Control::Syn,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &PAYLOAD_BYTES,
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&SYN_PACKET_BYTES[..]);
+ let repr = Repr::parse(
+ &packet,
+ &SRC_ADDR.into(),
+ &DST_ADDR.into(),
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_emit() {
+ let repr = packet_repr();
+ let mut bytes = vec![0xa5; repr.buffer_len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(
+ &mut packet,
+ &SRC_ADDR.into(),
+ &DST_ADDR.into(),
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &SYN_PACKET_BYTES[..]);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_header_len_multiple_of_4() {
+ let mut repr = packet_repr();
+ repr.window_scale = Some(0); // This TCP Option needs 3 bytes.
+ assert_eq!(repr.header_len() % 4, 0); // Should e.g. be 28 instead of 27.
+ }
+
+ macro_rules! assert_option_parses {
+ ($opt:expr, $data:expr) => {{
+ assert_eq!(TcpOption::parse($data), Ok((&[][..], $opt)));
+ let buffer = &mut [0; 40][..$opt.buffer_len()];
+ assert_eq!($opt.emit(buffer), &mut []);
+ assert_eq!(&*buffer, $data);
+ }};
+ }
+
+ #[test]
+ fn test_tcp_options() {
+ assert_option_parses!(TcpOption::EndOfList, &[0x00]);
+ assert_option_parses!(TcpOption::NoOperation, &[0x01]);
+ assert_option_parses!(TcpOption::MaxSegmentSize(1500), &[0x02, 0x04, 0x05, 0xdc]);
+ assert_option_parses!(TcpOption::WindowScale(12), &[0x03, 0x03, 0x0c]);
+ assert_option_parses!(TcpOption::SackPermitted, &[0x4, 0x02]);
+ assert_option_parses!(
+ TcpOption::SackRange([Some((500, 1500)), None, None]),
+ &[0x05, 0x0a, 0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x05, 0xdc]
+ );
+ assert_option_parses!(
+ TcpOption::SackRange([Some((875, 1225)), Some((1500, 2500)), None]),
+ &[
+ 0x05, 0x12, 0x00, 0x00, 0x03, 0x6b, 0x00, 0x00, 0x04, 0xc9, 0x00, 0x00, 0x05, 0xdc,
+ 0x00, 0x00, 0x09, 0xc4
+ ]
+ );
+ assert_option_parses!(
+ TcpOption::SackRange([
+ Some((875000, 1225000)),
+ Some((1500000, 2500000)),
+ Some((876543210, 876654320))
+ ]),
+ &[
+ 0x05, 0x1a, 0x00, 0x0d, 0x59, 0xf8, 0x00, 0x12, 0xb1, 0x28, 0x00, 0x16, 0xe3, 0x60,
+ 0x00, 0x26, 0x25, 0xa0, 0x34, 0x3e, 0xfc, 0xea, 0x34, 0x40, 0xae, 0xf0
+ ]
+ );
+ assert_option_parses!(
+ TcpOption::Unknown {
+ kind: 12,
+ data: &[1, 2, 3][..]
+ },
+ &[0x0c, 0x05, 0x01, 0x02, 0x03]
+ )
+ }
+
+ #[test]
+ fn test_malformed_tcp_options() {
+ assert_eq!(TcpOption::parse(&[]), Err(Error));
+ assert_eq!(TcpOption::parse(&[0xc]), Err(Error));
+ assert_eq!(TcpOption::parse(&[0xc, 0x05, 0x01, 0x02]), Err(Error));
+ assert_eq!(TcpOption::parse(&[0xc, 0x01]), Err(Error));
+ assert_eq!(TcpOption::parse(&[0x2, 0x02]), Err(Error));
+ assert_eq!(TcpOption::parse(&[0x3, 0x02]), Err(Error));
+ }
+}
diff --git a/src/wire/udp.rs b/src/wire/udp.rs
new file mode 100644
index 0000000..77f9f84
--- /dev/null
+++ b/src/wire/udp.rs
@@ -0,0 +1,482 @@
+use byteorder::{ByteOrder, NetworkEndian};
+use core::fmt;
+
+use super::{Error, Result};
+use crate::phy::ChecksumCapabilities;
+use crate::wire::ip::checksum;
+use crate::wire::{IpAddress, IpProtocol};
+
+/// A read/write wrapper around an User Datagram Protocol packet buffer.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Packet<T: AsRef<[u8]>> {
+ buffer: T,
+}
+
+mod field {
+ #![allow(non_snake_case)]
+
+ use crate::wire::field::*;
+
+ pub const SRC_PORT: Field = 0..2;
+ pub const DST_PORT: Field = 2..4;
+ pub const LENGTH: Field = 4..6;
+ pub const CHECKSUM: Field = 6..8;
+
+ pub const fn PAYLOAD(length: u16) -> Field {
+ CHECKSUM.end..(length as usize)
+ }
+}
+
+pub const HEADER_LEN: usize = field::CHECKSUM.end;
+
+#[allow(clippy::len_without_is_empty)]
+impl<T: AsRef<[u8]>> Packet<T> {
+ /// Imbue a raw octet buffer with UDP packet structure.
+ pub const fn new_unchecked(buffer: T) -> Packet<T> {
+ Packet { buffer }
+ }
+
+ /// Shorthand for a combination of [new_unchecked] and [check_len].
+ ///
+ /// [new_unchecked]: #method.new_unchecked
+ /// [check_len]: #method.check_len
+ pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+ let packet = Self::new_unchecked(buffer);
+ packet.check_len()?;
+ Ok(packet)
+ }
+
+ /// Ensure that no accessor method will panic if called.
+ /// Returns `Err(Error)` if the buffer is too short.
+ /// Returns `Err(Error)` if the length field has a value smaller
+ /// than the header length.
+ ///
+ /// The result of this check is invalidated by calling [set_len].
+ ///
+ /// [set_len]: #method.set_len
+ pub fn check_len(&self) -> Result<()> {
+ let buffer_len = self.buffer.as_ref().len();
+ if buffer_len < HEADER_LEN {
+ Err(Error)
+ } else {
+ let field_len = self.len() as usize;
+ if buffer_len < field_len || field_len < HEADER_LEN {
+ Err(Error)
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ /// Consume the packet, returning the underlying buffer.
+ pub fn into_inner(self) -> T {
+ self.buffer
+ }
+
+ /// Return the source port field.
+ #[inline]
+ pub fn src_port(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::SRC_PORT])
+ }
+
+ /// Return the destination port field.
+ #[inline]
+ pub fn dst_port(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::DST_PORT])
+ }
+
+ /// Return the length field.
+ #[inline]
+ pub fn len(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::LENGTH])
+ }
+
+ /// Return the checksum field.
+ #[inline]
+ pub fn checksum(&self) -> u16 {
+ let data = self.buffer.as_ref();
+ NetworkEndian::read_u16(&data[field::CHECKSUM])
+ }
+
+ /// Validate the packet checksum.
+ ///
+ /// # Panics
+ /// This function panics unless `src_addr` and `dst_addr` belong to the same family,
+ /// and that family is IPv4 or IPv6.
+ ///
+ /// # Fuzzing
+ /// This function always returns `true` when fuzzing.
+ pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
+ if cfg!(fuzzing) {
+ return true;
+ }
+
+ // From the RFC:
+ // > An all zero transmitted checksum value means that the transmitter
+ // > generated no checksum (for debugging or for higher level protocols
+ // > that don't care).
+ if self.checksum() == 0 {
+ return true;
+ }
+
+ let data = self.buffer.as_ref();
+ checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32),
+ checksum::data(&data[..self.len() as usize]),
+ ]) == !0
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+ /// Return a pointer to the payload.
+ #[inline]
+ pub fn payload(&self) -> &'a [u8] {
+ let length = self.len();
+ let data = self.buffer.as_ref();
+ &data[field::PAYLOAD(length)]
+ }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+ /// Set the source port field.
+ #[inline]
+ pub fn set_src_port(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::SRC_PORT], value)
+ }
+
+ /// Set the destination port field.
+ #[inline]
+ pub fn set_dst_port(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::DST_PORT], value)
+ }
+
+ /// Set the length field.
+ #[inline]
+ pub fn set_len(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::LENGTH], value)
+ }
+
+ /// Set the checksum field.
+ #[inline]
+ pub fn set_checksum(&mut self, value: u16) {
+ let data = self.buffer.as_mut();
+ NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+ }
+
+ /// Compute and fill in the header checksum.
+ ///
+ /// # Panics
+ /// This function panics unless `src_addr` and `dst_addr` belong to the same family,
+ /// and that family is IPv4 or IPv6.
+ pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) {
+ self.set_checksum(0);
+ let checksum = {
+ let data = self.buffer.as_ref();
+ !checksum::combine(&[
+ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32),
+ checksum::data(&data[..self.len() as usize]),
+ ])
+ };
+ // UDP checksum value of 0 means no checksum; if the checksum really is zero,
+ // use all-ones, which indicates that the remote end must verify the checksum.
+ // Arithmetically, RFC 1071 checksums of all-zeroes and all-ones behave identically,
+ // so no action is necessary on the remote end.
+ self.set_checksum(if checksum == 0 { 0xffff } else { checksum })
+ }
+
+ /// Return a mutable pointer to the payload.
+ #[inline]
+ pub fn payload_mut(&mut self) -> &mut [u8] {
+ let length = self.len();
+ let data = self.buffer.as_mut();
+ &mut data[field::PAYLOAD(length)]
+ }
+}
+
+impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
+ fn as_ref(&self) -> &[u8] {
+ self.buffer.as_ref()
+ }
+}
+
+/// A high-level representation of an User Datagram Protocol packet.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr {
+ pub src_port: u16,
+ pub dst_port: u16,
+}
+
+impl Repr {
+ /// Parse an User Datagram Protocol packet and return a high-level representation.
+ pub fn parse<T>(
+ packet: &Packet<&T>,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ checksum_caps: &ChecksumCapabilities,
+ ) -> Result<Repr>
+ where
+ T: AsRef<[u8]> + ?Sized,
+ {
+ // Destination port cannot be omitted (but source port can be).
+ if packet.dst_port() == 0 {
+ return Err(Error);
+ }
+ // Valid checksum is expected...
+ if checksum_caps.udp.rx() && !packet.verify_checksum(src_addr, dst_addr) {
+ match (src_addr, dst_addr) {
+ // ... except on UDP-over-IPv4, where it can be omitted.
+ #[cfg(feature = "proto-ipv4")]
+ (&IpAddress::Ipv4(_), &IpAddress::Ipv4(_)) if packet.checksum() == 0 => (),
+ _ => return Err(Error),
+ }
+ }
+
+ Ok(Repr {
+ src_port: packet.src_port(),
+ dst_port: packet.dst_port(),
+ })
+ }
+
+ /// Return the length of the packet header that will be emitted from this high-level representation.
+ pub const fn header_len(&self) -> usize {
+ HEADER_LEN
+ }
+
+ /// Emit a high-level representation into an User Datagram Protocol packet.
+ ///
+ /// This never calculates the checksum, and is intended for internal-use only,
+ /// not for packets that are going to be actually sent over the network. For
+ /// example, when decompressing 6lowpan.
+ pub(crate) fn emit_header<T: ?Sized>(&self, packet: &mut Packet<&mut T>, payload_len: usize)
+ where
+ T: AsRef<[u8]> + AsMut<[u8]>,
+ {
+ packet.set_src_port(self.src_port);
+ packet.set_dst_port(self.dst_port);
+ packet.set_len((HEADER_LEN + payload_len) as u16);
+ packet.set_checksum(0);
+ }
+
+ /// Emit a high-level representation into an User Datagram Protocol packet.
+ pub fn emit<T: ?Sized>(
+ &self,
+ packet: &mut Packet<&mut T>,
+ src_addr: &IpAddress,
+ dst_addr: &IpAddress,
+ payload_len: usize,
+ emit_payload: impl FnOnce(&mut [u8]),
+ checksum_caps: &ChecksumCapabilities,
+ ) where
+ T: AsRef<[u8]> + AsMut<[u8]>,
+ {
+ packet.set_src_port(self.src_port);
+ packet.set_dst_port(self.dst_port);
+ packet.set_len((HEADER_LEN + payload_len) as u16);
+ emit_payload(packet.payload_mut());
+
+ if checksum_caps.udp.tx() {
+ packet.fill_checksum(src_addr, dst_addr)
+ } else {
+ // make sure we get a consistently zeroed checksum,
+ // since implementations might rely on it
+ packet.set_checksum(0);
+ }
+ }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Cannot use Repr::parse because we don't have the IP addresses.
+ write!(
+ f,
+ "UDP src={} dst={} len={}",
+ self.src_port(),
+ self.dst_port(),
+ self.payload().len()
+ )
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl<'a, T: AsRef<[u8]> + ?Sized> defmt::Format for Packet<&'a T> {
+ fn format(&self, fmt: defmt::Formatter) {
+ // Cannot use Repr::parse because we don't have the IP addresses.
+ defmt::write!(
+ fmt,
+ "UDP src={} dst={} len={}",
+ self.src_port(),
+ self.dst_port(),
+ self.payload().len()
+ );
+ }
+}
+
+impl fmt::Display for Repr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "UDP src={} dst={}", self.src_port, self.dst_port)
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Repr {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(fmt, "UDP src={} dst={}", self.src_port, self.dst_port);
+ }
+}
+
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+ fn pretty_print(
+ buffer: &dyn AsRef<[u8]>,
+ f: &mut fmt::Formatter,
+ indent: &mut PrettyIndent,
+ ) -> fmt::Result {
+ match Packet::new_checked(buffer) {
+ Err(err) => write!(f, "{indent}({err})"),
+ Ok(packet) => write!(f, "{indent}{packet}"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[cfg(feature = "proto-ipv4")]
+ use crate::wire::Ipv4Address;
+
+ #[cfg(feature = "proto-ipv4")]
+ const SRC_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
+ #[cfg(feature = "proto-ipv4")]
+ const DST_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 2]);
+
+ #[cfg(feature = "proto-ipv4")]
+ static PACKET_BYTES: [u8; 12] = [
+ 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ #[cfg(feature = "proto-ipv4")]
+ static NO_CHECKSUM_PACKET: [u8; 12] = [
+ 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff,
+ ];
+
+ #[cfg(feature = "proto-ipv4")]
+ static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_deconstruct() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ assert_eq!(packet.src_port(), 48896);
+ assert_eq!(packet.dst_port(), 53);
+ assert_eq!(packet.len(), 12);
+ assert_eq!(packet.checksum(), 0x124d);
+ assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
+ assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_construct() {
+ let mut bytes = vec![0xa5; 12];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_src_port(48896);
+ packet.set_dst_port(53);
+ packet.set_len(12);
+ packet.set_checksum(0xffff);
+ packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
+ packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into());
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+
+ #[test]
+ fn test_impossible_len() {
+ let mut bytes = vec![0; 12];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_len(4);
+ assert_eq!(packet.check_len(), Err(Error));
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_zero_checksum() {
+ let mut bytes = vec![0; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_src_port(1);
+ packet.set_dst_port(31881);
+ packet.set_len(8);
+ packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into());
+ assert_eq!(packet.checksum(), 0xffff);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_no_checksum() {
+ let mut bytes = vec![0; 8];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ packet.set_src_port(1);
+ packet.set_dst_port(31881);
+ packet.set_len(8);
+ packet.set_checksum(0);
+ assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()));
+ }
+
+ #[cfg(feature = "proto-ipv4")]
+ fn packet_repr() -> Repr {
+ Repr {
+ src_port: 48896,
+ dst_port: 53,
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_parse() {
+ let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
+ let repr = Repr::parse(
+ &packet,
+ &SRC_ADDR.into(),
+ &DST_ADDR.into(),
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_emit() {
+ let repr = packet_repr();
+ let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()];
+ let mut packet = Packet::new_unchecked(&mut bytes);
+ repr.emit(
+ &mut packet,
+ &SRC_ADDR.into(),
+ &DST_ADDR.into(),
+ PAYLOAD_BYTES.len(),
+ |payload| payload.copy_from_slice(&PAYLOAD_BYTES),
+ &ChecksumCapabilities::default(),
+ );
+ assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]);
+ }
+
+ #[test]
+ #[cfg(feature = "proto-ipv4")]
+ fn test_checksum_omitted() {
+ let packet = Packet::new_unchecked(&NO_CHECKSUM_PACKET[..]);
+ let repr = Repr::parse(
+ &packet,
+ &SRC_ADDR.into(),
+ &DST_ADDR.into(),
+ &ChecksumCapabilities::default(),
+ )
+ .unwrap();
+ assert_eq!(repr, packet_repr());
+ }
+}
diff --git a/utils/packet2pcap.rs b/utils/packet2pcap.rs
new file mode 100644
index 0000000..7d06c6f
--- /dev/null
+++ b/utils/packet2pcap.rs
@@ -0,0 +1,74 @@
+use getopts::Options;
+use smoltcp::phy::{PcapLinkType, PcapSink};
+use smoltcp::time::Instant;
+use std::env;
+use std::fs::File;
+use std::io::{self, Read};
+use std::path::Path;
+use std::process::exit;
+
+fn convert(
+ packet_filename: &Path,
+ pcap_filename: &Path,
+ link_type: PcapLinkType,
+) -> io::Result<()> {
+ let mut packet_file = File::open(packet_filename)?;
+ let mut packet = Vec::new();
+ packet_file.read_to_end(&mut packet)?;
+
+ let mut pcap_file = File::create(pcap_filename)?;
+ PcapSink::global_header(&mut pcap_file, link_type);
+ PcapSink::packet(&mut pcap_file, Instant::from_millis(0), &packet[..]);
+
+ Ok(())
+}
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options] INPUT OUTPUT");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ let program = args[0].clone();
+
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "print this help menu");
+ opts.optopt(
+ "t",
+ "link-type",
+ "set link type (one of: ethernet ip)",
+ "TYPE",
+ );
+
+ let matches = match opts.parse(&args[1..]) {
+ Ok(m) => m,
+ Err(e) => {
+ eprintln!("{e}");
+ return;
+ }
+ };
+
+ let link_type = match matches.opt_str("t").as_ref().map(|s| &s[..]) {
+ Some("ethernet") => Some(PcapLinkType::Ethernet),
+ Some("ip") => Some(PcapLinkType::Ip),
+ _ => None,
+ };
+
+ if matches.opt_present("h") || matches.free.len() != 2 || link_type.is_none() {
+ print_usage(&program, opts);
+ return;
+ }
+
+ match convert(
+ Path::new(&matches.free[0]),
+ Path::new(&matches.free[1]),
+ link_type.unwrap(),
+ ) {
+ Ok(()) => (),
+ Err(e) => {
+ eprintln!("Cannot convert packet to pcap: {e}");
+ exit(1);
+ }
+ }
+}