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// Builder error type
98// ---------------------------------------------------------------------------
99
100/// Error returned by packet builders when construction fails.
101#[derive(Debug, Clone)]
102pub enum BuildError {
103    /// Payload exceeds the maximum allowed by this protocol.
104    PayloadTooLarge { max: usize, actual: usize },
105    /// A field value is out of range.
106    InvalidField { field: &'static str, reason: &'static str },
107}
108
109impl core::fmt::Display for BuildError {
110    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111        match self {
112            Self::PayloadTooLarge { max, actual } => {
113                write!(f, "payload too large: max {max} bytes, got {actual}")
114            }
115            Self::InvalidField { field, reason } => {
116                write!(f, "invalid field `{field}`: {reason}")
117            }
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn read_u16be_works() {
128        assert_eq!(read_u16be(&[0x08, 0x00]), 0x0800);
129        assert_eq!(read_u16be(&[0xFF, 0xFF]), 0xFFFF);
130    }
131
132    #[test]
133    fn read_u32be_works() {
134        assert_eq!(read_u32be(&[0xC0, 0xA8, 0x01, 0x01]), 0xC0A80101);
135    }
136
137    #[test]
138    fn write_u16be_works() {
139        let mut buf = [0u8; 2];
140        write_u16be(&mut buf, 0x0800);
141        assert_eq!(buf, [0x08, 0x00]);
142    }
143
144    #[test]
145    fn write_u32be_works() {
146        let mut buf = [0u8; 4];
147        write_u32be(&mut buf, 0xC0A80101);
148        assert_eq!(buf, [0xC0, 0xA8, 0x01, 0x01]);
149    }
150
151    // RFC 1071 test vectors
152    #[test]
153    fn checksum_empty() {
154        // Ones' complement of zero is 0xFFFF
155        assert_eq!(internet_checksum(&[]), 0xFFFF);
156    }
157
158    #[test]
159    fn checksum_single_byte() {
160        assert_eq!(internet_checksum(&[0x01]), 0xFEFF);
161    }
162
163    #[test]
164    fn checksum_known_ip_header() {
165        // A minimal valid IP header (20 bytes, checksum field zeroed)
166        let header: [u8; 20] = [
167            0x45, 0x00, 0x00, 0x3C, 0x1C, 0x46, 0x40, 0x00,
168            0x40, 0x06, 0x00, 0x00, 0xAC, 0x10, 0x0A, 0x63,
169            0xAC, 0x10, 0x0A, 0x0C,
170        ];
171        let checksum = internet_checksum(&header);
172        // Verify that header with checksum inserted passes verification
173        let mut with_checksum = header;
174        with_checksum[10..12].copy_from_slice(&checksum.to_be_bytes());
175        assert_eq!(internet_checksum(&with_checksum), 0);
176    }
177
178    #[test]
179    fn pseudo_header_checksum_basic() {
180        // IPv4 pseudo-header + minimal TCP header
181        let src = [192u8, 168, 1, 1];
182        let dst = [192u8, 168, 1, 2];
183        let tcp_header = [0x00u8, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
184                          0x50, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
185                          0x00, 0x00, 0x00, 0x00];
186        let csum = pseudo_header_checksum(&src, &dst, 6, &tcp_header);
187        // Should compute a valid non-zero value
188        assert_ne!(csum, 0);
189    }
190
191    #[test]
192    fn checksum_roundtrip() {
193        let data: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
194        let csum = internet_checksum(&data);
195        // Original data + checksum should verify to 0
196        let mut combined = [0u8; 10];
197        combined[..8].copy_from_slice(&data);
198        combined[8..10].copy_from_slice(&csum.to_be_bytes());
199        assert_eq!(internet_checksum(&combined), 0);
200    }
201}