Skip to main content

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}