Skip to main content

net_wire/icmpv6/
checksum.rs

1//! ICMPv6 checksum integration for IPv6 pseudoheaders.
2
3use core::fmt;
4
5use crate::icmpv6::message::{Icmpv6Message, Icmpv6MessageMut};
6use crate::internet_checksum::{self, add_ipv6_pseudoheader};
7use crate::ipv6::address::Ipv6Address;
8
9const ICMPV6_NEXT_HEADER: u8 = 58;
10
11/// Failure to represent an ICMPv6 message length in an IPv6 pseudoheader.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum Icmpv6ChecksumError {
14    /// The message length cannot be represented by the pseudoheader's length field.
15    LengthTooLarge {
16        /// Largest representable message length on this platform.
17        maximum: usize,
18        /// Actual message length.
19        actual: usize,
20    },
21}
22
23impl fmt::Display for Icmpv6ChecksumError {
24    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::LengthTooLarge { maximum, actual } => write!(
27                formatter,
28                "transport message length {actual} exceeds pseudoheader maximum {maximum}"
29            ),
30        }
31    }
32}
33
34impl Icmpv6Message<'_> {
35    /// Checks this ICMPv6 message against its IPv6 pseudoheader.
36    pub fn checksum_is_valid_ipv6(
37        &self,
38        source: Ipv6Address,
39        destination: Ipv6Address,
40    ) -> Result<bool, Icmpv6ChecksumError> {
41        let length = icmpv6_length(self.as_bytes())?;
42        Ok(icmpv6_checksum_sum(self.as_bytes(), source, destination, length) == 0xffff)
43    }
44}
45
46impl Icmpv6MessageMut<'_> {
47    /// Checks this ICMPv6 message against its IPv6 pseudoheader.
48    pub fn checksum_is_valid_ipv6(
49        &self,
50        source: Ipv6Address,
51        destination: Ipv6Address,
52    ) -> Result<bool, Icmpv6ChecksumError> {
53        let length = icmpv6_length(self.as_bytes())?;
54        Ok(icmpv6_checksum_sum(self.as_bytes(), source, destination, length) == 0xffff)
55    }
56
57    /// Recomputes the ICMPv6 checksum using the supplied IPv6 pseudoheader.
58    pub fn update_checksum_ipv6(
59        &mut self,
60        source: Ipv6Address,
61        destination: Ipv6Address,
62    ) -> Result<(), Icmpv6ChecksumError> {
63        let length = icmpv6_length(self.as_bytes())?;
64        let bytes = self.as_bytes_mut();
65        bytes[2..4].fill(0);
66        let value =
67            internet_checksum::checksum(icmpv6_checksum_sum(bytes, source, destination, length));
68        bytes[2..4].copy_from_slice(&value.to_be_bytes());
69        Ok(())
70    }
71}
72
73fn icmpv6_length(bytes: &[u8]) -> Result<u32, Icmpv6ChecksumError> {
74    let maximum = usize::try_from(u32::MAX).unwrap_or(usize::MAX);
75    u32::try_from(bytes.len()).map_err(|_| Icmpv6ChecksumError::LengthTooLarge {
76        maximum,
77        actual: bytes.len(),
78    })
79}
80
81fn icmpv6_checksum_sum(
82    bytes: &[u8],
83    source: Ipv6Address,
84    destination: Ipv6Address,
85    length: u32,
86) -> u32 {
87    internet_checksum::add_bytes(
88        add_ipv6_pseudoheader(
89            0,
90            source.octets(),
91            destination.octets(),
92            ICMPV6_NEXT_HEADER,
93            length,
94        ),
95        bytes,
96    )
97}