Skip to main content

wireforge_core/
arp.rs

1//! ARP packet parser and builder (Ethernet/IPv4 hardware/protocol combination).
2
3use alloc::vec;
4use alloc::vec::Vec;
5
6use crate::types::{ArpHardwareType, ArpOperation, EtherType};
7use crate::util::{read_u16be, write_u16be, BuildError};
8
9/// Minimum ARP packet length for Ethernet/IPv4 (28 bytes: 8-byte header + 2×6 MAC + 2×4 IP).
10pub const ARP_HEADER_LEN: usize = 28;
11
12/// Zero-copy ARP packet parser.
13#[derive(Debug, Clone)]
14pub struct ArpPacket<'a> {
15    buf: &'a [u8],
16}
17
18impl<'a> ArpPacket<'a> {
19    /// Attempt to parse an ARP packet. Returns `None` if `buf` is shorter than
20    /// [`ARP_HEADER_LEN`].
21    pub fn new(buf: &'a [u8]) -> Option<Self> {
22        if buf.len() < ARP_HEADER_LEN {
23            return None;
24        }
25        Some(Self { buf })
26    }
27
28    #[inline]
29    pub fn hardware_type(&self) -> ArpHardwareType {
30        ArpHardwareType::from(read_u16be(&self.buf[..2]))
31    }
32
33    #[inline]
34    pub fn protocol_type(&self) -> EtherType {
35        EtherType::from(read_u16be(&self.buf[2..4]))
36    }
37
38    #[inline]
39    pub fn hw_addr_len(&self) -> u8 {
40        self.buf[4]
41    }
42
43    #[inline]
44    pub fn proto_addr_len(&self) -> u8 {
45        self.buf[5]
46    }
47
48    #[inline]
49    pub fn operation(&self) -> ArpOperation {
50        ArpOperation::from(read_u16be(&self.buf[6..8]))
51    }
52
53    #[inline]
54    pub fn sender_hw_addr(&self) -> &'a [u8] {
55        let len = self.hw_addr_len() as usize;
56        &self.buf[8..8 + len]
57    }
58
59    #[inline]
60    pub fn sender_proto_addr(&self) -> &'a [u8] {
61        let hw_len = self.hw_addr_len() as usize;
62        let start = 8 + hw_len;
63        let len = self.proto_addr_len() as usize;
64        &self.buf[start..start + len]
65    }
66
67    #[inline]
68    pub fn target_hw_addr(&self) -> &'a [u8] {
69        let hw_len = self.hw_addr_len() as usize;
70        let proto_len = self.proto_addr_len() as usize;
71        let start = 8 + hw_len + proto_len;
72        &self.buf[start..start + hw_len]
73    }
74
75    #[inline]
76    pub fn target_proto_addr(&self) -> &'a [u8] {
77        let hw_len = self.hw_addr_len() as usize;
78        let proto_len = self.proto_addr_len() as usize;
79        let start = 8 + hw_len + proto_len + hw_len;
80        &self.buf[start..start + proto_len]
81    }
82
83    #[inline]
84    pub fn is_request(&self) -> bool {
85        self.operation() == ArpOperation::Request
86    }
87
88    #[inline]
89    pub fn is_reply(&self) -> bool {
90        self.operation() == ArpOperation::Reply
91    }
92}
93
94// ---------------------------------------------------------------------------
95// Builder
96// ---------------------------------------------------------------------------
97
98/// Builder for ARP packets (Ethernet/IPv4).
99#[derive(Debug, Clone)]
100pub struct ArpPacketBuilder {
101    hw_type: u16,
102    proto_type: u16,
103    hw_addr_len: u8,
104    proto_addr_len: u8,
105    operation: u16,
106    sender_hw: Vec<u8>,
107    sender_proto: Vec<u8>,
108    target_hw: Vec<u8>,
109    target_proto: Vec<u8>,
110}
111
112impl Default for ArpPacketBuilder {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl ArpPacketBuilder {
119    /// Create a new builder with Ethernet/IPv4 defaults.
120    pub fn new() -> Self {
121        Self {
122            hw_type: 1, // Ethernet
123            proto_type: 0x0800, // IPv4
124            hw_addr_len: 6,
125            proto_addr_len: 4,
126            operation: 1, // Request
127            sender_hw: vec![0u8; 6],
128            sender_proto: vec![0u8; 4],
129            target_hw: vec![0u8; 6],
130            target_proto: vec![0u8; 4],
131        }
132    }
133
134    pub fn hardware_type(mut self, ht: ArpHardwareType) -> Self {
135        self.hw_type = ht.into();
136        self
137    }
138
139    pub fn protocol_type(mut self, pt: EtherType) -> Self {
140        self.proto_type = pt.into();
141        self
142    }
143
144    pub fn operation(mut self, op: ArpOperation) -> Self {
145        self.operation = op.into();
146        self
147    }
148
149    pub fn sender_hw_addr(mut self, addr: &[u8]) -> Result<Self, BuildError> {
150        if addr.len() as u8 != self.hw_addr_len {
151            return Err(BuildError::InvalidField {
152                field: "sender_hw_addr",
153                reason: "length mismatch",
154            });
155        }
156        self.sender_hw = addr.to_vec();
157        Ok(self)
158    }
159
160    pub fn sender_proto_addr(mut self, addr: &[u8]) -> Result<Self, BuildError> {
161        if addr.len() as u8 != self.proto_addr_len {
162            return Err(BuildError::InvalidField {
163                field: "sender_proto_addr",
164                reason: "length mismatch",
165            });
166        }
167        self.sender_proto = addr.to_vec();
168        Ok(self)
169    }
170
171    pub fn target_hw_addr(mut self, addr: &[u8]) -> Result<Self, BuildError> {
172        if addr.len() as u8 != self.hw_addr_len {
173            return Err(BuildError::InvalidField {
174                field: "target_hw_addr",
175                reason: "length mismatch",
176            });
177        }
178        self.target_hw = addr.to_vec();
179        Ok(self)
180    }
181
182    pub fn target_proto_addr(mut self, addr: &[u8]) -> Result<Self, BuildError> {
183        if addr.len() as u8 != self.proto_addr_len {
184            return Err(BuildError::InvalidField {
185                field: "target_proto_addr",
186                reason: "length mismatch",
187            });
188        }
189        self.target_proto = addr.to_vec();
190        Ok(self)
191    }
192
193    pub fn build(self) -> Vec<u8> {
194        let total = 8 + self.sender_hw.len() + self.sender_proto.len()
195            + self.target_hw.len() + self.target_proto.len();
196        let mut buf = Vec::with_capacity(total);
197
198        write_u16be(&mut [0u8; 2], self.hw_type);
199        buf.extend_from_slice(&self.hw_type.to_be_bytes());
200        buf.extend_from_slice(&self.proto_type.to_be_bytes());
201        buf.push(self.hw_addr_len);
202        buf.push(self.proto_addr_len);
203        buf.extend_from_slice(&self.operation.to_be_bytes());
204        buf.extend_from_slice(&self.sender_hw);
205        buf.extend_from_slice(&self.sender_proto);
206        buf.extend_from_slice(&self.target_hw);
207        buf.extend_from_slice(&self.target_proto);
208
209        buf
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    // ARP Request: who-has 192.168.1.2 tell 192.168.1.1
218    const SAMPLE_ARP_REQUEST: &[u8] = &[
219        0x00, 0x01, // hardware: Ethernet
220        0x08, 0x00, // protocol: IPv4
221        0x06, // hw addr len
222        0x04, // proto addr len
223        0x00, 0x01, // operation: Request
224        0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // sender MAC
225        0xC0, 0xA8, 0x01, 0x01, // sender IP: 192.168.1.1
226        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target MAC (zero)
227        0xC0, 0xA8, 0x01, 0x02, // target IP: 192.168.1.2
228    ];
229
230    #[test]
231    fn parse_arp_request() {
232        let pkt = ArpPacket::new(SAMPLE_ARP_REQUEST).unwrap();
233        assert_eq!(pkt.hardware_type(), ArpHardwareType::Ethernet);
234        assert_eq!(pkt.protocol_type(), EtherType::Ipv4);
235        assert!(pkt.is_request());
236        assert!(!pkt.is_reply());
237        assert_eq!(pkt.sender_hw_addr(), &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
238        assert_eq!(pkt.sender_proto_addr(), &[192, 168, 1, 1]);
239        assert_eq!(pkt.target_hw_addr(), &[0u8; 6]);
240        assert_eq!(pkt.target_proto_addr(), &[192, 168, 1, 2]);
241    }
242
243    #[test]
244    fn parse_too_short() {
245        assert!(ArpPacket::new(&[0u8; 27]).is_none());
246        assert!(ArpPacket::new(&[]).is_none());
247    }
248
249    #[test]
250    fn build_and_parse_roundtrip() {
251        let buf = ArpPacketBuilder::new()
252            .operation(ArpOperation::Reply)
253            .sender_hw_addr(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66])
254            .unwrap()
255            .sender_proto_addr(&[10, 0, 0, 1])
256            .unwrap()
257            .target_hw_addr(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])
258            .unwrap()
259            .target_proto_addr(&[10, 0, 0, 2])
260            .unwrap()
261            .build();
262
263        let pkt = ArpPacket::new(&buf).unwrap();
264        assert!(pkt.is_reply());
265        assert_eq!(pkt.sender_hw_addr(), &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
266        assert_eq!(pkt.target_proto_addr(), &[10, 0, 0, 2]);
267    }
268}