wireforge_core/util.rs
1//! Byte-order helpers, checksum computation, error types.
2
3// ---------------------------------------------------------------------------
4// Big-endian byte read/write helpers
5// ---------------------------------------------------------------------------
6
7/// Read a big-endian u16 from a 2-byte slice. Caller guarantees `buf.len() >= 2`.
8#[inline]
9pub fn read_u16be(buf: &[u8]) -> u16 {
10 u16::from_be_bytes([buf[0], buf[1]])
11}
12
13/// Read a big-endian u32 from a 4-byte slice. Caller guarantees `buf.len() >= 4`.
14#[inline]
15pub fn read_u32be(buf: &[u8]) -> u32 {
16 u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]])
17}
18
19/// Write a big-endian u16 into a 2-byte slice. Caller guarantees `buf.len() >= 2`.
20#[inline]
21pub fn write_u16be(buf: &mut [u8], val: u16) {
22 buf[..2].copy_from_slice(&val.to_be_bytes());
23}
24
25/// Write a big-endian u32 into a 4-byte slice. Caller guarantees `buf.len() >= 4`.
26#[inline]
27pub fn write_u32be(buf: &mut [u8], val: u32) {
28 buf[..4].copy_from_slice(&val.to_be_bytes());
29}
30
31// ---------------------------------------------------------------------------
32// RFC 1071 Internet Checksum
33// ---------------------------------------------------------------------------
34
35/// Compute the 16-bit ones' complement of the ones' complement sum of `data`.
36///
37/// The result is in network byte order and is the value that should be
38/// written into a checksum field so that verifying the entire header
39/// (including the checksum field) yields zero.
40///
41/// For odd-length data, a virtual zero byte is appended for the final
42/// 16-bit word.
43pub fn internet_checksum(mut data: &[u8]) -> u16 {
44 let mut sum: u32 = 0;
45 while data.len() >= 2 {
46 sum += read_u16be(data) as u32;
47 data = &data[2..];
48 }
49 if !data.is_empty() {
50 // Odd byte: pad with zero
51 sum += (data[0] as u32) << 8;
52 }
53 // Fold carries
54 while sum >> 16 != 0 {
55 sum = (sum & 0xFFFF) + (sum >> 16);
56 }
57 !(sum as u16)
58}
59
60/// Compute the TCP/UDP pseudo-header checksum.
61///
62/// `src` and `dst` are the raw IP address bytes (4 bytes for IPv4, 16 for
63/// IPv6). `protocol` is the IP protocol number. `transport_data` is the
64/// TCP or UDP header + payload.
65pub fn pseudo_header_checksum(src: &[u8], dst: &[u8], protocol: u8, transport_data: &[u8]) -> u16 {
66 let mut sum: u32 = 0;
67
68 // Source address
69 sum = add_slice_to_sum(sum, src);
70 // Destination address
71 sum = add_slice_to_sum(sum, dst);
72 // Protocol (padded to 16 bits)
73 sum += protocol as u32;
74 // Transport-layer length
75 sum += transport_data.len() as u32;
76 // Transport data
77 sum = add_slice_to_sum(sum, transport_data);
78
79 while sum >> 16 != 0 {
80 sum = (sum & 0xFFFF) + (sum >> 16);
81 }
82 !(sum as u16)
83}
84
85fn add_slice_to_sum(mut sum: u32, mut data: &[u8]) -> u32 {
86 while data.len() >= 2 {
87 sum += read_u16be(data) as u32;
88 data = &data[2..];
89 }
90 if !data.is_empty() {
91 sum += (data[0] as u32) << 8;
92 }
93 sum
94}
95
96// ---------------------------------------------------------------------------
97// Checksum seal helpers — zero the field, compute, and write back in one call.
98// ---------------------------------------------------------------------------
99
100/// Zero the 2-byte checksum field at `csum_offset`, compute the internet
101/// checksum over `buf`, and write the result back.
102#[inline]
103pub fn seal_internet_checksum(buf: &mut [u8], csum_offset: usize) {
104 buf[csum_offset] = 0;
105 buf[csum_offset + 1] = 0;
106 let csum = internet_checksum(buf);
107 write_u16be(&mut buf[csum_offset..csum_offset + 2], csum);
108}
109
110/// Zero the 2-byte checksum field at `csum_offset`, compute the TCP/UDP
111/// pseudo-header checksum over `buf`, and write the result back.
112#[inline]
113pub fn seal_transport_checksum(buf: &mut [u8], csum_offset: usize, src: &[u8], dst: &[u8], protocol: u8) {
114 buf[csum_offset] = 0;
115 buf[csum_offset + 1] = 0;
116 let csum = pseudo_header_checksum(src, dst, protocol, buf);
117 write_u16be(&mut buf[csum_offset..csum_offset + 2], csum);
118}
119
120/// Generate a standard parser `new()` method with a minimum-length check.
121///
122/// Usage: `parser_new!(UDP_HEADER_LEN);` inside an `impl<'a> FooPacket<'a>` block
123/// where the struct has a `buf: &'a [u8]` field.
124#[macro_export]
125macro_rules! parser_new {
126 ($min_len:expr) => {
127 #[inline]
128 pub fn new(buf: &'a [u8]) -> Option<Self> {
129 if buf.len() < $min_len { return None; }
130 Some(Self { buf })
131 }
132 };
133}
134
135// ---------------------------------------------------------------------------
136// PacketBuilder trait — Template Method pattern
137// ---------------------------------------------------------------------------
138
139/// Common interface for all packet builders.
140///
141/// This is the **Template Method** pattern: every builder follows the same
142/// high-level construction sequence (validate → assemble header → seal
143/// checksum → append payload), but customizes the concrete steps via its
144/// own field values and the checksum helpers ([`seal_internet_checksum`]
145/// and [`seal_transport_checksum`]).
146///
147/// # Template sequence
148///
149/// 1. **Validate** — reject field values that violate protocol constraints
150/// 2. **Assemble header** — write fixed and variable fields into the buffer
151/// 3. **Seal checksum** — zero the checksum field, compute, write back
152/// 4. **Append payload** — attach the user-supplied payload bytes
153/// 5. **Return** — hand back the complete `Vec<u8>` packet
154pub trait PacketBuilder {
155 /// The final assembled output (typically `Vec<u8>`).
156 type Output;
157
158 /// Execute the construction template and return the assembled packet.
159 fn build(self) -> Self::Output;
160}
161
162// ---------------------------------------------------------------------------
163// Builder error type
164// ---------------------------------------------------------------------------
165
166/// Error returned by packet builders when construction fails.
167#[derive(Debug, Clone)]
168pub enum BuildError {
169 /// Payload exceeds the maximum allowed by this protocol.
170 PayloadTooLarge { max: usize, actual: usize },
171 /// A field value is out of range.
172 InvalidField { field: &'static str, reason: &'static str },
173}
174
175impl core::fmt::Display for BuildError {
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 match self {
178 Self::PayloadTooLarge { max, actual } => {
179 write!(f, "payload too large: max {max} bytes, got {actual}")
180 }
181 Self::InvalidField { field, reason } => {
182 write!(f, "invalid field `{field}`: {reason}")
183 }
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn read_u16be_works() {
194 assert_eq!(read_u16be(&[0x08, 0x00]), 0x0800);
195 assert_eq!(read_u16be(&[0xFF, 0xFF]), 0xFFFF);
196 }
197
198 #[test]
199 fn read_u32be_works() {
200 assert_eq!(read_u32be(&[0xC0, 0xA8, 0x01, 0x01]), 0xC0A80101);
201 }
202
203 #[test]
204 fn write_u16be_works() {
205 let mut buf = [0u8; 2];
206 write_u16be(&mut buf, 0x0800);
207 assert_eq!(buf, [0x08, 0x00]);
208 }
209
210 #[test]
211 fn write_u32be_works() {
212 let mut buf = [0u8; 4];
213 write_u32be(&mut buf, 0xC0A80101);
214 assert_eq!(buf, [0xC0, 0xA8, 0x01, 0x01]);
215 }
216
217 // RFC 1071 test vectors
218 #[test]
219 fn checksum_empty() {
220 // Ones' complement of zero is 0xFFFF
221 assert_eq!(internet_checksum(&[]), 0xFFFF);
222 }
223
224 #[test]
225 fn checksum_single_byte() {
226 assert_eq!(internet_checksum(&[0x01]), 0xFEFF);
227 }
228
229 #[test]
230 fn checksum_known_ip_header() {
231 // A minimal valid IP header (20 bytes, checksum field zeroed)
232 let header: [u8; 20] = [
233 0x45, 0x00, 0x00, 0x3C, 0x1C, 0x46, 0x40, 0x00,
234 0x40, 0x06, 0x00, 0x00, 0xAC, 0x10, 0x0A, 0x63,
235 0xAC, 0x10, 0x0A, 0x0C,
236 ];
237 let checksum = internet_checksum(&header);
238 // Verify that header with checksum inserted passes verification
239 let mut with_checksum = header;
240 with_checksum[10..12].copy_from_slice(&checksum.to_be_bytes());
241 assert_eq!(internet_checksum(&with_checksum), 0);
242 }
243
244 #[test]
245 fn pseudo_header_checksum_basic() {
246 // IPv4 pseudo-header + minimal TCP header
247 let src = [192u8, 168, 1, 1];
248 let dst = [192u8, 168, 1, 2];
249 let tcp_header = [0x00u8, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
250 0x50, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
251 0x00, 0x00, 0x00, 0x00];
252 let csum = pseudo_header_checksum(&src, &dst, 6, &tcp_header);
253 // Should compute a valid non-zero value
254 assert_ne!(csum, 0);
255 }
256
257 #[test]
258 fn checksum_roundtrip() {
259 let data: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
260 let csum = internet_checksum(&data);
261 // Original data + checksum should verify to 0
262 let mut combined = [0u8; 10];
263 combined[..8].copy_from_slice(&data);
264 combined[8..10].copy_from_slice(&csum.to_be_bytes());
265 assert_eq!(internet_checksum(&combined), 0);
266 }
267}