zero_packet/network/
icmpv4.rs

1use super::checksum::internet_checksum;
2use core::fmt;
3
4/// The length of an ICMP header in bytes.
5pub const ICMPV4_HEADER_LENGTH: usize = 8;
6
7/// The maximum valid ICMP type.
8pub const ICMPV4_MAX_VALID_CODE: u8 = 15;
9
10/// Writes ICMPv4 header fields.
11pub struct Icmpv4Writer<'a> {
12    pub bytes: &'a mut [u8],
13}
14
15impl<'a> Icmpv4Writer<'a> {
16    /// Creates a new `Icmpv4Writer` from the given slice.
17    #[inline]
18    pub fn new(bytes: &'a mut [u8]) -> Result<Self, &'static str> {
19        if bytes.len() < ICMPV4_HEADER_LENGTH {
20            return Err("Slice is too short to contain an ICMP header.");
21        }
22
23        Ok(Self { bytes })
24    }
25
26    /// Returns the header length in bytes.
27    #[inline]
28    pub fn header_len(&self) -> usize {
29        ICMPV4_HEADER_LENGTH
30    }
31
32    /// Returns the total length of the packet in bytes.
33    #[inline]
34    pub fn packet_len(&self) -> usize {
35        self.bytes.len()
36    }
37
38    /// Returns the identifier field.
39    #[inline]
40    pub fn set_icmp_type(&mut self, icmp_type: u8) {
41        self.bytes[0] = icmp_type;
42    }
43
44    /// Sets the code field.
45    #[inline]
46    pub fn set_icmp_code(&mut self, code: u8) {
47        self.bytes[1] = code;
48    }
49
50    /// Sets the payload field.
51    /// 
52    /// The `PacketBuilder` sets the payload before the checksum.
53    /// 
54    /// That is, because the checksum is invalidated if a payload is set after it.
55    #[inline]
56    pub fn set_payload(&mut self, payload: &[u8]) -> Result<(), &'static str> {
57        let start = self.header_len();
58        let payload_len = payload.len();
59
60        if self.packet_len() - start < payload_len {
61            return Err("Payload is too large to fit in the ICMPv4 packet.");
62        }
63        
64        let end = start + payload_len;
65        self.bytes[start..end].copy_from_slice(payload);
66
67        Ok(())
68    }
69
70    /// Calculates the internet checksum for error checking.
71    ///
72    /// Includes the ICMPv4 header and payload.
73    #[inline]
74    pub fn set_checksum(&mut self) {
75        self.bytes[2] = 0;
76        self.bytes[3] = 0;
77        let checksum = internet_checksum(self.bytes, 0);
78        self.bytes[2] = (checksum >> 8) as u8;
79        self.bytes[3] = (checksum & 0xff) as u8;
80    }
81}
82
83/// Reads an ICMPv4 header.
84#[derive(PartialEq)]
85pub struct Icmpv4Reader<'a> {
86    pub bytes: &'a [u8],
87}
88
89impl<'a> Icmpv4Reader<'a> {
90    /// Creates a new `Icmpv4Reader` from the given slice.
91    #[inline]
92    pub fn new(bytes: &'a [u8]) -> Result<Self, &'static str> {
93        if bytes.len() < ICMPV4_HEADER_LENGTH {
94            return Err("Slice is too short to contain an ICMP header.");
95        }
96
97        Ok(Self { bytes })
98    }
99
100    /// Returns the type field.
101    #[inline]
102    pub fn icmp_type(&self) -> u8 {
103        self.bytes[0]
104    }
105
106    /// Returns the code field.
107    #[inline]
108    pub fn icmp_code(&self) -> u8 {
109        self.bytes[1]
110    }
111
112    /// Returns the checksum field.
113    #[inline]
114    pub fn checksum(&self) -> u16 {
115        ((self.bytes[2] as u16) << 8) | (self.bytes[3] as u16)
116    }
117
118    /// Returns the header length in bytes.
119    #[inline]
120    pub fn header_len(&self) -> usize {
121        ICMPV4_HEADER_LENGTH
122    }
123
124    /// Returns a reference to the header.
125    #[inline]
126    pub fn header(&self) -> &'a [u8] {
127        &self.bytes[..ICMPV4_HEADER_LENGTH]
128    }
129
130    /// Returns a reference to the payload.
131    #[inline]
132    pub fn payload(&self) -> &'a [u8] {
133        &self.bytes[ICMPV4_HEADER_LENGTH..]
134    }
135}
136
137impl fmt::Debug for Icmpv4Reader<'_> {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        f.debug_struct("Icmpv4Packet")
140            .field("type", &self.icmp_type())
141            .field("code", &self.icmp_code())
142            .field("checksum", &self.checksum())
143            .finish()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn getters_and_setters() {
153        // Raw packet.
154        let mut bytes = [0u8; ICMPV4_HEADER_LENGTH];
155
156        // Random values.
157        let icmp_type = 8;
158        let code = 0;
159
160        // Create a ICMPv4 writer.
161        let mut writer = Icmpv4Writer::new(&mut bytes).unwrap();
162
163        // Set the values.
164        writer.set_icmp_type(icmp_type);
165        writer.set_icmp_code(code);
166        writer.set_checksum();
167
168        // Create a ICMP reader.
169        let reader = Icmpv4Reader::new(&bytes).unwrap();
170
171        // Check the values.
172        assert_eq!(reader.icmp_type(), icmp_type);
173        assert_eq!(reader.icmp_code(), code);
174    }
175}