Skip to main content

wireforge_packet/
lib.rs

1//! WireForge Packet — unified packet enum and protocol dispatch.
2//!
3//! This crate provides a single `Packet` enum that can represent any
4//! supported protocol layer, plus helper functions to automatically
5//! dispatch from raw bytes to the correct protocol parser.
6
7use wireforge_core::arp::ArpPacket;
8use wireforge_core::ether::EthernetPacket;
9use wireforge_core::icmp::{IcmpPacket, Icmpv6Packet};
10use wireforge_core::ipv4::Ipv4Packet;
11use wireforge_core::ipv6::Ipv6Packet;
12use wireforge_core::tcp::TcpPacket;
13use wireforge_core::udp::UdpPacket;
14use wireforge_core::types::{EtherType, IpProtocol};
15
16/// Unified packet enum covering all supported L2–L4 protocols.
17#[derive(Debug, Clone)]
18pub enum Packet<'a> {
19    Ethernet(EthernetPacket<'a>),
20    Arp(ArpPacket<'a>),
21    Ipv4(Ipv4Packet<'a>),
22    Ipv6(Ipv6Packet<'a>),
23    Tcp(TcpPacket<'a>),
24    Udp(UdpPacket<'a>),
25    Icmp(IcmpPacket<'a>),
26    Icmpv6(Icmpv6Packet<'a>),
27}
28
29// From impls for easy conversion
30impl<'a> From<EthernetPacket<'a>> for Packet<'a> {
31    fn from(p: EthernetPacket<'a>) -> Self { Packet::Ethernet(p) }
32}
33
34impl<'a> From<ArpPacket<'a>> for Packet<'a> {
35    fn from(p: ArpPacket<'a>) -> Self { Packet::Arp(p) }
36}
37
38impl<'a> From<Ipv4Packet<'a>> for Packet<'a> {
39    fn from(p: Ipv4Packet<'a>) -> Self { Packet::Ipv4(p) }
40}
41
42impl<'a> From<Ipv6Packet<'a>> for Packet<'a> {
43    fn from(p: Ipv6Packet<'a>) -> Self { Packet::Ipv6(p) }
44}
45
46impl<'a> From<TcpPacket<'a>> for Packet<'a> {
47    fn from(p: TcpPacket<'a>) -> Self { Packet::Tcp(p) }
48}
49
50impl<'a> From<UdpPacket<'a>> for Packet<'a> {
51    fn from(p: UdpPacket<'a>) -> Self { Packet::Udp(p) }
52}
53
54impl<'a> From<IcmpPacket<'a>> for Packet<'a> {
55    fn from(p: IcmpPacket<'a>) -> Self { Packet::Icmp(p) }
56}
57
58impl<'a> From<Icmpv6Packet<'a>> for Packet<'a> {
59    fn from(p: Icmpv6Packet<'a>) -> Self { Packet::Icmpv6(p) }
60}
61
62/// Parse an Ethernet frame and dispatch the encapsulated protocol.
63///
64/// Returns the parsed `EthernetPacket` and, if the EtherType matches a
65/// known protocol, the inner `Packet` as well.
66pub fn parse_ethernet_frame(buf: &[u8]) -> Option<(EthernetPacket<'_>, Option<Packet<'_>>)> {
67    let eth = EthernetPacket::new(buf)?;
68    let inner = match eth.ethertype() {
69        EtherType::Arp => ArpPacket::new(eth.payload()).map(Packet::Arp),
70        EtherType::Ipv4 => Ipv4Packet::new(eth.payload()).map(Packet::Ipv4),
71        EtherType::Ipv6 => Ipv6Packet::new(eth.payload()).map(Packet::Ipv6),
72        _ => None,
73    };
74    Some((eth, inner))
75}
76
77/// Dispatch the payload of an IPv4 packet to the correct transport-layer parser.
78pub fn parse_ipv4_payload<'a>(ip: &Ipv4Packet<'a>) -> Option<Packet<'a>> {
79    match ip.protocol() {
80        IpProtocol::Icmp => IcmpPacket::new(ip.payload()).map(Packet::Icmp),
81        IpProtocol::Tcp => TcpPacket::new(ip.payload()).map(Packet::Tcp),
82        IpProtocol::Udp => UdpPacket::new(ip.payload()).map(Packet::Udp),
83        _ => None,
84    }
85}
86
87/// Dispatch the payload of an IPv6 packet (skipping extension headers).
88pub fn parse_ipv6_payload<'a>(ip: &Ipv6Packet<'a>) -> Option<Packet<'a>> {
89    match ip.final_protocol() {
90        IpProtocol::Icmpv6 => Icmpv6Packet::new(ip.payload()).map(Packet::Icmpv6),
91        IpProtocol::Tcp => TcpPacket::new(ip.payload()).map(Packet::Tcp),
92        IpProtocol::Udp => UdpPacket::new(ip.payload()).map(Packet::Udp),
93        _ => None,
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use wireforge_core::ipv4::Ipv4PacketBuilder;
101    use wireforge_core::udp::UdpPacketBuilder;
102
103    #[test]
104    fn dispatch_ethernet_to_ipv4_to_tcp() {
105        let ip_bytes = Ipv4PacketBuilder::new()
106            .source(std::net::Ipv4Addr::new(10, 0, 0, 1))
107            .destination(std::net::Ipv4Addr::new(10, 0, 0, 2))
108            .protocol(IpProtocol::Tcp)
109            .ttl(64)
110            .build();
111
112        let mut frame = vec![0xFFu8; 12]; // dst + src mac
113        frame.extend_from_slice(&[0x08, 0x00]); // EtherType::Ipv4
114        frame.extend_from_slice(&ip_bytes);
115
116        let (eth, inner) = parse_ethernet_frame(&frame).unwrap();
117        assert_eq!(eth.ethertype(), EtherType::Ipv4);
118        assert!(matches!(inner, Some(Packet::Ipv4(_))));
119    }
120
121    #[test]
122    fn dispatch_ethernet_to_arp() {
123        // Build a valid ARP request (28 bytes minimum)
124        let mut arp_bytes = vec![0x00u8, 0x01]; // hw_type=Ethernet
125        arp_bytes.extend_from_slice(&[0x08, 0x00]); // proto_type=IPv4
126        arp_bytes.push(6);  // hw_addr_len
127        arp_bytes.push(4);  // proto_addr_len
128        arp_bytes.extend_from_slice(&[0x00, 0x01]); // operation=Request
129        arp_bytes.extend_from_slice(&[0x00u8; 20]); // addresses (6+4+6+4)
130
131        let mut frame = vec![0xFFu8; 12];
132        frame.extend_from_slice(&[0x08, 0x06]); // EtherType::Arp
133        frame.extend_from_slice(&arp_bytes);
134
135        let (eth, inner) = parse_ethernet_frame(&frame).unwrap();
136        assert_eq!(eth.ethertype(), EtherType::Arp);
137        assert!(matches!(inner, Some(Packet::Arp(_))));
138    }
139
140    #[test]
141    fn dispatch_ethernet_to_ipv6() {
142        // Build a minimal IPv6 header (40 bytes)
143        let mut frame = vec![0xFFu8; 12];
144        frame.extend_from_slice(&[0x86, 0xDD]); // EtherType::Ipv6
145        let mut ip6 = vec![0x60u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x40]; // TC+flow=0, payload=0, next=ICMPv6, hop=64
146        ip6.extend_from_slice(&[0u8; 32]); // src + dst = ::
147        frame.extend_from_slice(&ip6);
148
149        let (eth, inner) = parse_ethernet_frame(&frame).unwrap();
150        assert_eq!(eth.ethertype(), EtherType::Ipv6);
151        assert!(matches!(inner, Some(Packet::Ipv6(_))));
152    }
153
154    #[test]
155    fn dispatch_ipv4_to_udp() {
156        let udp_bytes = UdpPacketBuilder::new()
157            .source_port(12345)
158            .destination_port(53)
159            .payload(&[0x01])
160            .build(std::net::Ipv4Addr::new(10, 0, 0, 1), std::net::Ipv4Addr::new(10, 0, 0, 2));
161
162        let ip_bytes = Ipv4PacketBuilder::new()
163            .source(std::net::Ipv4Addr::new(10, 0, 0, 1))
164            .destination(std::net::Ipv4Addr::new(10, 0, 0, 2))
165            .protocol(IpProtocol::Udp)
166            .ttl(64)
167            .payload(&udp_bytes).unwrap()
168            .build();
169
170        let ip = Ipv4Packet::new(&ip_bytes).unwrap();
171        let inner = parse_ipv4_payload(&ip);
172        assert!(matches!(inner, Some(Packet::Udp(_))));
173    }
174
175    #[test]
176    fn dispatch_ipv4_to_icmp() {
177        use wireforge_core::icmp::IcmpPacketBuilder;
178        let icmp_bytes = IcmpPacketBuilder::echo_request()
179            .identifier(1).sequence_number(1)
180            .payload(b"ping")
181            .build();
182
183        let ip_bytes = Ipv4PacketBuilder::new()
184            .source(std::net::Ipv4Addr::new(10, 0, 0, 1))
185            .destination(std::net::Ipv4Addr::new(10, 0, 0, 2))
186            .protocol(IpProtocol::Icmp)
187            .ttl(64)
188            .payload(&icmp_bytes).unwrap()
189            .build();
190
191        let ip = Ipv4Packet::new(&ip_bytes).unwrap();
192        let inner = parse_ipv4_payload(&ip);
193        assert!(matches!(inner, Some(Packet::Icmp(_))));
194    }
195}