Skip to main content

internet/ietf/ipv4/encoding/
header.rs

1//! IPv4 header types and codecs.
2//!
3//! As defined in [RFC 791].
4//!
5//! [IETF RFC 791]: https://datatracker.ietf.org/doc/html/rfc791
6
7use crate::ietf::{
8    ip::{Protocol, Version},
9    ipv4::{Address, Options},
10};
11use crate::{Buf, BufError, BufMut, BufResult, Codec, Cursor};
12
13/// Differentiated Services Code Point (DSCP).
14///
15/// A 6-bit field used for packet classification.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct Dscp(pub u8);
18
19impl Dscp {
20    /// Creates a new DSCP value.
21    ///
22    /// # Errors
23    ///
24    /// Returns [`BufError::UnexpectedValue`] if the value exceeds 6 bits (> 63).
25    pub fn new(value: u8) -> BufResult<Self> {
26        if value <= 63 {
27            Ok(Self(value))
28        } else {
29            Err(BufError::UnexpectedValue)
30        }
31    }
32}
33
34/// Explicit Congestion Notification (ECN).
35///
36/// A 2-bit field used for end-to-end notification of network congestion.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38#[repr(u8)]
39pub enum Ecn {
40    /// Non-ECN-Capable Transport.
41    NonEct = 0,
42    /// ECN-Capable Transport (0).
43    Ect0 = 1,
44    /// ECN-Capable Transport (1).
45    Ect1 = 2,
46    /// Congestion Encountered.
47    Ce = 3,
48}
49
50impl Ecn {
51    /// Decodes a 2-bit ECN value.
52    fn from_bits(bits: u8) -> BufResult<Self> {
53        match bits & 0x03 {
54            0 => Ok(Self::NonEct),
55            1 => Ok(Self::Ect0),
56            2 => Ok(Self::Ect1),
57            3 => Ok(Self::Ce),
58            _ => Err(BufError::UnexpectedValue),
59        }
60    }
61}
62
63/// Internet Header Length (IHL).
64///
65/// The length of the IPv4 header in 32-bit (4-byte) words.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub struct Ihl(pub u8);
68
69impl Ihl {
70    /// Creates a new IHL value.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`BufError::UnexpectedValue`] if the value is not between 5 and 15.
75    ///
76    /// *Note: The IHL field is strictly 4 bits in the IPv4 header, making 15
77    /// (binary 1111) the absolute maximum value, which corresponds to a 60-byte header.*
78    pub fn new(value: u8) -> BufResult<Self> {
79        if (5..=15).contains(&value) {
80            Ok(Self(value))
81        } else {
82            Err(BufError::UnexpectedValue)
83        }
84    }
85}
86
87impl PartialEq<u8> for Ihl {
88    fn eq(&self, other: &u8) -> bool {
89        self.0 == *other
90    }
91}
92
93impl PartialEq<usize> for Ihl {
94    fn eq(&self, other: &usize) -> bool {
95        (self.0 as usize) == *other
96    }
97}
98
99impl PartialOrd<u8> for Ihl {
100    fn partial_cmp(&self, other: &u8) -> Option<core::cmp::Ordering> {
101        self.0.partial_cmp(other)
102    }
103}
104
105impl PartialOrd<usize> for Ihl {
106    fn partial_cmp(&self, other: &usize) -> Option<core::cmp::Ordering> {
107        (self.0 as usize).partial_cmp(other)
108    }
109}
110
111/// An IPv4 header checksum.
112///
113/// Used to detect data corruption in the header, as defined in [RFC 791].
114///
115/// [RFC 791]: https://datatracker.ietf.org/doc/html/rfc791
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub struct Checksum(pub u16);
118
119impl Checksum {
120    /// Calculates the IPv4 Header Checksum.
121    ///
122    /// The checksum field must be zeroed out before calculation.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use internet::ipv4::Checksum;
128    ///
129    /// let mut header = [
130    ///     0x45, 0x00, 0x00, 0x34,
131    ///     0x00, 0x00, 0x00, 0x00,
132    ///     0x40, 0x11, 0x00, 0x00,
133    ///     0xc0, 0xa8, 0x00, 0x01,
134    ///     0xc0, 0xa8, 0x00, 0x02,
135    /// ];
136    ///
137    /// let checksum = Checksum::calculate(&header);
138    /// header[10..12].copy_from_slice(&checksum.0.to_be_bytes());
139    /// ```
140    pub fn calculate(data: &[u8]) -> Self {
141        let mut sum: u32 = 0;
142        let mut i = 0;
143
144        while i + 1 < data.len() {
145            sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
146            i += 2;
147        }
148
149        if i < data.len() {
150            sum += (data[i] as u32) << 8;
151        }
152
153        while sum >> 16 != 0 {
154            sum = (sum & 0xFFFF) + (sum >> 16);
155        }
156
157        Self(!(sum as u16))
158    }
159}
160
161impl Codec for Checksum {
162    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
163        self.0.encode(writer, ())
164    }
165
166    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
167        Ok(Self(u16::decode(reader, ())?))
168    }
169}
170
171/// An IPv4 header following [RFC 791].
172///
173/// [IETF RFC 791]: https://datatracker.ietf.org/doc/html/rfc791
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct Header {
176    /// Differentiated Services Code Point.
177    pub dscp: Dscp,
178    /// Explicit Congestion Notification.
179    pub ecn: Ecn,
180    /// Internet Header Length (in 32-bit words).
181    pub ihl: Ihl,
182    /// Total length of the datagram (header + data) in bytes.
183    pub total_length: u16,
184    /// Identification field for fragmentation.
185    pub identification: u16,
186    /// Don't Fragment flag.
187    pub df: bool,
188    /// More Fragments flag.
189    pub mf: bool,
190    /// Fragment offset (in 8-byte blocks).
191    pub fragment_offset: u16,
192    /// Time to Live.
193    pub ttl: u8,
194    /// The protocol of the payload.
195    pub protocol: Protocol,
196    /// The header checksum.
197    pub checksum: Checksum,
198    /// The source IPv4 address.
199    pub source_address: Address,
200    /// The destination IPv4 address.
201    pub destination_address: Address,
202    /// Optional fields and padding.
203    pub options: Options,
204}
205
206impl Codec for Header {
207    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
208        let version_ihl = ((Version::Ip as u8) << 4) | (self.ihl.0 & 0x0F);
209        writer.write_u8(version_ihl)?;
210
211        let dscp_ecn = (self.dscp.0 << 2) | (self.ecn as u8);
212        writer.write_u8(dscp_ecn)?;
213
214        writer.write_u16_be(self.total_length)?;
215        writer.write_u16_be(self.identification)?;
216
217        let mut flags_offset = self.fragment_offset & 0x1FFF;
218        if self.df {
219            flags_offset |= 0x4000;
220        }
221        if self.mf {
222            flags_offset |= 0x2000;
223        }
224        writer.write_u16_be(flags_offset)?;
225
226        writer.write_u8(self.ttl)?;
227        self.protocol.encode(writer, ())?;
228        self.checksum.encode(writer, ())?;
229
230        self.source_address.encode(writer, ())?;
231        self.destination_address.encode(writer, ())?;
232
233        let options_len = ((self.ihl.0 as usize) * 4).saturating_sub(20);
234        self.options.encode(writer, options_len)?;
235
236        Ok(())
237    }
238
239    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
240        let version_ihl = reader.read_u8()?;
241        let version = version_ihl >> 4;
242
243        if version != (Version::Ip as u8) {
244            return Err(BufError::UnexpectedValue);
245        }
246
247        let ihl = Ihl::new(version_ihl & 0x0F)?;
248
249        let dscp_ecn = reader.read_u8()?;
250        let dscp = Dscp::new(dscp_ecn >> 2)?;
251        let ecn = Ecn::from_bits(dscp_ecn)?;
252
253        let total_length = reader.read_u16_be()?;
254        let identification = reader.read_u16_be()?;
255
256        let flags_offset = reader.read_u16_be()?;
257        let df = (flags_offset & 0x4000) != 0;
258        let mf = (flags_offset & 0x2000) != 0;
259        let fragment_offset = flags_offset & 0x1FFF;
260
261        let ttl = reader.read_u8()?;
262        let protocol = Protocol::decode(reader, ())?;
263        let checksum = Checksum::decode(reader, ())?;
264
265        let source_address = Address::decode(reader, ())?;
266        let destination_address = Address::decode(reader, ())?;
267
268        let options_len = ((ihl.0 as usize) * 4).saturating_sub(20);
269        let options = Options::decode(reader, options_len)?;
270
271        Ok(Self {
272            dscp,
273            ecn,
274            ihl,
275            total_length,
276            identification,
277            df,
278            mf,
279            fragment_offset,
280            ttl,
281            protocol,
282            checksum,
283            source_address,
284            destination_address,
285            options,
286        })
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293    use crate::Cursor;
294
295    #[test]
296    fn ihl_comparisons() {
297        let ihl = Ihl::new(5).unwrap();
298        assert_eq!(ihl, 5u8);
299        assert_eq!(ihl, 5usize);
300        assert!(ihl < 6u8);
301        assert!(ihl <= 5usize);
302    }
303
304    #[test]
305    fn ihl_validation() {
306        assert!(Ihl::new(4).is_err());
307        assert!(Ihl::new(5).is_ok());
308        assert!(Ihl::new(15).is_ok());
309        assert!(Ihl::new(16).is_err());
310    }
311
312    #[test]
313    fn header_roundtrip() {
314        let etalon_bytes = &[
315            0x45, 0x00, 0x00, 0x34, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8,
316            0x00, 0x01, 0xc0, 0xa8, 0x00, 0x02,
317        ];
318
319        let etalon_struct = Header {
320            dscp: Dscp(0),
321            ecn: Ecn::NonEct,
322            ihl: Ihl(5),
323            total_length: 52,
324            identification: 0,
325            df: true,
326            mf: false,
327            fragment_offset: 0,
328            ttl: 64,
329            protocol: Protocol::UDP,
330            checksum: Checksum(0),
331            source_address: Address::from([192, 168, 0, 1]),
332            destination_address: Address::from([192, 168, 0, 2]),
333            options: Options::try_from(&[][..]).unwrap(),
334        };
335
336        let mut encoded_bytes = vec![];
337        {
338            let writer = &mut Cursor::new(&mut encoded_bytes);
339            etalon_struct.encode(writer, ()).unwrap();
340        }
341        assert_eq!(&etalon_bytes.to_vec(), &encoded_bytes);
342
343        let decoded_struct = {
344            let reader = &mut Cursor::new(&encoded_bytes);
345            Header::decode(reader, ()).unwrap()
346        };
347        assert_eq!(etalon_struct, decoded_struct);
348    }
349}