etherparse/transport/
icmpv6_header.rs

1use crate::{err::ValueTooBigError, *};
2use arrayvec::ArrayVec;
3
4/// The statically sized data at the start of an ICMPv6 packet (at least the first 8 bytes of an ICMPv6 packet).
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct Icmpv6Header {
7    /// Type & type specific values & code.
8    pub icmp_type: Icmpv6Type,
9    /// Checksum in the ICMPv6 header.
10    pub checksum: u16,
11}
12
13impl Icmpv6Header {
14    /// Minimum number of bytes an ICMP header needs to have.
15    ///
16    /// Note that minimum size can be larger depending on
17    /// the type and code.
18    pub const MIN_LEN: usize = 8;
19
20    /// Deprecated, use [`Icmpv6Header::MIN_LEN`] instead.
21    #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MIN_LEN instead")]
22    pub const MIN_SERIALIZED_SIZE: usize = Icmpv6Header::MIN_LEN;
23
24    /// Maximum number of bytes/octets an Icmpv6Header takes up
25    /// in serialized form.
26    ///
27    /// Currently this number is determined by the biggest
28    /// planned ICMPv6 header type, which is currently the
29    /// "Neighbor Discovery Protocol" "Redirect" message.
30    pub const MAX_LEN: usize = 8 + 16 + 16;
31
32    /// Deprecated, use [`Icmpv6Header::MAX_LEN`] instead.
33    #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MAX_LEN instead")]
34    pub const MAX_SERIALIZED_SIZE: usize = Icmpv6Header::MAX_LEN;
35
36    /// Setups a new header with the checksum being set to 0.
37    #[inline]
38    pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header {
39        Icmpv6Header {
40            icmp_type,
41            checksum: 0, // will be filled in later
42        }
43    }
44
45    /// Creates a [`Icmpv6Header`] with a checksum calculated based
46    /// on the given payload & ip addresses from the IPv6 header.
47    pub fn with_checksum(
48        icmp_type: Icmpv6Type,
49        source_ip: [u8; 16],
50        destination_ip: [u8; 16],
51        payload: &[u8],
52    ) -> Result<Icmpv6Header, ValueTooBigError<usize>> {
53        let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?;
54        Ok(Icmpv6Header {
55            icmp_type,
56            checksum,
57        })
58    }
59
60    /// Reads an icmp6 header from a slice directly and returns a tuple
61    /// containing the resulting header & unused part of the slice.
62    #[inline]
63    pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError> {
64        let header = Icmpv6Slice::from_slice(slice)?.header();
65        let len = header.header_len();
66        Ok((header, &slice[len..]))
67    }
68
69    /// Read a ICMPv6 header from the given reader
70    #[cfg(feature = "std")]
71    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
72    pub fn read<T: std::io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, std::io::Error> {
73        // read the initial 8 bytes
74        let mut start = [0u8; 8];
75        reader.read_exact(&mut start)?;
76        Ok(Icmpv6Slice { slice: &start }.header())
77    }
78
79    /// Write the ICMPv6 header to the given writer.
80    #[cfg(feature = "std")]
81    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
82    pub fn write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error> {
83        writer.write_all(&self.to_bytes())
84    }
85
86    /// Serialized length of the header in bytes/octets.
87    ///
88    /// Note that this size is not the size of the entire
89    /// ICMPv6 packet but only the header.
90    #[inline]
91    pub fn header_len(&self) -> usize {
92        self.icmp_type.header_len()
93    }
94
95    /// If the ICMP type has a fixed size returns the number of
96    /// bytes that should be present after the header of this type.
97    #[inline]
98    pub fn fixed_payload_size(&self) -> Option<usize> {
99        self.icmp_type.fixed_payload_size()
100    }
101
102    /// Updates the checksum of the header.
103    pub fn update_checksum(
104        &mut self,
105        source_ip: [u8; 16],
106        destination_ip: [u8; 16],
107        payload: &[u8],
108    ) -> Result<(), ValueTooBigError<usize>> {
109        self.checksum = self
110            .icmp_type
111            .calc_checksum(source_ip, destination_ip, payload)?;
112        Ok(())
113    }
114
115    /// Returns the header on the wire bytes.
116    #[inline]
117    pub fn to_bytes(&self) -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
118        let checksum_be = self.checksum.to_be_bytes();
119
120        let return_trivial =
121            |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
122                #[rustfmt::skip]
123            let mut re = ArrayVec::from([
124                type_u8, code_u8, checksum_be[0], checksum_be[1],
125                0, 0, 0, 0,
126
127                0, 0, 0, 0,
128                0, 0, 0, 0,
129                0, 0, 0, 0,
130                0, 0, 0, 0,
131
132                0, 0, 0, 0,
133                0, 0, 0, 0,
134                0, 0, 0, 0,
135                0, 0, 0, 0,
136            ]);
137                // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
138                unsafe {
139                    re.set_len(8);
140                }
141                re
142            };
143
144        let return_4u8 = |type_u8: u8,
145                          code_u8: u8,
146                          bytes5to8: [u8; 4]|
147         -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
148            #[rustfmt::skip]
149            let mut re = ArrayVec::from([
150                type_u8, code_u8, checksum_be[0], checksum_be[1],
151                bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3],
152
153                0, 0, 0, 0,
154                0, 0, 0, 0,
155                0, 0, 0, 0,
156                0, 0, 0, 0,
157
158                0, 0, 0, 0,
159                0, 0, 0, 0,
160                0, 0, 0, 0,
161                0, 0, 0, 0,
162            ]);
163            // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
164            unsafe {
165                re.set_len(8);
166            }
167            re
168        };
169
170        use crate::{icmpv6::*, Icmpv6Type::*};
171        match self.icmp_type {
172            Unknown {
173                type_u8,
174                code_u8,
175                bytes5to8,
176            } => return_4u8(type_u8, code_u8, bytes5to8),
177            DestinationUnreachable(header) => return_trivial(TYPE_DST_UNREACH, header.code_u8()),
178            PacketTooBig { mtu } => return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes()),
179            TimeExceeded(code) => return_trivial(TYPE_TIME_EXCEEDED, code.code_u8()),
180            ParameterProblem(header) => return_4u8(
181                TYPE_PARAMETER_PROBLEM,
182                header.code.code_u8(),
183                header.pointer.to_be_bytes(),
184            ),
185            EchoRequest(echo) => return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes()),
186            EchoReply(echo) => return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes()),
187        }
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use crate::{
194        err::{ValueTooBigError, ValueType},
195        icmpv6::*,
196        test_gens::*,
197        *,
198    };
199    use alloc::{format, vec::Vec};
200    use arrayvec::ArrayVec;
201    use proptest::prelude::*;
202
203    proptest! {
204        #[test]
205        fn new(icmp_type in icmpv6_type_any()) {
206            assert_eq!(
207                Icmpv6Header::new(icmp_type.clone()),
208                Icmpv6Header {
209                    icmp_type,
210                    checksum: 0,
211                }
212            );
213        }
214    }
215
216    proptest! {
217        #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
218        #[test]
219        fn with_checksum(
220            ip_header in ipv6_any(),
221            icmp_type in icmpv6_type_any(),
222            // max length is u32::MAX - header_len (7)
223            bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
224            payload in proptest::collection::vec(any::<u8>(), 0..1024)
225        ) {
226
227            // error case
228            {
229                // SAFETY: In case the error is not triggered
230                //         a segmentation fault will be triggered.
231                let too_big_slice = unsafe {
232                    //NOTE: The pointer must be initialized with a non null value
233                    //      otherwise a key constraint of slices is not fulfilled
234                    //      which can lead to crashes in release mode.
235                    use core::ptr::NonNull;
236                    core::slice::from_raw_parts(
237                        NonNull::<u8>::dangling().as_ptr(),
238                        bad_len
239                    )
240                };
241                assert_eq!(
242                    Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice),
243                    Err(ValueTooBigError{
244                        actual: bad_len,
245                        max_allowed: (core::u32::MAX - 8) as usize,
246                        value_type: ValueType::Icmpv6PayloadLength,
247                    })
248                );
249            }
250
251            // non error case
252            assert_eq!(
253                Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(),
254                Icmpv6Header {
255                    icmp_type,
256                    checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
257                }
258            );
259        }
260    }
261
262    proptest! {
263        #[test]
264        fn from_slice(
265            icmp_type in icmpv6_type_any(),
266            checksum in any::<u16>(),
267        ) {
268            let bytes = {
269                Icmpv6Header {
270                    icmp_type: icmp_type.clone(),
271                    checksum,
272                }.to_bytes()
273            };
274
275            // ok case
276            {
277                let result = Icmpv6Header::from_slice(&bytes).unwrap();
278                assert_eq!(
279                    Icmpv6Header{
280                        icmp_type,
281                        checksum,
282                    },
283                    result.0,
284                );
285                assert_eq!(&bytes[8..], result.1);
286            }
287
288
289            // size error case
290            for length in 0..8 {
291                assert_eq!(
292                    Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(),
293                    err::LenError{
294                        required_len: bytes.len(),
295                        len: length,
296                        len_source: LenSource::Slice,
297                        layer: err::Layer::Icmpv6,
298                        layer_start_offset: 0
299                    }
300                );
301            }
302        }
303    }
304
305    proptest! {
306        #[test]
307        fn read(
308            icmp_type in icmpv6_type_any(),
309            checksum in any::<u16>(),
310        ) {
311            let header = Icmpv6Header {
312                icmp_type: icmp_type.clone(),
313                checksum,
314            };
315            let bytes = header.to_bytes();
316
317            // ok case
318            {
319                let mut cursor = std::io::Cursor::new(&bytes);
320                let result = Icmpv6Header::read(&mut cursor).unwrap();
321                assert_eq!(header, result,);
322                assert_eq!(header.header_len() as u64, cursor.position());
323            }
324
325            // size error case
326            for length in 0..header.header_len() {
327                let mut cursor = std::io::Cursor::new(&bytes[..length]);
328                assert!(Icmpv6Header::read(&mut cursor).is_err());
329            }
330        }
331    }
332
333    proptest! {
334        #[test]
335        fn write(
336            icmp_type in icmpv6_type_any(),
337            checksum in any::<u16>(),
338            bad_len in 0..8usize
339        ) {
340            // normal case
341            {
342                let mut buffer = Vec::with_capacity(icmp_type.header_len());
343                let header = Icmpv6Header {
344                    icmp_type,
345                    checksum,
346                };
347                header.write(&mut buffer).unwrap();
348                assert_eq!(
349                    &header.to_bytes(),
350                    &buffer[..]
351                );
352            }
353
354            // error case
355            {
356                let mut buffer = [0u8;Icmpv6Header::MAX_LEN];
357                let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]);
358                Icmpv6Header {
359                    icmp_type,
360                    checksum,
361                }.write(&mut writer).unwrap_err();
362            }
363        }
364    }
365
366    proptest! {
367        #[test]
368        fn header_len(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
369            assert_eq!(
370                icmp_type.header_len(),
371                Icmpv6Header{
372                    icmp_type,
373                    checksum
374                }.header_len()
375            );
376        }
377    }
378
379    proptest! {
380        #[test]
381        fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
382            assert_eq!(
383                icmp_type.fixed_payload_size(),
384                Icmpv6Header{
385                    icmp_type,
386                    checksum
387                }.fixed_payload_size()
388            );
389        }
390    }
391
392    proptest! {
393        #[test]
394        #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
395        fn update_checksum(
396            ip_header in ipv6_any(),
397            icmp_type in icmpv6_type_any(),
398            start_checksum in any::<u16>(),
399            // max length is u32::MAX - header_len (7)
400            bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
401            payload in proptest::collection::vec(any::<u8>(), 0..1024)
402        ) {
403
404            // error case
405            {
406                // SAFETY: In case the error is not triggered
407                //         a segmentation fault will be triggered.
408                let too_big_slice = unsafe {
409                    //NOTE: The pointer must be initialized with a non null value
410                    //      otherwise a key constraint of slices is not fulfilled
411                    //      which can lead to crashes in release mode.
412                    use core::ptr::NonNull;
413                    core::slice::from_raw_parts(
414                        NonNull::<u8>::dangling().as_ptr(),
415                        bad_len
416                    )
417                };
418                assert_eq!(
419                    Icmpv6Header{
420                        icmp_type,
421                        checksum: 0
422                    }.update_checksum(ip_header.source, ip_header.destination, too_big_slice),
423                    Err(ValueTooBigError{
424                        actual: bad_len,
425                        max_allowed: (u32::MAX - 8) as usize,
426                        value_type: ValueType::Icmpv6PayloadLength
427                    })
428                );
429            }
430
431            // normal case
432            assert_eq!(
433                {
434                    let mut header = Icmpv6Header{
435                        icmp_type,
436                        checksum: start_checksum,
437                    };
438                    header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap();
439                    header
440                },
441                Icmpv6Header{
442                    icmp_type,
443                    checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
444                }
445            );
446        }
447    }
448
449    proptest! {
450        #[test]
451        fn to_bytes(
452            checksum in any::<u16>(),
453            rand_u32 in any::<u32>(),
454            rand_4bytes in any::<[u8;4]>(),
455        ) {
456            use Icmpv6Type::*;
457
458            let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
459                let mut bytes = ArrayVec::<u8, { Icmpv6Header::MAX_LEN }>::new();
460                bytes.push(type_u8);
461                bytes.push(code_u8);
462                bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap();
463                bytes.try_extend_from_slice(&bytes5to8).unwrap();
464                bytes
465            };
466
467            let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
468                with_5to8_bytes(type_u8, code_u8, [0;4])
469            };
470
471            // destination unreachable
472            for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES {
473                assert_eq!(
474                    Icmpv6Header{
475                        icmp_type: DestinationUnreachable(code),
476                        checksum
477                    }.to_bytes(),
478                    simple_bytes(TYPE_DST_UNREACH, code_u8)
479                );
480            }
481
482            // packet too big
483            assert_eq!(
484                Icmpv6Header{
485                    icmp_type: PacketTooBig{ mtu: rand_u32 },
486                    checksum
487                }.to_bytes(),
488                with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes())
489            );
490
491            // time exceeded
492            for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES {
493                assert_eq!(
494                    Icmpv6Header{
495                        icmp_type: TimeExceeded(code),
496                        checksum
497                    }.to_bytes(),
498                    simple_bytes(TYPE_TIME_EXCEEDED, code_u8)
499                );
500            }
501
502            // parameter problem
503            for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES {
504                assert_eq!(
505                    Icmpv6Header{
506                        icmp_type: ParameterProblem(
507                            ParameterProblemHeader{
508                                code,
509                                pointer: rand_u32,
510                            }
511                        ),
512                        checksum
513                    }.to_bytes(),
514                    with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes())
515                );
516            }
517
518            // echo request
519            assert_eq!(
520                Icmpv6Header{
521                    icmp_type: EchoRequest(IcmpEchoHeader {
522                        id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
523                        seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
524                    }),
525                    checksum
526                }.to_bytes(),
527                with_5to8_bytes(TYPE_ECHO_REQUEST, 0, rand_4bytes)
528            );
529
530            // echo reply
531            assert_eq!(
532                Icmpv6Header{
533                    icmp_type: EchoReply(IcmpEchoHeader {
534                        id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
535                        seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
536                    }),
537                    checksum
538                }.to_bytes(),
539                with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes)
540            );
541
542            // unknown
543            for type_u8 in 0..=u8::MAX {
544                for code_u8 in 0..=u8::MAX {
545                    assert_eq!(
546                        Icmpv6Header{
547                            icmp_type: Unknown {
548                                type_u8,
549                                code_u8,
550                                bytes5to8: rand_4bytes,
551                            },
552                            checksum
553                        }.to_bytes(),
554                        with_5to8_bytes(type_u8, code_u8, rand_4bytes)
555                    );
556                }
557            }
558        }
559    }
560
561    #[test]
562    fn debug() {
563        let t = Icmpv6Type::Unknown {
564            type_u8: 0,
565            code_u8: 1,
566            bytes5to8: [2, 3, 4, 5],
567        };
568        assert_eq!(
569            format!(
570                "{:?}",
571                Icmpv6Header {
572                    icmp_type: t.clone(),
573                    checksum: 7
574                }
575            ),
576            format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7)
577        );
578    }
579
580    proptest! {
581        #[test]
582        fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
583            let header = Icmpv6Header{ icmp_type, checksum };
584            assert_eq!(header, header.clone());
585        }
586    }
587}