zero_packet/network/
icmpv6.rs

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