async-icmp 0.2.1

Async ICMP library
Documentation
//! ICMP message encoding and decoding.

use crate::IcmpVersion;

#[cfg(test)]
mod tests;

pub mod decode;
pub mod echo;

/// Encode an ICMP message to be sent via an [`crate::socket::IcmpSocket`].
pub trait EncodeIcmpMessage<V: IcmpVersion> {
    /// Encode the message per the spec for `V`.
    fn encode(&mut self) -> &mut IcmpMessageBuffer;
}

/// A helper for constructing ICMP packets.
#[derive(Clone, Debug)]
pub struct IcmpMessageBuffer {
    /// Message bytes:
    ///
    /// - 0: type
    /// - 1: code
    /// - 2-3: checksum
    /// - 4+: per-type body
    ///
    /// The buffer is always at least 4 bytes long.
    buf: Vec<u8>,
}

impl IcmpMessageBuffer {
    const ICMP_HDR_LEN: usize = 4;

    /// Create a new buffer with the provided ICMP type `typ`, `code`, and `body`.
    pub fn new(typ: u8, code: u8, body: &[u8]) -> Self {
        let mut buf = vec![typ, code, 0, 0];
        buf.extend_from_slice(body);
        Self { buf }
    }

    /// Set byte 0 with the ICMP message type
    pub fn set_type(&mut self, typ: u8) {
        self.buf[0] = typ;
    }

    /// Set byte 1 with the ICMP message code
    pub fn set_code(&mut self, code: u8) {
        self.buf[1] = code;
    }

    /// Access the message body as a slice.
    pub fn body(&self) -> &[u8] {
        &self.buf[Self::ICMP_HDR_LEN..]
    }

    /// Access the message body as a mutable slice.
    pub fn body_mut(&mut self) -> &mut [u8] {
        &mut self.buf[Self::ICMP_HDR_LEN..]
    }

    /// Set the type-specific ICMP message body
    pub fn set_body(&mut self, body: impl IntoIterator<Item = u8>) {
        self.truncate_body(0);
        self.extend_body(body);
    }

    /// Append `body_suffix` to the buffer, leaving the existing body in place
    pub fn extend_body(&mut self, body_suffix: impl IntoIterator<Item = u8>) {
        self.buf.extend(body_suffix);
    }

    /// Truncate the message body at `body_len` bytes
    pub fn truncate_body(&mut self, body_len: usize) {
        self.buf.truncate(Self::ICMP_HDR_LEN + body_len);
    }

    /// Populate bytes 2-3 with the IPv4 ones complement checksum.
    pub(crate) fn calculate_icmpv4_checksum(&mut self) {
        // checksum starts at zero
        self.buf[2..4].fill(0);
        let checksum = ones_complement_checksum(&self.buf);
        self.buf[2..4].copy_from_slice(&checksum.to_be_bytes());
    }

    /// The entire buffer (ICMP header and body)
    pub fn as_slice(&self) -> &[u8] {
        &self.buf
    }
}

/// RFC 792 ICMPv4 message types.
///
/// Provided as a reference for the message type magic numbers.
#[repr(u8)]
#[allow(missing_docs)]
pub enum IcmpV4MsgType {
    EchoReply = 0,
    DestinationUnreachable = 3,
    SourceQuench = 4,
    Redirect = 5,
    EchoRequest = 8,
    TimeExceeded = 11,
    ParameterProblem = 12,
    Timestamp = 13,
    TimestampReply = 14,
    InfoRequest = 15,
    InfoReply = 16,
}

/// RFC 4443 ICMPv6 message types.
///
/// Provided as a reference for the message type magic numbers.
#[repr(u8)]
#[allow(missing_docs)]
pub enum IcmpV6MsgType {
    DestinationUnreachable = 1,
    PacketTooBig = 2,
    TimeExceeded = 3,
    ParameterProblem = 4,
    PrivateExperimentationError1 = 100,
    PrivateExperimentationError2 = 101,
    ReservedForExpansionError = 127,
    EchoRequest = 128,
    EchoReply = 129,
    PrivateExperimentationInfo1 = 200,
    PrivateExperimentationInfo2 = 201,
    ReservedForExpansionInfo = 255,
}

/// See https://www.rfc-editor.org/rfc/rfc1071 section 4.1
fn ones_complement_checksum(data: &[u8]) -> u16 {
    let last = if data.len() % 2 == 1 {
        u16::from(data.last().copied().unwrap()) << 8
    } else {
        0_u16
    };

    let mut sum = data
        .chunks_exact(2)
        .map(|chunk| u16::from_be_bytes(chunk.try_into().unwrap()))
        .fold(0_u32, |accum, num| accum.wrapping_add(num.into()))
        .wrapping_add(last.into());

    while (sum >> 16) > 0 {
        sum = (sum & 0xffff) + (sum >> 16);
    }

    !(sum as u16)
}