Thanks to visit codestin.com
Credit goes to docs.rs

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            RouterSolicitation => return_trivial(TYPE_ROUTER_SOLICITATION, 0),
188            RouterAdvertisement(header) => {
189                return_4u8(TYPE_ROUTER_ADVERTISEMENT, 0, header.to_bytes())
190            }
191            NeighborSolicitation => return_trivial(TYPE_NEIGHBOR_SOLICITATION, 0),
192            NeighborAdvertisement(header) => {
193                return_4u8(TYPE_NEIGHBOR_ADVERTISEMENT, 0, header.to_bytes())
194            }
195            Redirect => return_trivial(TYPE_REDIRECT_MESSAGE, 0),
196        }
197    }
198}
199
200#[cfg(test)]
201mod test {
202    use crate::{
203        err::{ValueTooBigError, ValueType},
204        icmpv6::*,
205        test_gens::*,
206        *,
207    };
208    use alloc::{format, vec::Vec};
209    use arrayvec::ArrayVec;
210    use proptest::prelude::*;
211
212    proptest! {
213        #[test]
214        fn new(icmp_type in icmpv6_type_any()) {
215            assert_eq!(
216                Icmpv6Header::new(icmp_type.clone()),
217                Icmpv6Header {
218                    icmp_type,
219                    checksum: 0,
220                }
221            );
222        }
223    }
224
225    proptest! {
226        #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
227        #[test]
228        fn with_checksum(
229            ip_header in ipv6_any(),
230            icmp_type in icmpv6_type_any(),
231            // max length is u32::MAX - header_len (7)
232            bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
233            payload in proptest::collection::vec(any::<u8>(), 0..1024)
234        ) {
235
236            // error case
237            {
238                // SAFETY: In case the error is not triggered
239                //         a segmentation fault will be triggered.
240                let too_big_slice = unsafe {
241                    //NOTE: The pointer must be initialized with a non null value
242                    //      otherwise a key constraint of slices is not fulfilled
243                    //      which can lead to crashes in release mode.
244                    use core::ptr::NonNull;
245                    core::slice::from_raw_parts(
246                        NonNull::<u8>::dangling().as_ptr(),
247                        bad_len
248                    )
249                };
250                assert_eq!(
251                    Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice),
252                    Err(ValueTooBigError{
253                        actual: bad_len,
254                        max_allowed: (core::u32::MAX - 8) as usize,
255                        value_type: ValueType::Icmpv6PayloadLength,
256                    })
257                );
258            }
259
260            // non error case
261            assert_eq!(
262                Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(),
263                Icmpv6Header {
264                    icmp_type,
265                    checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
266                }
267            );
268        }
269    }
270
271    proptest! {
272        #[test]
273        fn from_slice(
274            icmp_type in icmpv6_type_any(),
275            checksum in any::<u16>(),
276        ) {
277            let bytes = {
278                Icmpv6Header {
279                    icmp_type: icmp_type.clone(),
280                    checksum,
281                }.to_bytes()
282            };
283
284            // ok case
285            {
286                let result = Icmpv6Header::from_slice(&bytes).unwrap();
287                assert_eq!(
288                    Icmpv6Header{
289                        icmp_type,
290                        checksum,
291                    },
292                    result.0,
293                );
294                assert_eq!(&bytes[8..], result.1);
295            }
296
297
298            // size error case
299            for length in 0..8 {
300                assert_eq!(
301                    Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(),
302                    err::LenError{
303                        required_len: bytes.len(),
304                        len: length,
305                        len_source: LenSource::Slice,
306                        layer: err::Layer::Icmpv6,
307                        layer_start_offset: 0
308                    }
309                );
310            }
311        }
312    }
313
314    proptest! {
315        #[test]
316        fn read(
317            icmp_type in icmpv6_type_any(),
318            checksum in any::<u16>(),
319        ) {
320            let header = Icmpv6Header {
321                icmp_type: icmp_type.clone(),
322                checksum,
323            };
324            let bytes = header.to_bytes();
325
326            // ok case
327            {
328                let mut cursor = std::io::Cursor::new(&bytes);
329                let result = Icmpv6Header::read(&mut cursor).unwrap();
330                assert_eq!(header, result,);
331                assert_eq!(header.header_len() as u64, cursor.position());
332            }
333
334            // size error case
335            for length in 0..header.header_len() {
336                let mut cursor = std::io::Cursor::new(&bytes[..length]);
337                assert!(Icmpv6Header::read(&mut cursor).is_err());
338            }
339        }
340    }
341
342    proptest! {
343        #[test]
344        fn write(
345            icmp_type in icmpv6_type_any(),
346            checksum in any::<u16>(),
347            bad_len in 0..8usize
348        ) {
349            // normal case
350            {
351                let mut buffer = Vec::with_capacity(icmp_type.header_len());
352                let header = Icmpv6Header {
353                    icmp_type,
354                    checksum,
355                };
356                header.write(&mut buffer).unwrap();
357                assert_eq!(
358                    &header.to_bytes(),
359                    &buffer[..]
360                );
361            }
362
363            // error case
364            {
365                let mut buffer = [0u8;Icmpv6Header::MAX_LEN];
366                let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]);
367                Icmpv6Header {
368                    icmp_type,
369                    checksum,
370                }.write(&mut writer).unwrap_err();
371            }
372        }
373    }
374
375    proptest! {
376        #[test]
377        fn header_len(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
378            assert_eq!(
379                icmp_type.header_len(),
380                Icmpv6Header{
381                    icmp_type,
382                    checksum
383                }.header_len()
384            );
385        }
386    }
387
388    proptest! {
389        #[test]
390        fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
391            assert_eq!(
392                icmp_type.fixed_payload_size(),
393                Icmpv6Header{
394                    icmp_type,
395                    checksum
396                }.fixed_payload_size()
397            );
398        }
399    }
400
401    proptest! {
402        #[test]
403        #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
404        fn update_checksum(
405            ip_header in ipv6_any(),
406            icmp_type in icmpv6_type_any(),
407            start_checksum in any::<u16>(),
408            // max length is u32::MAX - header_len (7)
409            bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
410            payload in proptest::collection::vec(any::<u8>(), 0..1024)
411        ) {
412
413            // error case
414            {
415                // SAFETY: In case the error is not triggered
416                //         a segmentation fault will be triggered.
417                let too_big_slice = unsafe {
418                    //NOTE: The pointer must be initialized with a non null value
419                    //      otherwise a key constraint of slices is not fulfilled
420                    //      which can lead to crashes in release mode.
421                    use core::ptr::NonNull;
422                    core::slice::from_raw_parts(
423                        NonNull::<u8>::dangling().as_ptr(),
424                        bad_len
425                    )
426                };
427                assert_eq!(
428                    Icmpv6Header{
429                        icmp_type,
430                        checksum: 0
431                    }.update_checksum(ip_header.source, ip_header.destination, too_big_slice),
432                    Err(ValueTooBigError{
433                        actual: bad_len,
434                        max_allowed: (u32::MAX - 8) as usize,
435                        value_type: ValueType::Icmpv6PayloadLength
436                    })
437                );
438            }
439
440            // normal case
441            assert_eq!(
442                {
443                    let mut header = Icmpv6Header{
444                        icmp_type,
445                        checksum: start_checksum,
446                    };
447                    header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap();
448                    header
449                },
450                Icmpv6Header{
451                    icmp_type,
452                    checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
453                }
454            );
455        }
456    }
457
458    proptest! {
459        #[test]
460        fn to_bytes(
461            checksum in any::<u16>(),
462            rand_u32 in any::<u32>(),
463            rand_4bytes in any::<[u8;4]>(),
464            rand_bool0 in any::<bool>(),
465            rand_bool1 in any::<bool>(),
466            rand_bool2 in any::<bool>(),
467        ) {
468            use Icmpv6Type::*;
469
470            let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
471                let mut bytes = ArrayVec::<u8, { Icmpv6Header::MAX_LEN }>::new();
472                bytes.push(type_u8);
473                bytes.push(code_u8);
474                bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap();
475                bytes.try_extend_from_slice(&bytes5to8).unwrap();
476                bytes
477            };
478
479            let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
480                with_5to8_bytes(type_u8, code_u8, [0;4])
481            };
482
483            // destination unreachable
484            for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES {
485                assert_eq!(
486                    Icmpv6Header{
487                        icmp_type: DestinationUnreachable(code),
488                        checksum
489                    }.to_bytes(),
490                    simple_bytes(TYPE_DST_UNREACH, code_u8)
491                );
492            }
493
494            // packet too big
495            assert_eq!(
496                Icmpv6Header{
497                    icmp_type: PacketTooBig{ mtu: rand_u32 },
498                    checksum
499                }.to_bytes(),
500                with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes())
501            );
502
503            // time exceeded
504            for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES {
505                assert_eq!(
506                    Icmpv6Header{
507                        icmp_type: TimeExceeded(code),
508                        checksum
509                    }.to_bytes(),
510                    simple_bytes(TYPE_TIME_EXCEEDED, code_u8)
511                );
512            }
513
514            // parameter problem
515            for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES {
516                assert_eq!(
517                    Icmpv6Header{
518                        icmp_type: ParameterProblem(
519                            ParameterProblemHeader{
520                                code,
521                                pointer: rand_u32,
522                            }
523                        ),
524                        checksum
525                    }.to_bytes(),
526                    with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes())
527                );
528            }
529
530            // echo request
531            assert_eq!(
532                Icmpv6Header{
533                    icmp_type: EchoRequest(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_REQUEST, 0, rand_4bytes)
540            );
541
542            // echo reply
543            assert_eq!(
544                Icmpv6Header{
545                    icmp_type: EchoReply(IcmpEchoHeader {
546                        id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
547                        seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
548                    }),
549                    checksum
550                }.to_bytes(),
551                with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes)
552            );
553
554            // neighbor solicitation
555            assert_eq!(
556                Icmpv6Header{
557                    icmp_type: NeighborSolicitation,
558                    checksum
559                }.to_bytes(),
560                with_5to8_bytes(TYPE_NEIGHBOR_SOLICITATION, 0, [0;4])
561            );
562
563            // neighbor advertisement
564            assert_eq!(
565                Icmpv6Header{
566                    icmp_type: NeighborAdvertisement(
567                        NeighborAdvertisementHeader {
568                            router: rand_bool0,
569                            solicited: rand_bool1,
570                            r#override: rand_bool2,
571                        }
572                    ),
573                    checksum
574                }.to_bytes(),
575                with_5to8_bytes(TYPE_NEIGHBOR_ADVERTISEMENT, 0, [
576                    if rand_bool0 {
577                        NeighborAdvertisementHeader::ROUTER_MASK
578                    } else {
579                        0
580                    } | if rand_bool1 {
581                        NeighborAdvertisementHeader::SOLICITED_MASK
582                    } else {
583                        0
584                    } | if rand_bool2 {
585                        NeighborAdvertisementHeader::OVERRIDE_MASK
586                    } else {
587                        0
588                    }, 0, 0, 0
589                ])
590            );
591
592            // unknown
593            for type_u8 in 0..=u8::MAX {
594                for code_u8 in 0..=u8::MAX {
595                    assert_eq!(
596                        Icmpv6Header{
597                            icmp_type: Unknown {
598                                type_u8,
599                                code_u8,
600                                bytes5to8: rand_4bytes,
601                            },
602                            checksum
603                        }.to_bytes(),
604                        with_5to8_bytes(type_u8, code_u8, rand_4bytes)
605                    );
606                }
607            }
608        }
609    }
610
611    #[test]
612    fn debug() {
613        let t = Icmpv6Type::Unknown {
614            type_u8: 0,
615            code_u8: 1,
616            bytes5to8: [2, 3, 4, 5],
617        };
618        assert_eq!(
619            format!(
620                "{:?}",
621                Icmpv6Header {
622                    icmp_type: t.clone(),
623                    checksum: 7
624                }
625            ),
626            format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7)
627        );
628    }
629
630    proptest! {
631        #[test]
632        fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
633            let header = Icmpv6Header{ icmp_type, checksum };
634            assert_eq!(header, header.clone());
635        }
636    }
637}