summaryrefslogtreecommitdiff
path: root/src/iface/interface/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/iface/interface/tests')
-rw-r--r--src/iface/interface/tests/ipv4.rs968
-rw-r--r--src/iface/interface/tests/ipv6.rs988
-rw-r--r--src/iface/interface/tests/mod.rs235
-rw-r--r--src/iface/interface/tests/sixlowpan.rs434
4 files changed, 2625 insertions, 0 deletions
diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs
new file mode 100644
index 0000000..d685f37
--- /dev/null
+++ b/src/iface/interface/tests/ipv4.rs
@@ -0,0 +1,968 @@
+use super::*;
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_no_icmp_no_unicast(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ // Unknown Ipv4 Protocol
+ //
+ // Because the destination is the broadcast address
+ // this should not trigger and Destination Unreachable
+ // response. See RFC 1122 ยง 3.2.2.
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 54];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // Ensure that the unknown protocol frame does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_icmp_error_no_payload(#[case] medium: Medium) {
+ static NO_BYTES: [u8; 0] = [];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ // Unknown Ipv4 Protocol with no payload
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 34];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // The expected Destination Unreachable response due to the
+ // unknown protocol
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::ProtoUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(12),
+ payload_len: 0,
+ hop_limit: 64,
+ },
+ data: &NO_BYTES,
+ };
+
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_repr)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_local_subnet_broadcasts(#[case] medium: Medium) {
+ let (mut iface, _, _device) = setup(medium);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 23]), 24));
+ });
+ });
+
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 254])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 16));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 255])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 8));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 255])));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "medium-ip", feature = "socket-udp"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "medium-ethernet", feature = "socket-udp"))]
+fn test_icmp_error_port_unreachable(#[case] medium: Medium) {
+ static UDP_PAYLOAD: [u8; 12] = [
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x6c, 0x64, 0x21,
+ ];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut udp_bytes_unicast = vec![0u8; 20];
+ let mut udp_bytes_broadcast = vec![0u8; 20];
+ let mut packet_unicast = UdpPacket::new_unchecked(&mut udp_bytes_unicast);
+ let mut packet_broadcast = UdpPacket::new_unchecked(&mut udp_bytes_broadcast);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_unicast,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ let data = packet_unicast.into_inner();
+
+ // The expected Destination Unreachable ICMPv4 error response due
+ // to no sockets listening on the destination port.
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ },
+ data,
+ };
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ data
+ ),
+ Some(expected_repr)
+ );
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_broadcast,
+ &ip_repr.src_addr(),
+ &IpAddress::Ipv4(Ipv4Address::BROADCAST),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ // Ensure that the port unreachable error does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address and no socket is bound to the port.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ packet_broadcast.into_inner(),
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_ipv4_broadcast(#[case] medium: Medium) {
+ use crate::wire::{Icmpv4Packet, Icmpv4Repr};
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let our_ipv4_addr = iface.ipv4_addr().unwrap();
+ let src_ipv4_addr = Ipv4Address([127, 0, 0, 2]);
+
+ // ICMPv4 echo request
+ let icmpv4_data: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+ let icmpv4_repr = Icmpv4Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+
+ // Send to IPv4 broadcast address
+ let ipv4_repr = Ipv4Repr {
+ src_addr: src_ipv4_addr,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: icmpv4_repr.buffer_len(),
+ };
+
+ // Emit to ip frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + icmpv4_repr.buffer_len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes[..]),
+ &ChecksumCapabilities::default(),
+ );
+ icmpv4_repr.emit(
+ &mut Icmpv4Packet::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ // Expected ICMPv4 echo reply
+ let expected_icmpv4_repr = Icmpv4Repr::EchoReply {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+ let expected_ipv4_repr = Ipv4Repr {
+ src_addr: our_ipv4_addr,
+ dst_addr: src_ipv4_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmpv4_repr.buffer_len(),
+ };
+ let expected_packet =
+ Packet::new_ipv4(expected_ipv4_repr, IpPayload::Icmpv4(expected_icmpv4_repr));
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_packet)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_valid_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: local_ip_addr,
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_other_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x03]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for someone else does not trigger an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Ensure the address of the requester was NOT entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(Ipv4Address([0x7f, 0x00, 0x00, 0x01])),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Err(DispatchError::NeighborPending)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_arp_flush_after_update_ip(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ {
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+ }
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+
+ // Update IP addrs to trigger ARP cache flush
+ let local_ip_addr_new = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(local_ip_addr_new, 24));
+ });
+ });
+
+ // ARP cache flush after address change
+ assert!(!iface.inner.has_neighbor(&IpAddress::Ipv4(remote_ip_addr)));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ethernet"))]
+fn test_icmpv4_socket(#[case] medium: Medium) {
+ use crate::wire::Icmpv4Packet;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+ let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+
+ let icmpv4_socket = icmp::Socket::new(rx_buffer, tx_buffer);
+
+ let socket_handle = sockets.add(icmpv4_socket);
+
+ let ident = 0x1234;
+ let seq_no = 0x5432;
+ let echo_data = &[0xff; 16];
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ // Bind to the ID 0x1234
+ assert_eq!(socket.bind(icmp::Endpoint::Ident(ident)), Ok(()));
+
+ // Ensure the ident we bound to and the ident of the packet are the same.
+ let mut bytes = [0xff; 24];
+ let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]);
+ let echo_repr = Icmpv4Repr::EchoRequest {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ echo_repr.emit(&mut packet, &ChecksumCapabilities::default());
+ let icmp_data = &*packet.into_inner();
+
+ let ipv4_repr = Ipv4Repr {
+ src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02),
+ dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01),
+ next_header: IpProtocol::Icmp,
+ payload_len: 24,
+ hop_limit: 64,
+ };
+ let ip_repr = IpRepr::Ipv4(ipv4_repr);
+
+ // Open a socket and ensure the packet is handled due to the listening
+ // socket.
+ assert!(!sockets.get_mut::<icmp::Socket>(socket_handle).can_recv());
+
+ // Confirm we still get EchoReply from `smoltcp` even with the ICMP socket listening
+ let echo_reply = Icmpv4Repr::EchoReply {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ let ipv4_reply = Ipv4Repr {
+ src_addr: ipv4_repr.dst_addr,
+ dst_addr: ipv4_repr.src_addr,
+ ..ipv4_repr
+ };
+ assert_eq!(
+ iface.inner.process_icmpv4(&mut sockets, ip_repr, icmp_data),
+ Some(Packet::new_ipv4(ipv4_reply, IpPayload::Icmpv4(echo_reply)))
+ );
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ icmp_data,
+ IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02))
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ethernet"))]
+fn test_handle_igmp(#[case] medium: Medium) {
+ fn recv_igmp(
+ device: &mut crate::tests::TestingDevice,
+ timestamp: Instant,
+ ) -> Vec<(Ipv4Repr, IgmpRepr)> {
+ let caps = device.capabilities();
+ let checksum_caps = &caps.checksum;
+ recv_all(device, timestamp)
+ .iter()
+ .filter_map(|frame| {
+ let ipv4_packet = match caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ let eth_frame = EthernetFrame::new_checked(frame).ok()?;
+ Ipv4Packet::new_checked(eth_frame.payload()).ok()?
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => Ipv4Packet::new_checked(&frame[..]).ok()?,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+ let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, checksum_caps).ok()?;
+ let ip_payload = ipv4_packet.payload();
+ let igmp_packet = IgmpPacket::new_checked(ip_payload).ok()?;
+ let igmp_repr = IgmpRepr::parse(&igmp_packet).ok()?;
+ Some((ipv4_repr, igmp_repr))
+ })
+ .collect::<Vec<_>>()
+ }
+
+ let groups = [
+ Ipv4Address::new(224, 0, 0, 22),
+ Ipv4Address::new(224, 0, 0, 56),
+ ];
+
+ let (mut iface, mut sockets, mut device) = setup(medium);
+
+ // Join multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .join_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let reports = recv_igmp(&mut device, timestamp);
+ assert_eq!(reports.len(), 2);
+ for (i, group_addr) in groups.iter().enumerate() {
+ assert_eq!(reports[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(reports[i].0.dst_addr, *group_addr);
+ assert_eq!(
+ reports[i].1,
+ IgmpRepr::MembershipReport {
+ group_addr: *group_addr,
+ version: IgmpVersion::Version2,
+ }
+ );
+ }
+
+ // General query
+ let timestamp = Instant::ZERO;
+ const GENERAL_QUERY_BYTES: &[u8] = &[
+ 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63,
+ 0x04, 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ ];
+ {
+ // Transmit GENERAL_QUERY_BYTES into loopback
+ let tx_token = device.transmit(timestamp).unwrap();
+ tx_token.consume(GENERAL_QUERY_BYTES.len(), |buffer| {
+ buffer.copy_from_slice(GENERAL_QUERY_BYTES);
+ });
+ }
+ // Trigger processing until all packets received through the
+ // loopback have been processed, including responses to
+ // GENERAL_QUERY_BYTES. Therefore `recv_all()` would return 0
+ // pkts that could be checked.
+ iface.socket_ingress(&mut device, &mut sockets);
+
+ // Leave multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .leave_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let leaves = recv_igmp(&mut device, timestamp);
+ assert_eq!(leaves.len(), 2);
+ for (i, group_addr) in groups.iter().cloned().enumerate() {
+ assert_eq!(leaves[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(leaves[i].0.dst_addr, Ipv4Address::MULTICAST_ALL_ROUTERS);
+ assert_eq!(leaves[i].1, IgmpRepr::LeaveGroup { group_addr });
+ }
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))]
+fn test_raw_socket_no_reply(#[case] medium: Medium) {
+ use crate::wire::{IpVersion, UdpPacket, UdpRepr};
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let packets = 1;
+ let rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ const PAYLOAD_LEN: usize = 10;
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + PAYLOAD_LEN,
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(
+ feature = "socket-raw",
+ feature = "socket-udp",
+ feature = "medium-ethernet"
+))]
+fn test_raw_socket_with_udp_socket(#[case] medium: Medium) {
+ use crate::wire::{IpEndpoint, IpVersion, UdpPacket, UdpRepr};
+
+ static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_socket_handle = sockets.add(udp_socket);
+
+ // Bind the socket to port 68
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert_eq!(socket.bind(68), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+
+ let packets = 1;
+ let raw_rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let raw_tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Udp,
+ raw_rx_buffer,
+ raw_tx_buffer,
+ );
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Make sure the UDP socket can still receive in presence of a Raw socket that handles UDP
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ &UDP_PAYLOAD[..],
+ IpEndpoint::new(src_addr.into(), 67).into()
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))]
+fn test_icmp_reply_size(#[case] medium: Medium) {
+ use crate::wire::IPV4_MIN_MTU as MIN_MTU;
+ const MAX_PAYLOAD_LEN: usize = 528;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let src_addr = Ipv4Address([192, 168, 1, 1]);
+ let dst_addr = Ipv4Address([192, 168, 1, 2]);
+
+ // UDP packet that if not tructated will cause a icmp port unreachable reply
+ // to exceed the minimum mtu bytes in length.
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ MAX_PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+
+ let ip_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN,
+ };
+ let payload = packet.into_inner();
+
+ let expected_icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: ip_repr,
+ data: &payload[..MAX_PAYLOAD_LEN],
+ };
+
+ let expected_ip_repr = Ipv4Repr {
+ src_addr: dst_addr,
+ dst_addr: src_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmp_repr.buffer_len(),
+ };
+
+ assert_eq!(
+ expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(),
+ MIN_MTU
+ );
+
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr.into(),
+ udp_repr,
+ false,
+ &vec![0x2a; MAX_PAYLOAD_LEN],
+ payload,
+ ),
+ Some(Packet::new_ipv4(
+ expected_ip_repr,
+ IpPayload::Icmpv4(expected_icmp_repr)
+ ))
+ );
+}
diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs
new file mode 100644
index 0000000..9c5f099
--- /dev/null
+++ b/src/iface/interface/tests/ipv6.rs
@@ -0,0 +1,988 @@
+use super::*;
+
+fn parse_ipv6(data: &[u8]) -> crate::wire::Result<Packet<'_>> {
+ let ipv6_header = Ipv6Packet::new_checked(data)?;
+ let ipv6 = Ipv6Repr::parse(&ipv6_header)?;
+
+ match ipv6.next_header {
+ IpProtocol::HopByHop => todo!(),
+ IpProtocol::Icmp => todo!(),
+ IpProtocol::Igmp => todo!(),
+ IpProtocol::Tcp => todo!(),
+ IpProtocol::Udp => todo!(),
+ IpProtocol::Ipv6Route => todo!(),
+ IpProtocol::Ipv6Frag => todo!(),
+ IpProtocol::IpSecEsp => todo!(),
+ IpProtocol::IpSecAh => todo!(),
+ IpProtocol::Icmpv6 => {
+ let icmp = Icmpv6Repr::parse(
+ &ipv6.src_addr.into(),
+ &ipv6.dst_addr.into(),
+ &Icmpv6Packet::new_checked(ipv6_header.payload())?,
+ &Default::default(),
+ )?;
+ Ok(Packet::new_ipv6(ipv6, IpPayload::Icmpv6(icmp)))
+ }
+ IpProtocol::Ipv6NoNxt => todo!(),
+ IpProtocol::Ipv6Opts => todo!(),
+ IpProtocol::Unknown(_) => todo!(),
+ }
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn multicast_source_address(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn hop_by_hop_skip_with_icmp(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (skipped)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0xf, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+fn hop_by_hop_discard_param_problem(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard + ParamProblem)
+ // - ICMP echo request
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 75,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedOption,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::HopByHop,
+ payload_len: 27,
+ hop_limit: 64,
+ },
+ data: &[
+ 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
+ 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) {
+ // The following contains:
+ // - IPv6 header
+ // - Hop-by-hop, with options:
+ // - PADN (skipped)
+ // - Unknown option (discard (0b11) + ParamProblem)
+ // - ICMP echo request
+ //
+ // In this case, even if the destination address is a multicast address, an ICMPv6 ParamProblem
+ // should be transmitted.
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
+ 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 75,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedOption,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::HopByHop,
+ payload_len: 27,
+ hop_limit: 64,
+ },
+ data: &[
+ 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
+ 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn imcp_empty_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x8, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x84, 0x3c, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 8,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoRequest {
+ ident: 0,
+ seq_no: 0,
+ data: b"",
+ })
+ ))
+ );
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 8,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 0,
+ seq_no: 0,
+ data: b"",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72,
+ 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoRequest {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ })
+ ))
+ );
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 42,
+ seq_no: 420,
+ data: b"Lorem Ipsum",
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_reply_as_input(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x81, 0x0, 0x2d, 0x56, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x6f, 0x72, 0x65,
+ 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 19,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 0,
+ seq_no: 0,
+ data: b"Lorem Ipsum",
+ })
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 48,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ },
+ data: &[],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ip(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn unknown_proto(#[case] medium: Medium) {
+ // Since the destination address is multicast, we should answer with an ICMPv6 message.
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 48,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
+ reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
+ pointer: 40,
+ header: Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 64,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ },
+ data: &[],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+}
+
+#[rstest]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn ndsic_neighbor_advertisement_ethernet(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x9f, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 32,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 1])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::Found(HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
+ 0, 0, 0, 0, 0, 1
+ ]))),
+ );
+}
+
+#[rstest]
+#[case::ethernet(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn ndsic_neighbor_advertisement_ethernet_multicast_addr(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0xa0, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 32,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ ])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::NotFound,
+ );
+}
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn ndsic_neighbor_advertisement_ieee802154(#[case] medium: Medium) {
+ let data = [
+ 0x60, 0x0, 0x0, 0x0, 0x0, 0x28, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x96, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
+
+ assert_eq!(
+ parse_ipv6(&data),
+ Ok(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
+ dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]),
+ hop_limit: 255,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 40,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]),
+ lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 0, 0, 1])),
+ }))
+ ))
+ );
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ipv6(
+ &mut sockets,
+ PacketMeta::default(),
+ &Ipv6Packet::new_checked(&data[..]).unwrap()
+ ),
+ response
+ );
+
+ assert_eq!(
+ iface.inner.neighbor_cache.lookup(
+ &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])),
+ iface.inner.now,
+ ),
+ NeighborAnswer::Found(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
+ &[0, 0, 0, 0, 0, 0, 0, 1]
+ ))),
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_valid_ndisc_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 86];
+
+ let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1);
+ let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
+ target_addr: local_ip_addr,
+ lladdr: Some(remote_hw_addr.into()),
+ });
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: remote_ip_addr,
+ dst_addr: local_ip_addr.solicited_node(),
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 0xff,
+ payload_len: solicit.buffer_len(),
+ });
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00]));
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Ipv6);
+ ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
+ solicit.emit(
+ &remote_ip_addr.into(),
+ &local_ip_addr.solicited_node().into(),
+ &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]),
+ &ChecksumCapabilities::default(),
+ );
+
+ let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+ flags: NdiscNeighborFlags::SOLICITED,
+ target_addr: local_ip_addr,
+ lladdr: Some(local_hw_addr.into()),
+ });
+
+ let ipv6_expected = Ipv6Repr {
+ src_addr: local_ip_addr,
+ dst_addr: remote_ip_addr,
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 0xff,
+ payload_len: icmpv6_expected.buffer_len(),
+ };
+
+ // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Ip(Packet::new_ipv6(
+ ipv6_expected,
+ IpPayload::Icmpv6(icmpv6_expected)
+ )))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv6(local_ip_addr),
+ &IpAddress::Ipv6(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+#[case(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn test_solicited_node_addrs(#[case] medium: Medium) {
+ let (mut iface, _, _) = setup(medium);
+ let mut new_addrs = heapless::Vec::<IpCidr, IFACE_MAX_ADDR_COUNT>::new();
+ new_addrs
+ .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 1, 2, 0, 2), 64))
+ .unwrap();
+ new_addrs
+ .push(IpCidr::new(
+ IpAddress::v6(0xfe80, 0, 0, 0, 3, 4, 0, 0xffff),
+ 64,
+ ))
+ .unwrap();
+ iface.update_ip_addrs(|addrs| {
+ new_addrs.extend(addrs.to_vec());
+ *addrs = new_addrs;
+ });
+ assert!(iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
+ assert!(iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
+ assert!(!iface
+ .inner
+ .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003)));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))]
+#[case(Medium::Ieee802154)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ieee802154"))]
+fn test_icmp_reply_size(#[case] medium: Medium) {
+ use crate::wire::Icmpv6DstUnreachable;
+ use crate::wire::IPV6_MIN_MTU as MIN_MTU;
+ const MAX_PAYLOAD_LEN: usize = 1192;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let src_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ let dst_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
+
+ // UDP packet that if not tructated will cause a icmp port unreachable reply
+ // to exceed the minimum mtu bytes in length.
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ MAX_PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+
+ let ip_repr = Ipv6Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN,
+ };
+ let payload = packet.into_inner();
+
+ let expected_icmp_repr = Icmpv6Repr::DstUnreachable {
+ reason: Icmpv6DstUnreachable::PortUnreachable,
+ header: ip_repr,
+ data: &payload[..MAX_PAYLOAD_LEN],
+ };
+
+ let expected_ip_repr = Ipv6Repr {
+ src_addr: dst_addr,
+ dst_addr: src_addr,
+ next_header: IpProtocol::Icmpv6,
+ hop_limit: 64,
+ payload_len: expected_icmp_repr.buffer_len(),
+ };
+
+ assert_eq!(
+ expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(),
+ MIN_MTU
+ );
+
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr.into(),
+ udp_repr,
+ false,
+ &vec![0x2a; MAX_PAYLOAD_LEN],
+ payload,
+ ),
+ Some(Packet::new_ipv6(
+ expected_ip_repr,
+ IpPayload::Icmpv6(expected_icmp_repr)
+ ))
+ );
+}
+
+#[cfg(feature = "medium-ip")]
+#[test]
+fn get_source_address() {
+ let (mut iface, _, _) = setup(Medium::Ip);
+
+ const OWN_LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ const OWN_UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 2);
+ const OWN_UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 2);
+ const OWN_GLOBAL_UNICAST_ADDR1: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 1);
+
+ // List of addresses of the interface:
+ // fe80::1/64
+ // fd00::201:1:1:1:2/64
+ // fd01::201:1:1:1:2/64
+ // 2001:db8:3::1/64
+ // ::1/128
+ // ::/128
+ iface.update_ip_addrs(|addrs| {
+ addrs.clear();
+
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_LINK_LOCAL_ADDR, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 64)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_GLOBAL_UNICAST_ADDR1, 64)))
+ .unwrap();
+
+ // These should never be used:
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128)))
+ .unwrap();
+ addrs
+ .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128)))
+ .unwrap();
+ });
+
+ // List of addresses we test:
+ // fe80::42 -> fe80::1
+ // fd00::201:1:1:1:1 -> fd00::201:1:1:1:2
+ // fd01::201:1:1:1:1 -> fd01::201:1:1:1:2
+ // fd02::201:1:1:1:1 -> fd00::201:1:1:1:2 (because first added in the list)
+ // ff02::1 -> fe80::1 (same scope)
+ // 2001:db8:3::2 -> 2001:db8:3::1
+ // 2001:db9:3::2 -> 2001:db8:3::1
+ const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42);
+ const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
+ const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1);
+ const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1);
+ const GLOBAL_UNICAST_ADDR1: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2);
+ const GLOBAL_UNICAST_ADDR2: Ipv6Address =
+ Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2);
+
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2),
+ Some(OWN_UNIQUE_LOCAL_ADDR2)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface
+ .inner
+ .get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+ assert_eq!(
+ iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+
+ assert_eq!(
+ iface.get_source_address_ipv6(&LINK_LOCAL_ADDR),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2),
+ Some(OWN_UNIQUE_LOCAL_ADDR2)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3),
+ Some(OWN_UNIQUE_LOCAL_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES),
+ Some(OWN_LINK_LOCAL_ADDR)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+ assert_eq!(
+ iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2),
+ Some(OWN_GLOBAL_UNICAST_ADDR1)
+ );
+}
diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs
new file mode 100644
index 0000000..b4b4416
--- /dev/null
+++ b/src/iface/interface/tests/mod.rs
@@ -0,0 +1,235 @@
+#[cfg(feature = "proto-ipv4")]
+mod ipv4;
+#[cfg(feature = "proto-ipv6")]
+mod ipv6;
+#[cfg(feature = "proto-sixlowpan")]
+mod sixlowpan;
+
+#[cfg(feature = "proto-igmp")]
+use std::vec::Vec;
+
+use crate::tests::setup;
+
+use rstest::*;
+
+use super::*;
+
+use crate::iface::Interface;
+use crate::phy::ChecksumCapabilities;
+#[cfg(feature = "alloc")]
+use crate::phy::Loopback;
+use crate::time::Instant;
+
+#[allow(unused)]
+fn fill_slice(s: &mut [u8], val: u8) {
+ for x in s.iter_mut() {
+ *x = val
+ }
+}
+
+#[cfg(feature = "proto-igmp")]
+fn recv_all(device: &mut crate::tests::TestingDevice, timestamp: Instant) -> Vec<Vec<u8>> {
+ let mut pkts = Vec::new();
+ while let Some((rx, _tx)) = device.receive(timestamp) {
+ rx.consume(|pkt| {
+ pkts.push(pkt.to_vec());
+ });
+ }
+ pkts
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct MockTxToken;
+
+impl TxToken for MockTxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut junk = [0; 1536];
+ f(&mut junk[..len])
+ }
+}
+
+#[test]
+#[should_panic(expected = "The hardware address does not match the medium of the interface.")]
+#[cfg(all(feature = "medium-ip", feature = "medium-ethernet", feature = "alloc"))]
+fn test_new_panic() {
+ let mut device = Loopback::new(Medium::Ethernet);
+ let config = Config::new(HardwareAddress::Ip);
+ Interface::new(config, &mut device, Instant::ZERO);
+}
+
+#[rstest]
+#[cfg(feature = "default")]
+fn test_handle_udp_broadcast(
+ #[values(Medium::Ip, Medium::Ethernet, Medium::Ieee802154)] medium: Medium,
+) {
+ use crate::wire::IpEndpoint;
+
+ static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+
+ let udp_socket = udp::Socket::new(rx_buffer, tx_buffer);
+
+ let mut udp_bytes = vec![0u8; 13];
+ let mut packet = UdpPacket::new_unchecked(&mut udp_bytes);
+
+ let socket_handle = sockets.add(udp_socket);
+
+ #[cfg(feature = "proto-ipv6")]
+ let src_ip = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ let src_ip = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+
+ #[cfg(feature = "proto-ipv6")]
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+ src_addr: src_ip,
+ dst_addr: Ipv6Address::LINK_LOCAL_ALL_NODES,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 0x40,
+ });
+ #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: src_ip,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 0x40,
+ });
+
+ // Bind the socket to port 68
+ let socket = sockets.get_mut::<udp::Socket>(socket_handle);
+ assert_eq!(socket.bind(68), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+
+ udp_repr.emit(
+ &mut packet,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ // Packet should be handled by bound UDP socket
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ packet.into_inner(),
+ ),
+ None
+ );
+
+ // Make sure the payload to the UDP packet processed by process_udp is
+ // appended to the bound sockets rx_buffer
+ let socket = sockets.get_mut::<udp::Socket>(socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((&UDP_PAYLOAD[..], IpEndpoint::new(src_ip.into(), 67).into()))
+ );
+}
+
+#[test]
+#[cfg(all(feature = "medium-ip", feature = "socket-tcp", feature = "proto-ipv6"))]
+pub fn tcp_not_accepted() {
+ let (mut iface, mut sockets, _) = setup(Medium::Ip);
+ let tcp = TcpRepr {
+ src_port: 4242,
+ dst_port: 4243,
+ control: TcpControl::Syn,
+ seq_number: TcpSeqNumber(-10001),
+ ack_number: None,
+ window_len: 256,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ };
+
+ let mut tcp_bytes = vec![0u8; tcp.buffer_len()];
+
+ tcp.emit(
+ &mut TcpPacket::new_unchecked(&mut tcp_bytes),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1).into(),
+ &ChecksumCapabilities::default(),
+ );
+
+ assert_eq!(
+ iface.inner.process_tcp(
+ &mut sockets,
+ IpRepr::Ipv6(Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ }),
+ &tcp_bytes,
+ ),
+ Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
+ dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Tcp(TcpRepr {
+ src_port: 4243,
+ dst_port: 4242,
+ control: TcpControl::Rst,
+ seq_number: TcpSeqNumber(0),
+ ack_number: Some(TcpSeqNumber(-10000)),
+ window_len: 0,
+ window_scale: None,
+ max_seg_size: None,
+ sack_permitted: false,
+ sack_ranges: [None, None, None],
+ payload: &[],
+ })
+ ))
+ );
+ // Unspecified destination address.
+ tcp.emit(
+ &mut TcpPacket::new_unchecked(&mut tcp_bytes),
+ &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(),
+ &Ipv6Address::UNSPECIFIED.into(),
+ &ChecksumCapabilities::default(),
+ );
+
+ assert_eq!(
+ iface.inner.process_tcp(
+ &mut sockets,
+ IpRepr::Ipv6(Ipv6Repr {
+ src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
+ dst_addr: Ipv6Address::UNSPECIFIED,
+ next_header: IpProtocol::Tcp,
+ payload_len: tcp.buffer_len(),
+ hop_limit: 64,
+ }),
+ &tcp_bytes,
+ ),
+ None,
+ );
+}
diff --git a/src/iface/interface/tests/sixlowpan.rs b/src/iface/interface/tests/sixlowpan.rs
new file mode 100644
index 0000000..676835e
--- /dev/null
+++ b/src/iface/interface/tests/sixlowpan.rs
@@ -0,0 +1,434 @@
+use super::*;
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn ieee802154_wrong_pan_id(#[case] medium: Medium) {
+ let data = [
+ 0x41, 0xcc, 0x3b, 0xff, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a,
+ 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26,
+ ];
+
+ let response = None;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ assert_eq!(
+ iface.inner.process_ieee802154(
+ &mut sockets,
+ PacketMeta::default(),
+ &data[..],
+ &mut iface.fragments
+ ),
+ response,
+ );
+}
+
+#[rstest]
+#[case::ieee802154(Medium::Ieee802154)]
+#[cfg(feature = "medium-ieee802154")]
+fn icmp_echo_request(#[case] medium: Medium) {
+ let data = [
+ 0x41, 0xcc, 0x3b, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a,
+ 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, 0x6a, 0x33, 0x0a, 0x62, 0x17, 0x3a, 0x80, 0x00, 0xb0,
+ 0xe3, 0x00, 0x04, 0x00, 0x01, 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37,
+ ];
+
+ let response = Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]),
+ dst_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x241c, 0x2957, 0x34a6, 0x3a62]),
+ hop_limit: 64,
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 4,
+ seq_no: 1,
+ data: &[
+ 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ ],
+ }),
+ ));
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]),
+ 10,
+ )))
+ .unwrap();
+ });
+
+ assert_eq!(
+ iface.inner.process_ieee802154(
+ &mut sockets,
+ PacketMeta::default(),
+ &data[..],
+ &mut iface.fragments
+ ),
+ response,
+ );
+}
+
+#[test]
+#[cfg(feature = "proto-sixlowpan-fragmentation")]
+fn test_echo_request_sixlowpan_128_bytes() {
+ use crate::phy::Checksum;
+
+ let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76),
+ 10,
+ )))
+ .unwrap();
+ });
+ // TODO: modify the example, such that we can also test if the checksum is correctly
+ // computed.
+ iface.inner.caps.checksum.icmpv6 = Checksum::None;
+
+ assert_eq!(iface.inner.caps.medium, Medium::Ieee802154);
+ let now = iface.inner.now();
+
+ iface.inner.neighbor_cache.fill(
+ Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x2, 0, 0, 0, 0, 0, 0, 0]).into(),
+ HardwareAddress::Ieee802154(Ieee802154Address::default()),
+ now,
+ );
+
+ let mut ieee802154_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(5),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: Some(Ieee802154Pan(0xbeef)),
+ dst_addr: Some(Ieee802154Address::Extended([
+ 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76,
+ ])),
+ src_pan_id: Some(Ieee802154Pan(0xbeef)),
+ src_addr: Some(Ieee802154Address::Extended([
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a,
+ ])),
+ };
+
+ // NOTE: this data is retrieved from tests with Contiki-NG
+
+ let request_first_part_packet = SixlowpanFragPacket::new_checked(&[
+ 0xc0, 0xb0, 0x00, 0x8e, 0x6a, 0x33, 0x05, 0x25, 0x2c, 0x3a, 0x80, 0x00, 0xe0, 0x71, 0x00,
+ 0x27, 0x00, 0x02, 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ])
+ .unwrap();
+
+ let request_first_part_iphc_packet =
+ SixlowpanIphcPacket::new_checked(request_first_part_packet.payload()).unwrap();
+
+ let request_first_part_iphc_repr = SixlowpanIphcRepr::parse(
+ &request_first_part_iphc_packet,
+ ieee802154_repr.src_addr,
+ ieee802154_repr.dst_addr,
+ &iface.inner.sixlowpan_address_context,
+ )
+ .unwrap();
+
+ assert_eq!(
+ request_first_part_iphc_repr.src_addr,
+ Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb,
+ 0x1a,
+ ]),
+ );
+ assert_eq!(
+ request_first_part_iphc_repr.dst_addr,
+ Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc,
+ 0x76,
+ ]),
+ );
+
+ let request_second_part = [
+ 0xe0, 0xb0, 0x00, 0x8e, 0x10, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ &request_first_part_packet.into_inner()[..],
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ ieee802154_repr.sequence_number = Some(6);
+
+ // data that was generated when using `ping -s 128`
+ let data = &[
+ 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ];
+
+ let result = iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ &request_second_part,
+ &mut iface.fragments,
+ );
+
+ assert_eq!(
+ result,
+ Some(Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41,
+ 0xfc, 0x76,
+ ]),
+ dst_addr: Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42,
+ 0xb, 0x1a,
+ ]),
+ next_header: IpProtocol::Icmpv6,
+ payload_len: 136,
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv6(Icmpv6Repr::EchoReply {
+ ident: 39,
+ seq_no: 2,
+ data,
+ })
+ ))
+ );
+
+ iface.inner.neighbor_cache.fill(
+ IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a,
+ ])),
+ HardwareAddress::Ieee802154(Ieee802154Address::default()),
+ Instant::now(),
+ );
+
+ let tx_token = device.transmit(Instant::now()).unwrap();
+ iface.inner.dispatch_ieee802154(
+ Ieee802154Address::default(),
+ tx_token,
+ PacketMeta::default(),
+ result.unwrap(),
+ &mut iface.fragmenter,
+ );
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb0, 0x5, 0x4e, 0x7a, 0x11, 0x3a, 0x92, 0xfc, 0x48, 0xc2,
+ 0xa4, 0x41, 0xfc, 0x76, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a, 0x81, 0x0, 0x0,
+ 0x0, 0x0, 0x27, 0x0, 0x2, 0xa2, 0xc2, 0x2d, 0x63, 0x0, 0x0, 0x0, 0x0, 0xd9, 0x5e, 0xc,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+ 0x44, 0x45, 0x46, 0x47,
+ ]
+ );
+
+ iface.poll(Instant::now(), &mut device, &mut sockets);
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb0, 0x5, 0x4e, 0xf, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+ 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
+ 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ ]
+ );
+}
+
+#[test]
+#[cfg(feature = "proto-sixlowpan-fragmentation")]
+fn test_sixlowpan_udp_with_fragmentation() {
+ use crate::phy::Checksum;
+
+ let mut ieee802154_repr = Ieee802154Repr {
+ frame_type: Ieee802154FrameType::Data,
+ security_enabled: false,
+ frame_pending: false,
+ ack_request: false,
+ sequence_number: Some(5),
+ pan_id_compression: true,
+ frame_version: Ieee802154FrameVersion::Ieee802154_2003,
+ dst_pan_id: Some(Ieee802154Pan(0xbeef)),
+ dst_addr: Some(Ieee802154Address::Extended([
+ 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76,
+ ])),
+ src_pan_id: Some(Ieee802154Pan(0xbeef)),
+ src_addr: Some(Ieee802154Address::Extended([
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a,
+ ])),
+ };
+
+ let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154);
+ iface.update_ip_addrs(|ips| {
+ ips.push(IpCidr::Ipv6(Ipv6Cidr::new(
+ Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76),
+ 10,
+ )))
+ .unwrap();
+ });
+ iface.inner.caps.checksum.udp = Checksum::None;
+
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_socket_handle = sockets.add(udp_socket);
+
+ {
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert_eq!(socket.bind(6969), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+ }
+
+ let udp_first_part = &[
+ 0xc0, 0xbc, 0x00, 0x92, 0x6e, 0x33, 0x07, 0xe7, 0xdc, 0xf0, 0xd3, 0xc9, 0x1b, 0x39, 0xbf,
+ 0xa0, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
+ 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63,
+ 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70,
+ 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x49, 0x6e,
+ 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, 0x20, 0x74, 0x6f, 0x72,
+ 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ udp_first_part,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ ieee802154_repr.sequence_number = Some(6);
+
+ let udp_second_part = &[
+ 0xe0, 0xbc, 0x00, 0x92, 0x11, 0x64, 0x69, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73,
+ 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76,
+ 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20,
+ 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e,
+ ];
+
+ assert_eq!(
+ iface.inner.process_sixlowpan(
+ &mut sockets,
+ PacketMeta::default(),
+ &ieee802154_repr,
+ udp_second_part,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+
+ let udp_data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
+In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo nec.";
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ &udp_data[..],
+ IpEndpoint {
+ addr: IpAddress::Ipv6(Ipv6Address([
+ 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42,
+ 0xb, 0x1a,
+ ])),
+ port: 54217,
+ }
+ .into()
+ ))
+ );
+
+ let tx_token = device.transmit(Instant::now()).unwrap();
+ iface.inner.dispatch_ieee802154(
+ Ieee802154Address::default(),
+ tx_token,
+ PacketMeta::default(),
+ Packet::new_ipv6(
+ Ipv6Repr {
+ src_addr: Ipv6Address::default(),
+ dst_addr: Ipv6Address::default(),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_data.len(),
+ hop_limit: 64,
+ },
+ IpPayload::Udp(
+ UdpRepr {
+ src_port: 1234,
+ dst_port: 1234,
+ },
+ udp_data,
+ ),
+ ),
+ &mut iface.fragmenter,
+ );
+
+ iface.poll(Instant::now(), &mut device, &mut sockets);
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb4, 0x5, 0x4e, 0x7e, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0x0, 0x0,
+ 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
+ 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20,
+ 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64,
+ 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e,
+ 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73,
+ 0x20, 0x74,
+ ],
+ );
+
+ assert_eq!(
+ device.queue.pop_front().unwrap(),
+ &[
+ 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb4, 0x5, 0x4e, 0xf, 0x6f, 0x72, 0x74, 0x6f, 0x72, 0x2e,
+ 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x64, 0x69, 0x74, 0x20,
+ 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76,
+ 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c,
+ 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e,
+ ]
+ );
+}