瀏覽代碼

Merge branch 'main' into add-basic-ipsec-support-to-wire

thegreathir 1 年之前
父節點
當前提交
8835c7673b

+ 8 - 0
Cargo.toml

@@ -203,6 +203,14 @@ 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 = []

+ 17 - 7
README.md

@@ -38,9 +38,8 @@ There are 3 supported mediums.
   * Jumbo frames are **not** supported.
 * IP
   * Unicast, broadcast and multicast packets are supported.
-* IEEE 802.15.4 + 6LoWPAN (experimental)
-  * Unicast, broadcast and multicast packets are supported.
-  * ONLY UDP packets are supported.
+* IEEE 802.15.4
+  * Only support for data frames.
 
 ### IP layer
 
@@ -62,6 +61,14 @@ There are 3 supported mediums.
   * 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
@@ -137,7 +144,7 @@ To use the _smoltcp_ library in your project, add the following to `Cargo.toml`:
 
 ```toml
 [dependencies]
-smoltcp = "0.8.0"
+smoltcp = "0.10.0"
 ```
 
 The default configuration assumes a hosted environment, for ease of evaluation.
@@ -145,7 +152,7 @@ You probably want to disable default features and configure them one by one:
 
 ```toml
 [dependencies]
-smoltcp = { version = "0.8.0", default-features = false, features = ["log"] }
+smoltcp = { version = "0.10.0", default-features = false, features = ["log"] }
 ```
 
 ## Feature flags
@@ -204,12 +211,13 @@ Enable the corresponding socket type.
 
 These features are enabled by default.
 
-### Features `proto-ipv4` and `proto-ipv6`
+### Features `proto-ipv4`, `proto-ipv6` and `proto-sixlowpan`
 
-Enable [IPv4] and [IPv6] respectively.
+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
 
@@ -276,7 +284,9 @@ Maximum amount of DNS servers that can be configured in one DNS socket. Default:
 
 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
 

+ 1 - 0
build.rs

@@ -15,6 +15,7 @@ static CONFIGS: &[(&str, usize)] = &[
     ("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),

+ 1 - 0
ci.sh

@@ -30,6 +30,7 @@ FEATURES_TEST=(
     "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"
 )
 

+ 1 - 0
gen_config.py

@@ -36,6 +36,7 @@ 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)

+ 7 - 7
src/iface/interface/ipv6.rs

@@ -250,19 +250,19 @@ impl InterfaceInner {
         handled_by_raw_socket: bool,
         ip_payload: &'frame [u8],
     ) -> Option<IpPacket<'frame>> {
-        let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ip_payload));
+        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));
 
-        let hbh_options = Ipv6OptionsIterator::new(hbh_repr.data);
-        for opt_repr in hbh_options {
-            let opt_repr = check!(opt_repr);
+        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_) {
+                    match Ipv6OptionFailureType::from(*type_) {
                         Ipv6OptionFailureType::Skip => (),
                         Ipv6OptionFailureType::Discard => {
                             return None;
@@ -280,9 +280,9 @@ impl InterfaceInner {
             sockets,
             meta,
             ipv6_repr,
-            hbh_repr.next_header,
+            ext_repr.next_header,
             handled_by_raw_socket,
-            &ip_payload[hbh_repr.header_len() + hbh_repr.data.len()..],
+            &ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
         )
     }
 

+ 14 - 98
src/iface/interface/mod.rs

@@ -230,7 +230,7 @@ use check;
 /// 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 {
-    inner: InterfaceInner,
+    pub(crate) inner: InterfaceInner,
     fragments: FragmentsBuffer,
     fragmenter: Fragmenter,
 }
@@ -968,97 +968,6 @@ impl InterfaceInner {
         None
     }
 
-    #[cfg(test)]
-    pub(crate) fn mock() -> Self {
-        Self {
-            caps: DeviceCapabilities {
-                #[cfg(feature = "medium-ethernet")]
-                medium: crate::phy::Medium::Ethernet,
-                #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ip"))]
-                medium: crate::phy::Medium::Ip,
-                #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
-                medium: crate::phy::Medium::Ieee802154,
-
-                checksum: crate::phy::ChecksumCapabilities {
-                    #[cfg(feature = "proto-ipv4")]
-                    icmpv4: crate::phy::Checksum::Both,
-                    #[cfg(feature = "proto-ipv6")]
-                    icmpv6: crate::phy::Checksum::Both,
-                    ipv4: crate::phy::Checksum::Both,
-                    tcp: crate::phy::Checksum::Both,
-                    udp: crate::phy::Checksum::Both,
-                },
-                max_burst_size: None,
-                #[cfg(feature = "medium-ethernet")]
-                max_transmission_unit: 1514,
-                #[cfg(not(feature = "medium-ethernet"))]
-                max_transmission_unit: 1500,
-            },
-            now: Instant::from_millis_const(0),
-
-            ip_addrs: Vec::from_slice(&[
-                #[cfg(feature = "proto-ipv4")]
-                IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 1), 24)),
-                #[cfg(feature = "proto-ipv6")]
-                IpCidr::Ipv6(Ipv6Cidr::new(
-                    Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
-                    64,
-                )),
-            ])
-            .unwrap(),
-            rand: Rand::new(1234),
-            routes: Routes::new(),
-
-            #[cfg(feature = "proto-ipv4")]
-            any_ip: false,
-
-            #[cfg(feature = "medium-ieee802154")]
-            pan_id: Some(crate::wire::Ieee802154Pan(0xabcd)),
-            #[cfg(feature = "medium-ieee802154")]
-            sequence_no: 1,
-
-            #[cfg(feature = "proto-sixlowpan-fragmentation")]
-            tag: 1,
-
-            #[cfg(feature = "proto-sixlowpan")]
-            sixlowpan_address_context: Vec::new(),
-
-            #[cfg(feature = "proto-ipv4-fragmentation")]
-            ipv4_id: 1,
-
-            #[cfg(all(
-                feature = "medium-ip",
-                not(feature = "medium-ethernet"),
-                not(feature = "medium-ieee802154")
-            ))]
-            hardware_addr: crate::wire::HardwareAddress::Ip,
-
-            #[cfg(feature = "medium-ethernet")]
-            hardware_addr: crate::wire::HardwareAddress::Ethernet(crate::wire::EthernetAddress([
-                0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
-            ])),
-
-            #[cfg(all(
-                not(feature = "medium-ip"),
-                not(feature = "medium-ethernet"),
-                feature = "medium-ieee802154"
-            ))]
-            hardware_addr: crate::wire::HardwareAddress::Ieee802154(
-                crate::wire::Ieee802154Address::Extended([
-                    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x2, 0x2,
-                ]),
-            ),
-
-            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
-            neighbor_cache: NeighborCache::new(),
-
-            #[cfg(feature = "proto-igmp")]
-            igmp_report_state: IgmpReportState::Inactive,
-            #[cfg(feature = "proto-igmp")]
-            ipv4_multicast_groups: LinearMap::new(),
-        }
-    }
-
     #[cfg(test)]
     #[allow(unused)] // unused depending on which sockets are enabled
     pub(crate) fn set_now(&mut self, now: Instant) {
@@ -1427,16 +1336,21 @@ impl InterfaceInner {
             let b = dst_addr.as_bytes();
             let hardware_addr = match *dst_addr {
                 #[cfg(feature = "proto-ipv4")]
-                IpAddress::Ipv4(_addr) => {
-                    HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
+                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")]
@@ -1467,8 +1381,10 @@ impl InterfaceInner {
         }
 
         match (src_addr, dst_addr) {
-            #[cfg(feature = "proto-ipv4")]
-            (&IpAddress::Ipv4(src_addr), IpAddress::Ipv4(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

+ 582 - 196
src/iface/interface/sixlowpan.rs

@@ -1,5 +1,6 @@
 use super::*;
 
+use crate::iface::ip_packet::Ipv6Packet;
 use crate::phy::ChecksumCapabilities;
 use crate::wire::{Ipv6Packet as Ipv6PacketWire, *};
 
@@ -33,9 +34,10 @@ impl InterfaceInner {
                 }
             }
             SixlowpanPacket::IphcHeader => {
-                match self.decompress_sixlowpan(
+                match Self::sixlowpan_to_ipv6(
+                    &self.sixlowpan_address_context,
                     ieee802154_repr,
-                    payload.as_ref(),
+                    payload,
                     None,
                     &mut f.decompress_buf,
                 ) {
@@ -101,8 +103,14 @@ impl InterfaceInner {
 
             // Decompress headers+payload into the assembler.
             if let Err(e) = frag_slot.add_with(0, |buffer| {
-                self.decompress_sixlowpan(ieee802154_repr, frag.payload(), Some(total_size), buffer)
-                    .map_err(|_| AssemblerError)
+                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;
@@ -124,8 +132,8 @@ impl InterfaceInner {
         }
     }
 
-    fn decompress_sixlowpan(
-        &self,
+    fn sixlowpan_to_ipv6(
+        address_context: &[SixlowpanAddressContext],
         ieee802154_repr: &Ieee802154Repr,
         iphc_payload: &[u8],
         total_size: Option<usize>,
@@ -136,20 +144,40 @@ impl InterfaceInner {
             &iphc,
             ieee802154_repr.src_addr,
             ieee802154_repr.dst_addr,
-            &self.sixlowpan_address_context,
+            address_context,
         )?;
 
-        let mut decompressed_size = 40 + iphc.payload().len();
-
-        let next_header = match iphc_repr.next_header {
+        let first_next_header = match iphc_repr.next_header {
             SixlowpanNextHeader::Compressed => {
                 match SixlowpanNhcPacket::dispatch(iphc.payload())? {
                     SixlowpanNhcPacket::ExtHeader => {
-                        net_debug!("Extension headers are currently not supported for 6LoWPAN");
-                        IpProtocol::Unknown(0)
+                        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);
+                        data = &data[ext_repr.buffer_len() + ext_repr.length as usize..];
                     }
                     SixlowpanNhcPacket::UdpHeader => {
-                        let udp_packet = SixlowpanUdpNhcPacket::new_checked(iphc.payload())?;
+                        let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?;
                         let udp_repr = SixlowpanUdpNhcRepr::parse(
                             &udp_packet,
                             &iphc_repr.src_addr,
@@ -159,12 +187,20 @@ impl InterfaceInner {
 
                         decompressed_size += 8;
                         decompressed_size -= udp_repr.header_len();
-                        IpProtocol::Udp
+                        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);
+                    }
+                },
             }
-            SixlowpanNextHeader::Uncompressed(proto) => proto,
-        };
+        }
 
         if buffer.len() < decompressed_size {
             net_debug!("sixlowpan decompress: buffer too short");
@@ -178,27 +214,68 @@ impl InterfaceInner {
             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,
+            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 = Ipv6PacketWire::new_unchecked(&mut buffer[..ipv6_repr.buffer_len()]);
         ipv6_repr.emit(&mut ipv6_packet);
-        let buffer = &mut buffer[ipv6_repr.buffer_len()..];
+        let mut buffer = &mut buffer[ipv6_repr.buffer_len()..];
 
-        match iphc_repr.next_header {
-            SixlowpanNextHeader::Compressed => {
-                match SixlowpanNhcPacket::dispatch(iphc.payload())? {
-                    SixlowpanNhcPacket::ExtHeader => todo!(),
+        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 => {
-                        // We need to uncompress the UDP packet and emit it to the
-                        // buffer.
-                        let udp_packet = SixlowpanUdpNhcPacket::new_checked(iphc.payload())?;
+                        let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?;
+                        let payload = udp_packet.payload();
                         let udp_repr = SixlowpanUdpNhcRepr::parse(
                             &udp_packet,
                             &iphc_repr.src_addr,
@@ -206,22 +283,33 @@ impl InterfaceInner {
                             &ChecksumCapabilities::ignored(),
                         )?;
 
-                        let mut udp = UdpPacket::new_unchecked(
-                            &mut buffer[..udp_repr.0.header_len() + iphc.payload().len()
-                                - udp_repr.header_len()],
-                        );
-                        udp_repr.0.emit_header(&mut udp, ipv6_repr.payload_len - 8);
+                        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);
 
-                        buffer[8..].copy_from_slice(&iphc.payload()[udp_repr.header_len()..]);
+                        break;
                     }
-                }
-            }
-            SixlowpanNextHeader::Uncompressed(_) => {
-                // For uncompressed headers we just copy the slice.
-                let len = iphc.payload().len();
-                buffer[..len].copy_from_slice(iphc.payload());
+                },
+                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)
     }
@@ -234,69 +322,20 @@ impl InterfaceInner {
         ieee_repr: Ieee802154Repr,
         frag: &mut Fragmenter,
     ) {
-        let ip_repr = packet.ip_repr();
-
-        let (src_addr, dst_addr) = match (ip_repr.src_addr(), ip_repr.dst_addr()) {
-            (IpAddress::Ipv6(src_addr), IpAddress::Ipv6(dst_addr)) => (src_addr, dst_addr),
-            #[allow(unreachable_patterns)]
-            _ => {
-                unreachable!()
-            }
+        let packet = match packet {
+            #[cfg(feature = "proto-ipv4")]
+            IpPacket::Ipv4(_) => unreachable!(),
+            IpPacket::Ipv6(packet) => packet,
         };
 
-        // Create the 6LoWPAN IPHC header.
-        let iphc_repr = SixlowpanIphcRepr {
-            src_addr,
-            ll_src_addr: ieee_repr.src_addr,
-            dst_addr,
-            ll_dst_addr: ieee_repr.dst_addr,
-            next_header: match &packet.payload() {
-                IpPayload::Icmpv6(..) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6),
-                #[cfg(feature = "socket-tcp")]
-                IpPayload::Tcp(..) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp),
-                #[cfg(feature = "socket-udp")]
-                IpPayload::Udp(..) => SixlowpanNextHeader::Compressed,
-                #[allow(unreachable_patterns)]
-                _ => {
-                    net_debug!("dispatch_ieee802154: dropping, unhandled protocol.");
-                    return;
-                }
-            },
-            hop_limit: ip_repr.hop_limit(),
-            ecn: None,
-            dscp: None,
-            flow_label: None,
-        };
-
-        // Now we calculate the total size of the packet.
-        // We need to know this, such that we know when to do the fragmentation.
-        let mut total_size = 0;
-        total_size += iphc_repr.buffer_len();
-        let mut _compressed_headers_len = iphc_repr.buffer_len();
-        let mut _uncompressed_headers_len = ip_repr.header_len();
-
-        match packet.payload() {
-            #[cfg(feature = "socket-udp")]
-            IpPayload::Udp(udpv6_repr, payload) => {
-                let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr);
-                _compressed_headers_len += udp_repr.header_len();
-                _uncompressed_headers_len += udpv6_repr.header_len();
-                total_size += udp_repr.header_len() + payload.len();
-            }
-            #[cfg(feature = "socket-tcp")]
-            IpPayload::Tcp(tcp_repr) => {
-                total_size += tcp_repr.buffer_len();
-            }
-            #[cfg(feature = "proto-ipv6")]
-            IpPayload::Icmpv6(icmp_repr) => {
-                total_size += icmp_repr.buffer_len();
-            }
-            #[allow(unreachable_patterns)]
-            _ => unreachable!(),
-        }
+        // 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")]
             {
@@ -308,8 +347,8 @@ impl InterfaceInner {
 
                 // `dispatch_ieee802154_frag` requires some information about the total packet size,
                 // the link local source and destination address...
-                let pkt = frag;
 
+                let pkt = frag;
                 if pkt.buffer.len() < total_size {
                     net_debug!(
                         "dispatch_ieee802154: dropping, \
@@ -319,63 +358,23 @@ impl InterfaceInner {
                     return;
                 }
 
-                pkt.sixlowpan.ll_dst_addr = ieee_repr.dst_addr.unwrap();
-                pkt.sixlowpan.ll_src_addr = ieee_repr.src_addr.unwrap();
+                let payload_length = packet.header.payload_len;
 
-                let mut iphc_packet =
-                    SixlowpanIphcPacket::new_unchecked(&mut pkt.buffer[..iphc_repr.buffer_len()]);
-                iphc_repr.emit(&mut iphc_packet);
-
-                let b = &mut pkt.buffer[iphc_repr.buffer_len()..];
-
-                match packet.payload() {
-                    #[cfg(feature = "socket-udp")]
-                    IpPayload::Udp(udpv6_repr, payload) => {
-                        let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr);
-                        let mut udp_packet = SixlowpanUdpNhcPacket::new_unchecked(
-                            &mut b[..udp_repr.header_len() + payload.len()],
-                        );
-                        udp_repr.emit(
-                            &mut udp_packet,
-                            &iphc_repr.src_addr,
-                            &iphc_repr.dst_addr,
-                            payload.len(),
-                            |buf| buf.copy_from_slice(payload),
-                        );
-                    }
-                    #[cfg(feature = "socket-tcp")]
-                    IpPayload::Tcp(tcp_repr) => {
-                        let mut tcp_packet =
-                            TcpPacket::new_unchecked(&mut b[..tcp_repr.buffer_len()]);
-                        tcp_repr.emit(
-                            &mut tcp_packet,
-                            &iphc_repr.src_addr.into(),
-                            &iphc_repr.dst_addr.into(),
-                            &self.caps.checksum,
-                        );
-                    }
-                    #[cfg(feature = "proto-ipv6")]
-                    IpPayload::Icmpv6(icmp_repr) => {
-                        let mut icmp_packet =
-                            Icmpv6Packet::new_unchecked(&mut b[..icmp_repr.buffer_len()]);
-                        icmp_repr.emit(
-                            &iphc_repr.src_addr.into(),
-                            &iphc_repr.dst_addr.into(),
-                            &mut icmp_packet,
-                            &self.caps.checksum,
-                        );
-                    }
-                    #[allow(unreachable_patterns)]
-                    _ => unreachable!(),
-                }
+                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 = (packet.ip_repr().payload_len() + 40) as u16;
+                pkt.sixlowpan.datagram_size = (payload_length + 40) as u16;
 
-                // We generate a random tag.
                 let tag = self.get_sixlowpan_fragment_tag();
                 // We save the tag for the other fragments that will be created when calling `poll`
                 // multiple times.
@@ -398,15 +397,15 @@ impl InterfaceInner {
                 //
                 // [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
 
-                let header_diff = _uncompressed_headers_len - _compressed_headers_len;
+                let header_diff = uncompressed_size - compressed_size;
                 let frag1_size =
-                    (125 - ieee_len - frag1.buffer_len() + header_diff) / 8 * 8 - (header_diff);
+                    (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]);
@@ -439,55 +438,241 @@ impl InterfaceInner {
                 ieee_repr.emit(&mut ieee_packet);
                 tx_buf = &mut tx_buf[ieee_len..];
 
-                let mut iphc_packet =
-                    SixlowpanIphcPacket::new_unchecked(&mut tx_buf[..iphc_repr.buffer_len()]);
-                iphc_repr.emit(&mut iphc_packet);
-                tx_buf = &mut tx_buf[iphc_repr.buffer_len()..];
-
-                match packet.payload() {
-                    #[cfg(feature = "socket-udp")]
-                    IpPayload::Udp(udpv6_repr, payload) => {
-                        let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr);
-                        let mut udp_packet = SixlowpanUdpNhcPacket::new_unchecked(
-                            &mut tx_buf[..udp_repr.header_len() + payload.len()],
-                        );
-                        udp_repr.emit(
-                            &mut udp_packet,
-                            &iphc_repr.src_addr,
-                            &iphc_repr.dst_addr,
-                            payload.len(),
-                            |buf| buf.copy_from_slice(payload),
-                        );
-                    }
-                    #[cfg(feature = "socket-tcp")]
-                    IpPayload::Tcp(tcp_repr) => {
-                        let mut tcp_packet =
-                            TcpPacket::new_unchecked(&mut tx_buf[..tcp_repr.buffer_len()]);
-                        tcp_repr.emit(
-                            &mut tcp_packet,
-                            &iphc_repr.src_addr.into(),
-                            &iphc_repr.dst_addr.into(),
-                            &self.caps.checksum,
-                        );
-                    }
-                    #[cfg(feature = "proto-ipv6")]
-                    IpPayload::Icmpv6(icmp_repr) => {
-                        let mut icmp_packet =
-                            Icmpv6Packet::new_unchecked(&mut tx_buf[..icmp_repr.buffer_len()]);
-                        icmp_repr.emit(
-                            &iphc_repr.src_addr.into(),
-                            &iphc_repr.dst_addr.into(),
-                            &mut icmp_packet,
-                            &self.caps.checksum,
-                        );
-                    }
-                    #[allow(unreachable_patterns)]
-                    _ => unreachable!(),
-                }
+                Self::ipv6_to_sixlowpan(&self.checksum_caps(), packet, &ieee_repr, tx_buf);
             });
         }
     }
 
+    fn ipv6_to_sixlowpan(
+        checksum_caps: &ChecksumCapabilities,
+        mut packet: Ipv6Packet,
+        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: &Ipv6Packet,
+        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,
@@ -526,3 +711,204 @@ impl InterfaceInner {
         );
     }
 }
+
+#[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 = Ipv6Packet {
+            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 = Ipv6Packet {
+            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);
+    }
+}

+ 9 - 6
src/iface/interface/tests/ipv4.rs

@@ -364,7 +364,7 @@ fn test_handle_valid_arp_request(#[case] medium: Medium) {
 
     let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
     let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
-    let local_hw_addr = EthernetAddress([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+    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 {
@@ -470,7 +470,7 @@ fn test_arp_flush_after_update_ip(#[case] medium: Medium) {
 
     let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
     let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
-    let local_hw_addr = EthernetAddress([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+    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 {
@@ -615,7 +615,10 @@ fn test_icmpv4_socket(#[case] medium: Medium) {
 #[case(Medium::Ethernet)]
 #[cfg(all(feature = "proto-igmp", feature = "medium-ethernet"))]
 fn test_handle_igmp(#[case] medium: Medium) {
-    fn recv_igmp(device: &mut Loopback, timestamp: Instant) -> Vec<(Ipv4Repr, IgmpRepr)> {
+    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)
@@ -649,7 +652,7 @@ fn test_handle_igmp(#[case] medium: Medium) {
     let (mut iface, mut sockets, mut device) = setup(medium);
 
     // Join multicast groups
-    let timestamp = Instant::now();
+    let timestamp = Instant::ZERO;
     for group in &groups {
         iface
             .join_multicast_group(&mut device, *group, timestamp)
@@ -671,7 +674,7 @@ fn test_handle_igmp(#[case] medium: Medium) {
     }
 
     // General query
-    let timestamp = Instant::now();
+    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,
@@ -692,7 +695,7 @@ fn test_handle_igmp(#[case] medium: Medium) {
     iface.socket_ingress(&mut device, &mut sockets);
 
     // Leave multicast groups
-    let timestamp = Instant::now();
+    let timestamp = Instant::ZERO;
     for group in &groups {
         iface
             .leave_multicast_group(&mut device, *group, timestamp)

+ 1 - 1
src/iface/interface/tests/ipv6.rs

@@ -556,7 +556,7 @@ fn test_handle_valid_ndisc_request(#[case] medium: Medium) {
 
     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([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+    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 {

+ 7 - 41
src/iface/interface/tests/mod.rs

@@ -8,12 +8,16 @@ 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, Loopback};
+use crate::phy::ChecksumCapabilities;
+#[cfg(feature = "alloc")]
+use crate::phy::Loopback;
 use crate::time::Instant;
 
 #[allow(unused)]
@@ -23,46 +27,8 @@ fn fill_slice(s: &mut [u8], val: u8) {
     }
 }
 
-fn setup<'a>(medium: Medium) -> (Interface, SocketSet<'a>, Loopback) {
-    let mut device = Loopback::new(medium);
-
-    let config = Config::new(match medium {
-        #[cfg(feature = "medium-ethernet")]
-        Medium::Ethernet => HardwareAddress::Ethernet(Default::default()),
-        #[cfg(feature = "medium-ip")]
-        Medium::Ip => HardwareAddress::Ip,
-        #[cfg(feature = "medium-ieee802154")]
-        Medium::Ieee802154 => HardwareAddress::Ieee802154(Default::default()),
-    });
-
-    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(127, 0, 0, 1), 8))
-                .unwrap();
-        });
-    }
-
-    #[cfg(feature = "proto-ipv6")]
-    {
-        iface.update_ip_addrs(|ip_addrs| {
-            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)
-}
-
 #[cfg(feature = "proto-igmp")]
-fn recv_all(device: &mut Loopback, timestamp: Instant) -> Vec<Vec<u8>> {
+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| {
@@ -88,7 +54,7 @@ impl TxToken for MockTxToken {
 
 #[test]
 #[should_panic(expected = "The hardware address does not match the medium of the interface.")]
-#[cfg(all(feature = "medium-ip", feature = "medium-ethernet"))]
+#[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);

+ 20 - 20
src/iface/interface/tests/sixlowpan.rs

@@ -230,10 +230,10 @@ fn test_echo_request_sixlowpan_128_bytes() {
     );
 
     assert_eq!(
-        device.queue[0],
+        device.queue.pop_front().unwrap(),
         &[
-            0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-            0x0, 0x0, 0x0, 0x0, 0xc0, 0xb0, 0x5, 0x4e, 0x7a, 0x11, 0x3a, 0x92, 0xfc, 0x48, 0xc2,
+            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,
@@ -247,10 +247,10 @@ fn test_echo_request_sixlowpan_128_bytes() {
     iface.poll(Instant::now(), &mut device, &mut sockets);
 
     assert_eq!(
-        device.queue[1],
+        device.queue.pop_front().unwrap(),
         &[
-            0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-            0x0, 0x0, 0x0, 0x0, 0xe0, 0xb0, 0x5, 0x4e, 0xf, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+            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,
@@ -384,25 +384,25 @@ In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo n
     iface.poll(Instant::now(), &mut device, &mut sockets);
 
     assert_eq!(
-        device.queue[0],
+        device.queue.pop_front().unwrap(),
         &[
-            0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-            0x0, 0x0, 0x0, 0x0, 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, 0xf6,
-            0x4d, 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,
-        ]
+            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[1],
+        device.queue.pop_front().unwrap(),
         &[
-            0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-            0x0, 0x0, 0x0, 0x0, 0xe0, 0xb4, 0x5, 0x4e, 0xf, 0x6f, 0x72, 0x74, 0x6f, 0x72, 0x2e,
+            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,

+ 27 - 5
src/iface/ip_packet.rs

@@ -153,14 +153,14 @@ pub(crate) struct Ipv4Packet<'p> {
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 #[cfg(feature = "proto-ipv6")]
 pub(crate) struct Ipv6Packet<'p> {
-    header: Ipv6Repr,
+    pub(crate) header: Ipv6Repr,
     #[cfg(feature = "proto-ipv6-hbh")]
-    hop_by_hop: Option<Ipv6HopByHopRepr<'p>>,
+    pub(crate) hop_by_hop: Option<Ipv6HopByHopRepr<'p>>,
     #[cfg(feature = "proto-ipv6-fragmentation")]
-    fragment: Option<Ipv6FragmentRepr>,
+    pub(crate) fragment: Option<Ipv6FragmentRepr>,
     #[cfg(feature = "proto-ipv6-routing")]
-    routing: Option<Ipv6RoutingRepr<'p>>,
-    payload: IpPayload<'p>,
+    pub(crate) routing: Option<Ipv6RoutingRepr<'p>>,
+    pub(crate) payload: IpPayload<'p>,
 }
 
 #[derive(Debug, PartialEq)]
@@ -182,6 +182,28 @@ pub(crate) enum IpPayload<'p> {
     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

+ 1 - 1
src/iface/neighbor.rs

@@ -146,7 +146,7 @@ impl Cache {
     }
 }
 
-#[cfg(any(feature = "medium-ethernet", feature = "medium-ip"))]
+#[cfg(feature = "medium-ethernet")]
 #[cfg(test)]
 mod test {
     use super::*;

+ 11 - 0
src/lib.rs

@@ -146,6 +146,7 @@ mod config {
     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))]
@@ -167,3 +168,13 @@ 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;

+ 62 - 36
src/socket/dhcpv4.rs

@@ -1048,28 +1048,34 @@ mod test {
     // =========================================================================================//
     // Tests
 
-    fn socket() -> TestSocket {
+    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: Context::mock(),
+            cx: iface.inner,
         }
     }
 
-    fn socket_different_port() -> TestSocket {
+    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: Context::mock(),
+            cx: iface.inner,
         }
     }
 
-    fn socket_bound() -> TestSocket {
-        let mut s = socket();
+    fn socket_bound(medium: Medium) -> TestSocket {
+        let mut s = socket(medium);
         s.state = ClientState::Renewing(RenewState {
             config: Config {
                 server: ServerInfo {
@@ -1090,9 +1096,11 @@ mod test {
         s
     }
 
-    #[test]
-    fn test_bind() {
-        let mut s = socket();
+    #[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);
@@ -1126,9 +1134,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_bind_different_ports() {
-        let mut s = socket_different_port();
+    #[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);
@@ -1162,9 +1172,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_discover_retransmit() {
-        let mut s = socket();
+    #[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, []);
@@ -1177,9 +1189,11 @@ mod test {
         recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
     }
 
-    #[test]
-    fn test_request_retransmit() {
-        let mut s = socket();
+    #[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()));
@@ -1203,9 +1217,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_request_timeout() {
-        let mut s = socket();
+    #[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()));
@@ -1224,9 +1240,11 @@ mod test {
         recv!(s, time 60_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
     }
 
-    #[test]
-    fn test_request_nak() {
-        let mut s = socket();
+    #[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()));
@@ -1235,9 +1253,11 @@ mod test {
         recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
     }
 
-    #[test]
-    fn test_renew() {
-        let mut s = socket_bound();
+    #[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);
@@ -1266,9 +1286,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_renew_rebind_retransmit() {
-        let mut s = socket_bound();
+    #[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
@@ -1306,9 +1328,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_renew_rebind_timeout() {
-        let mut s = socket_bound();
+    #[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
@@ -1334,9 +1358,11 @@ mod test {
         }
     }
 
-    #[test]
-    fn test_renew_nak() {
-        let mut s = socket_bound();
+    #[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));

+ 102 - 60
src/socket/icmp.rs

@@ -647,6 +647,10 @@ mod tests_common {
 
 #[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};
 
@@ -689,16 +693,17 @@ mod test_ipv4 {
         assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV4.into()), Ok(()));
     }
 
-    #[test]
-    fn test_send_dispatch() {
+    #[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 mut cx = Context::mock();
         let checksum = ChecksumCapabilities::default();
 
-        assert_eq!(
-            socket.dispatch(&mut cx, |_, _| unreachable!()),
-            Ok::<_, ()>(())
-        );
+        assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
 
         // This buffer is too long
         assert_eq!(
@@ -722,7 +727,7 @@ mod test_ipv4 {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, (ip_repr, icmp_repr)| {
+            socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
                 assert_eq!(ip_repr, LOCAL_IPV4_REPR);
                 assert_eq!(icmp_repr, ECHOV4_REPR.into());
                 Err(())
@@ -733,7 +738,7 @@ mod test_ipv4 {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, (ip_repr, icmp_repr)| {
+            socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
                 assert_eq!(ip_repr, LOCAL_IPV4_REPR);
                 assert_eq!(icmp_repr, ECHOV4_REPR.into());
                 Ok::<_, ()>(())
@@ -744,10 +749,14 @@ mod test_ipv4 {
         assert!(socket.can_send());
     }
 
-    #[test]
-    fn test_set_hop_limit_v4() {
+    #[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 mut cx = Context::mock();
         let checksum = ChecksumCapabilities::default();
 
         let mut bytes = [0xff; 24];
@@ -761,7 +770,7 @@ mod test_ipv4 {
             Ok(())
         );
         assert_eq!(
-            s.dispatch(&mut cx, |_, (ip_repr, _)| {
+            s.dispatch(cx, |_, (ip_repr, _)| {
                 assert_eq!(
                     ip_repr,
                     IpRepr::Ipv4(Ipv4Repr {
@@ -778,10 +787,14 @@ mod test_ipv4 {
         );
     }
 
-    #[test]
-    fn test_recv_process() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
 
         assert!(!socket.can_recv());
@@ -794,21 +807,25 @@ mod test_ipv4 {
         ECHOV4_REPR.emit(&mut packet, &checksum);
         let data = &*packet.into_inner();
 
-        assert!(socket.accepts(&mut cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into()));
-        socket.process(&mut cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into());
+        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(&mut cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into()));
-        socket.process(&mut cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR.into());
+        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());
     }
 
-    #[test]
-    fn test_accept_bad_id() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
 
         let checksum = ChecksumCapabilities::default();
@@ -823,13 +840,17 @@ mod test_ipv4 {
 
         // Ensure that a packet with an identifier that isn't the bound
         // ID is not accepted
-        assert!(!socket.accepts(&mut cx, &REMOTE_IPV4_REPR, &icmp_repr.into()));
+        assert!(!socket.accepts(cx, &REMOTE_IPV4_REPR, &icmp_repr.into()));
     }
 
-    #[test]
-    fn test_accepts_udp() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V4.into())), Ok(()));
 
         let checksum = ChecksumCapabilities::default();
@@ -856,7 +877,7 @@ mod test_ipv4 {
                 payload_len: 12,
                 hop_limit: 0x40,
             },
-            data: data,
+            data,
         };
         let ip_repr = IpRepr::Ipv4(Ipv4Repr {
             src_addr: REMOTE_IPV4,
@@ -870,8 +891,8 @@ mod test_ipv4 {
 
         // Ensure we can accept ICMP error response to the bound
         // UDP port
-        assert!(socket.accepts(&mut cx, &ip_repr, &icmp_repr.into()));
-        socket.process(&mut cx, &ip_repr, &icmp_repr.into());
+        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];
@@ -887,6 +908,10 @@ mod test_ipv4 {
 
 #[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};
@@ -931,16 +956,17 @@ mod test_ipv6 {
         assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV6.into()), Ok(()));
     }
 
-    #[test]
-    fn test_send_dispatch() {
+    #[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 mut cx = Context::mock();
         let checksum = ChecksumCapabilities::default();
 
-        assert_eq!(
-            socket.dispatch(&mut cx, |_, _| unreachable!()),
-            Ok::<_, ()>(())
-        );
+        assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
 
         // This buffer is too long
         assert_eq!(
@@ -969,7 +995,7 @@ mod test_ipv6 {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, (ip_repr, icmp_repr)| {
+            socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
                 assert_eq!(ip_repr, LOCAL_IPV6_REPR);
                 assert_eq!(icmp_repr, ECHOV6_REPR.into());
                 Err(())
@@ -980,7 +1006,7 @@ mod test_ipv6 {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, (ip_repr, icmp_repr)| {
+            socket.dispatch(cx, |_, (ip_repr, icmp_repr)| {
                 assert_eq!(ip_repr, LOCAL_IPV6_REPR);
                 assert_eq!(icmp_repr, ECHOV6_REPR.into());
                 Ok::<_, ()>(())
@@ -991,10 +1017,14 @@ mod test_ipv6 {
         assert!(socket.can_send());
     }
 
-    #[test]
-    fn test_set_hop_limit() {
+    #[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 mut cx = Context::mock();
         let checksum = ChecksumCapabilities::default();
 
         let mut bytes = vec![0xff; 24];
@@ -1013,7 +1043,7 @@ mod test_ipv6 {
             Ok(())
         );
         assert_eq!(
-            s.dispatch(&mut cx, |_, (ip_repr, _)| {
+            s.dispatch(cx, |_, (ip_repr, _)| {
                 assert_eq!(
                     ip_repr,
                     IpRepr::Ipv6(Ipv6Repr {
@@ -1030,10 +1060,14 @@ mod test_ipv6 {
         );
     }
 
-    #[test]
-    fn test_recv_process() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
 
         assert!(!socket.can_recv());
@@ -1051,21 +1085,25 @@ mod test_ipv6 {
         );
         let data = &*packet.into_inner();
 
-        assert!(socket.accepts(&mut cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
-        socket.process(&mut cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+        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(&mut cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into()));
-        socket.process(&mut cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR.into());
+        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());
     }
 
-    #[test]
-    fn test_accept_bad_id() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(()));
 
         let checksum = ChecksumCapabilities::default();
@@ -1085,13 +1123,17 @@ mod test_ipv6 {
 
         // Ensure that a packet with an identifier that isn't the bound
         // ID is not accepted
-        assert!(!socket.accepts(&mut cx, &REMOTE_IPV6_REPR, &icmp_repr.into()));
+        assert!(!socket.accepts(cx, &REMOTE_IPV6_REPR, &icmp_repr.into()));
     }
 
-    #[test]
-    fn test_accepts_udp() {
+    #[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));
-        let mut cx = Context::mock();
         assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V6.into())), Ok(()));
 
         let checksum = ChecksumCapabilities::default();
@@ -1118,7 +1160,7 @@ mod test_ipv6 {
                 payload_len: 12,
                 hop_limit: 0x40,
             },
-            data: data,
+            data,
         };
         let ip_repr = IpRepr::Ipv6(Ipv6Repr {
             src_addr: REMOTE_IPV6,
@@ -1132,8 +1174,8 @@ mod test_ipv6 {
 
         // Ensure we can accept ICMP error response to the bound
         // UDP port
-        assert!(socket.accepts(&mut cx, &ip_repr, &icmp_repr.into()));
-        socket.process(&mut cx, &ip_repr, &icmp_repr.into());
+        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];

+ 90 - 81
src/socket/raw.rs

@@ -445,6 +445,10 @@ impl<'a> Socket<'a> {
 
 #[cfg(test)]
 mod test {
+    use crate::phy::Medium;
+    use crate::tests::setup;
+    use rstest::*;
+
     use super::*;
     use crate::wire::IpRepr;
     #[cfg(feature = "proto-ipv4")]
@@ -539,10 +543,17 @@ mod test {
                     assert_eq!(socket.send_slice(&[0; 56][..]), Err(SendError::BufferFull));
                 }
 
-                #[test]
-                fn test_send_dispatch() {
+                #[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));
-                    let mut cx = Context::mock();
 
                     assert!(socket.can_send());
                     assert_eq!(
@@ -575,10 +586,17 @@ mod test {
                     assert!(socket.can_send());
                 }
 
-                #[test]
-                fn test_recv_truncated_slice() {
+                #[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));
-                    let mut cx = Context::mock();
 
                     assert!(socket.accepts(&$hdr));
                     socket.process(&mut cx, &$hdr, &$payload);
@@ -588,10 +606,17 @@ mod test {
                     assert_eq!(&slice, &$packet[..slice.len()]);
                 }
 
-                #[test]
-                fn test_recv_truncated_packet() {
+                #[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 cx = Context::mock();
 
                     let mut buffer = vec![0; 128];
                     buffer[..$packet.len()].copy_from_slice(&$packet[..]);
@@ -600,10 +625,17 @@ mod test {
                     socket.process(&mut cx, &$hdr, &buffer);
                 }
 
-                #[test]
-                fn test_peek_truncated_slice() {
+                #[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));
-                    let mut cx = Context::mock();
 
                     assert!(socket.accepts(&$hdr));
                     socket.process(&mut cx, &$hdr, &$payload);
@@ -637,159 +669,136 @@ mod test {
         ipv6_locals::PACKET_PAYLOAD
     );
 
-    #[test]
-    #[cfg(feature = "proto-ipv4")]
-    fn test_send_illegal() {
+    #[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 cx = Context::mock();
 
             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(&mut cx, |_, _| unreachable!()),
-                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(&mut cx, |_, _| unreachable!()),
-                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 cx = Context::mock();
 
             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(&mut cx, |_, _| unreachable!()),
-                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(&mut cx, |_, _| unreachable!()),
-                Ok::<_, ()>(())
-            );
+            assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(()));
         }
     }
 
-    #[test]
-    fn test_recv_process() {
+    #[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 cx = Context::mock();
 
             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(
-                &mut cx,
-                &ipv4_locals::HEADER_REPR,
-                &ipv4_locals::PACKET_PAYLOAD,
-            );
+            socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
             assert!(socket.can_recv());
 
             assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv4_locals::HEADER_REPR,
-                &ipv4_locals::PACKET_PAYLOAD,
-            );
+            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());
-            let mut cx = Context::mock();
 
             assert_eq!(socket.recv(), Err(RecvError::Exhausted));
             assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv6_locals::HEADER_REPR,
-                &ipv6_locals::PACKET_PAYLOAD,
-            );
+            socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
             assert!(socket.can_recv());
 
             assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv6_locals::HEADER_REPR,
-                &ipv6_locals::PACKET_PAYLOAD,
-            );
+            socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
             assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..]));
             assert!(!socket.can_recv());
         }
     }
 
-    #[test]
-    fn test_peek_process() {
+    #[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 cx = Context::mock();
 
             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(
-                &mut cx,
-                &ipv4_locals::HEADER_REPR,
-                &ipv4_locals::PACKET_PAYLOAD,
-            );
+            socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD);
 
             assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv4_locals::HEADER_REPR,
-                &ipv4_locals::PACKET_PAYLOAD,
-            );
+            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));
-            let mut cx = Context::mock();
 
             assert_eq!(socket.peek(), Err(RecvError::Exhausted));
             assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv6_locals::HEADER_REPR,
-                &ipv6_locals::PACKET_PAYLOAD,
-            );
+            socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD);
 
             assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
-            socket.process(
-                &mut cx,
-                &ipv6_locals::HEADER_REPR,
-                &ipv6_locals::PACKET_PAYLOAD,
-            );
+            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));

+ 10 - 3
src/socket/tcp.rs

@@ -2378,7 +2378,10 @@ impl<'a> fmt::Write for Socket<'a> {
     }
 }
 
-#[cfg(test)]
+// 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;
@@ -2624,12 +2627,16 @@ mod test {
     }
 
     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);
-        let cx = Context::mock();
-        TestSocket { socket, cx }
+        TestSocket {
+            socket,
+            cx: iface.inner,
+        }
     }
 
     fn socket_syn_received_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket {

+ 118 - 44
src/socket/udp.rs

@@ -560,6 +560,10 @@ 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)
@@ -702,16 +706,23 @@ mod test {
         assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(()));
     }
 
-    #[test]
-    fn test_send_dispatch() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_END), Ok(()));
 
         assert!(socket.can_send());
         assert_eq!(
-            socket.dispatch(&mut cx, |_, _, _| unreachable!()),
+            socket.dispatch(cx, |_, _, _| unreachable!()),
             Ok::<_, ()>(())
         );
 
@@ -723,7 +734,7 @@ mod test {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, _, (ip_repr, udp_repr, payload)| {
+            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);
@@ -734,7 +745,7 @@ mod test {
         assert!(!socket.can_send());
 
         assert_eq!(
-            socket.dispatch(&mut cx, |_, _, (ip_repr, udp_repr, payload)| {
+            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);
@@ -745,19 +756,27 @@ mod test {
         assert!(socket.can_send());
     }
 
-    #[test]
-    fn test_recv_process() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
         assert!(!socket.can_recv());
         assert_eq!(socket.recv(), Err(RecvError::Exhausted));
 
-        assert!(socket.accepts(&mut cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+        assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
         socket.process(
-            &mut cx,
+            cx,
             PacketMeta::default(),
             &REMOTE_IP_REPR,
             &REMOTE_UDP_REPR,
@@ -765,9 +784,9 @@ mod test {
         );
         assert!(socket.can_recv());
 
-        assert!(socket.accepts(&mut cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+        assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
         socket.process(
-            &mut cx,
+            cx,
             PacketMeta::default(),
             &REMOTE_IP_REPR,
             &REMOTE_UDP_REPR,
@@ -778,17 +797,25 @@ mod test {
         assert!(!socket.can_recv());
     }
 
-    #[test]
-    fn test_peek_process() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
         assert_eq!(socket.peek(), Err(RecvError::Exhausted));
 
         socket.process(
-            &mut cx,
+            cx,
             PacketMeta::default(),
             &REMOTE_IP_REPR,
             &REMOTE_UDP_REPR,
@@ -799,16 +826,24 @@ mod test {
         assert_eq!(socket.peek(), Err(RecvError::Exhausted));
     }
 
-    #[test]
-    fn test_recv_truncated_slice() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
-        assert!(socket.accepts(&mut cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
+        assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR));
         socket.process(
-            &mut cx,
+            cx,
             PacketMeta::default(),
             &REMOTE_IP_REPR,
             &REMOTE_UDP_REPR,
@@ -823,15 +858,23 @@ mod test {
         assert_eq!(&slice, b"abcd");
     }
 
-    #[test]
-    fn test_peek_truncated_slice() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
         socket.process(
-            &mut cx,
+            cx,
             PacketMeta::default(),
             &REMOTE_IP_REPR,
             &REMOTE_UDP_REPR,
@@ -852,17 +895,25 @@ mod test {
         assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Exhausted));
     }
 
-    #[test]
-    fn test_set_hop_limit() {
+    #[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));
-        let mut cx = Context::mock();
 
         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(&mut cx, |_, _, (ip_repr, _, _)| {
+            s.dispatch(cx, |_, _, (ip_repr, _, _)| {
                 assert_eq!(
                     ip_repr,
                     IpReprIpvX(IpvXRepr {
@@ -879,30 +930,45 @@ mod test {
         );
     }
 
-    #[test]
-    fn test_doesnt_accept_wrong_port() {
+    #[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));
-        let mut cx = Context::mock();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
         let mut udp_repr = REMOTE_UDP_REPR;
-        assert!(socket.accepts(&mut cx, &REMOTE_IP_REPR, &udp_repr));
+        assert!(socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr));
         udp_repr.dst_port += 1;
-        assert!(!socket.accepts(&mut cx, &REMOTE_IP_REPR, &udp_repr));
+        assert!(!socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr));
     }
 
-    #[test]
-    fn test_doesnt_accept_wrong_ip() {
-        let mut cx = Context::mock();
+    #[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(&mut cx, &BAD_IP_REPR, &REMOTE_UDP_REPR));
+        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(&mut cx, &BAD_IP_REPR, &REMOTE_UDP_REPR));
+        assert!(!ip_bound_socket.accepts(cx, &BAD_IP_REPR, &REMOTE_UDP_REPR));
     }
 
     #[test]
@@ -919,12 +985,20 @@ mod test {
         assert_eq!(socket.send_slice(&too_large[..16 * 4], REMOTE_END), Ok(()));
     }
 
-    #[test]
-    fn test_process_empty_payload() {
+    #[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 cx = Context::mock();
+
+        let (mut iface, _, _) = setup(medium);
+        let cx = iface.context();
 
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
@@ -932,7 +1006,7 @@ mod test {
             src_port: REMOTE_PORT,
             dst_port: LOCAL_PORT,
         };
-        socket.process(&mut cx, PacketMeta::default(), &REMOTE_IP_REPR, &repr, &[]);
+        socket.process(cx, PacketMeta::default(), &REMOTE_IP_REPR, &repr, &[]);
         assert_eq!(socket.recv(), Ok((&[][..], REMOTE_END.into())));
     }
 

+ 148 - 0
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
+    }
+}

+ 176 - 0
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!("eror 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[..]);
+    }
+}

+ 7 - 9
src/wire/mod.rs

@@ -105,6 +105,8 @@ 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;
@@ -162,9 +164,9 @@ pub use self::sixlowpan::{
     frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr},
     iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr},
     nhc::{
-        ExtHeaderPacket as SixlowpanExtHeaderPacket, ExtHeaderRepr as SixlowpanExtHeaderRepr,
-        NhcPacket as SixlowpanNhcPacket, UdpNhcPacket as SixlowpanUdpNhcPacket,
-        UdpNhcRepr as SixlowpanUdpNhcRepr,
+        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,
 };
@@ -204,14 +206,10 @@ pub use self::ipv6option::{
 pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr};
 
 #[cfg(feature = "proto-ipv6")]
-/// A read/write wrapper around an IPv6 Hop-By-Hop header.
-pub type Ipv6HopByHopHeader<T> = Ipv6ExtHeader<T>;
-#[cfg(feature = "proto-ipv6")]
-/// A high-level representation of an IPv6 Hop-By-Hop heade.
-pub type Ipv6HopByHopRepr<'a> = Ipv6ExtHeaderRepr<'a>;
+pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
 
 #[cfg(feature = "proto-ipv6")]
-pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
+pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
 
 #[cfg(feature = "proto-ipv6")]
 pub use self::ipv6routing::{

+ 40 - 5
src/wire/rpl.rs

@@ -179,7 +179,7 @@ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
                 return Err(Error)
             }
             RplControlMessage::DestinationAdvertisementObjectAck
-                if self.dao_dodag_id_present() && len < field::DAO_ACK_DODAG_ID.end =>
+                if self.dao_ack_dodag_id_present() && len < field::DAO_ACK_DODAG_ID.end =>
             {
                 return Err(Error)
             }
@@ -205,7 +205,9 @@ impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> {
                 &buffer[field::DAO_DODAG_ID.end..]
             }
             RplControlMessage::DestinationAdvertisementObject => &buffer[field::DAO_SEQUENCE + 1..],
-            RplControlMessage::DestinationAdvertisementObjectAck if self.dao_dodag_id_present() => {
+            RplControlMessage::DestinationAdvertisementObjectAck
+                if self.dao_ack_dodag_id_present() =>
+            {
                 &buffer[field::DAO_ACK_DODAG_ID.end..]
             }
             RplControlMessage::DestinationAdvertisementObjectAck => {
@@ -247,7 +249,7 @@ impl<'p, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'p mut T> {
                 }
             }
             RplControlMessage::DestinationAdvertisementObjectAck => {
-                if self.dao_dodag_id_present() {
+                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..]
@@ -539,7 +541,7 @@ 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: 6, mask: 0b1)
+        get!(self.buffer, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1)
     }
 
     /// Return the DODAG sequence number.
@@ -572,7 +574,7 @@ 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: 6, mask: 0b1)
+        set!(self.buffer, value, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1)
     }
 
     /// Set the DODAG sequence number.
@@ -2682,5 +2684,38 @@ mod tests {
         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[..]);
     }
 }

+ 0 - 2463
src/wire/sixlowpan.rs

@@ -1,2463 +0,0 @@
-//! 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;
-
-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)
-        }
-    }
-}
-
-pub mod frag {
-    //! 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);
-                }
-            }
-        }
-    }
-}
-
-#[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),
-        }
-    }
-}
-
-pub mod iphc {
-    //! 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 destinatino 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
-                )))
-            );
-        }
-    }
-}
-
-pub mod nhc {
-    //! 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 Extenstion 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 = 1;
-            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!(),
-            }
-        }
-
-        /// 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.
-                let start = 1;
-
-                let data = self.buffer.as_ref();
-                let nh = data[start];
-                NextHeader::Uncompressed(IpProtocol::from(nh))
-            }
-        }
-
-        /// 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();
-            &self.buffer.as_ref()[start..]
-        }
-    }
-
-    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();
-            &mut self.buffer.as_mut()[start..]
-        }
-
-        /// 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 {
-        ext_header_id: ExtHeaderId,
-        next_header: NextHeader,
-        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.payload().len() as u8,
-            })
-        }
-
-        /// 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 representaiton 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 formath:
-    /// ```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]),
-        ) {
-            packet.set_dispatch_field();
-            packet.set_ports(self.src_port, self.dst_port);
-            emit_payload(packet.payload_mut());
-
-            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_nhc_fields() {
-            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.dispatch_field(), DISPATCH_EXT_HEADER);
-            assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
-
-            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[..])
-            });
-
-            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!");
-        }
-    }
-}
-
-#[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);
-    }
-}

+ 275 - 0
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);
+            }
+        }
+    }
+}

+ 948 - 0
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 destinatino 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
+            )))
+        );
+    }
+}

+ 371 - 0
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);
+    }
+}

+ 875 - 0
src/wire/sixlowpan/nhc.rs

@@ -0,0 +1,875 @@
+//! 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 Extenstion 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 = 1;
+        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.
+            let start = 1;
+
+            let data = self.buffer.as_ref();
+            let nh = data[start];
+            NextHeader::Uncompressed(IpProtocol::from(nh))
+        }
+    }
+
+    /// 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 representaiton 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 formath:
+/// ```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_nhc_fields() {
+        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.dispatch_field(), DISPATCH_EXT_HEADER);
+        assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
+
+        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!");
+    }
+}

+ 1 - 1
src/wire/tcp.rs

@@ -553,7 +553,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
         NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
     }
 
-    /// Return the window size field.
+    /// Set the window size field.
     #[inline]
     pub fn set_window_len(&mut self, value: u16) {
         let data = self.buffer.as_mut();