blob: 7eaef40108615644be3524d452e904cc5ddbae38 [file] [log] [blame]
use crate::{err::ValueTooBigError, *};
use arrayvec::ArrayVec;
/// The statically sized data at the start of an ICMPv6 packet (at least the first 8 bytes of an ICMPv6 packet).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Icmpv6Header {
/// Type & type specific values & code.
pub icmp_type: Icmpv6Type,
/// Checksum in the ICMPv6 header.
pub checksum: u16,
}
impl Icmpv6Header {
/// Minimum number of bytes an ICMP header needs to have.
///
/// Note that minimum size can be larger depending on
/// the type and code.
pub const MIN_LEN: usize = 8;
/// Deprecated, use [`Icmpv6Header::MIN_LEN`] instead.
#[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MIN_LEN instead")]
pub const MIN_SERIALIZED_SIZE: usize = Icmpv6Header::MIN_LEN;
/// Maximum number of bytes/octets an Icmpv6Header takes up
/// in serialized form.
///
/// Currently this number is determined by the biggest
/// planned ICMPv6 header type, which is currently the
/// "Neighbor Discovery Protocol" "Redirect" message.
pub const MAX_LEN: usize = 8 + 16 + 16;
/// Deprecated, use [`Icmpv6Header::MAX_LEN`] instead.
#[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MAX_LEN instead")]
pub const MAX_SERIALIZED_SIZE: usize = Icmpv6Header::MAX_LEN;
/// Setups a new header with the checksum being set to 0.
#[inline]
pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header {
Icmpv6Header {
icmp_type,
checksum: 0, // will be filled in later
}
}
/// Creates a [`Icmpv6Header`] with a checksum calculated based
/// on the given payload & ip addresses from the IPv6 header.
pub fn with_checksum(
icmp_type: Icmpv6Type,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<Icmpv6Header, ValueTooBigError<usize>> {
let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?;
Ok(Icmpv6Header {
icmp_type,
checksum,
})
}
/// Reads an icmp6 header from a slice directly and returns a tuple
/// containing the resulting header & unused part of the slice.
#[inline]
pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError> {
let header = Icmpv6Slice::from_slice(slice)?.header();
let len = header.header_len();
Ok((header, &slice[len..]))
}
/// Read a ICMPv6 header from the given reader
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read<T: std::io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, std::io::Error> {
// read the initial 8 bytes
let mut start = [0u8; 8];
reader.read_exact(&mut start)?;
Ok(Icmpv6Slice { slice: &start }.header())
}
/// Write the ICMPv6 header to the given writer.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error> {
writer.write_all(&self.to_bytes())
}
/// Serialized length of the header in bytes/octets.
///
/// Note that this size is not the size of the entire
/// ICMPv6 packet but only the header.
#[inline]
pub fn header_len(&self) -> usize {
self.icmp_type.header_len()
}
/// If the ICMP type has a fixed size returns the number of
/// bytes that should be present after the header of this type.
#[inline]
pub fn fixed_payload_size(&self) -> Option<usize> {
self.icmp_type.fixed_payload_size()
}
/// Updates the checksum of the header.
pub fn update_checksum(
&mut self,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<(), ValueTooBigError<usize>> {
self.checksum = self
.icmp_type
.calc_checksum(source_ip, destination_ip, payload)?;
Ok(())
}
/// Returns the header on the wire bytes.
#[inline]
pub fn to_bytes(&self) -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
let checksum_be = self.checksum.to_be_bytes();
let return_trivial =
|type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
#[rustfmt::skip]
let mut re = ArrayVec::from([
type_u8, code_u8, checksum_be[0], checksum_be[1],
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
]);
// SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
unsafe {
re.set_len(8);
}
re
};
let return_4u8 = |type_u8: u8,
code_u8: u8,
bytes5to8: [u8; 4]|
-> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
#[rustfmt::skip]
let mut re = ArrayVec::from([
type_u8, code_u8, checksum_be[0], checksum_be[1],
bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3],
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
]);
// SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
unsafe {
re.set_len(8);
}
re
};
use crate::{icmpv6::*, Icmpv6Type::*};
match self.icmp_type {
Unknown {
type_u8,
code_u8,
bytes5to8,
} => return_4u8(type_u8, code_u8, bytes5to8),
DestinationUnreachable(header) => return_trivial(TYPE_DST_UNREACH, header.code_u8()),
PacketTooBig { mtu } => return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes()),
TimeExceeded(code) => return_trivial(TYPE_TIME_EXCEEDED, code.code_u8()),
ParameterProblem(header) => return_4u8(
TYPE_PARAMETER_PROBLEM,
header.code.code_u8(),
header.pointer.to_be_bytes(),
),
EchoRequest(echo) => return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes()),
EchoReply(echo) => return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes()),
}
}
}
#[cfg(test)]
mod test {
use crate::{
err::{ValueTooBigError, ValueType},
icmpv6::*,
test_gens::*,
*,
};
use alloc::{format, vec::Vec};
use arrayvec::ArrayVec;
use proptest::prelude::*;
proptest! {
#[test]
fn new(icmp_type in icmpv6_type_any()) {
assert_eq!(
Icmpv6Header::new(icmp_type.clone()),
Icmpv6Header {
icmp_type,
checksum: 0,
}
);
}
}
proptest! {
#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
#[test]
fn with_checksum(
ip_header in ipv6_any(),
icmp_type in icmpv6_type_any(),
// max length is u32::MAX - header_len (7)
bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
payload in proptest::collection::vec(any::<u8>(), 0..1024)
) {
// error case
{
// SAFETY: In case the error is not triggered
// a segmentation fault will be triggered.
let too_big_slice = unsafe {
//NOTE: The pointer must be initialized with a non null value
// otherwise a key constraint of slices is not fulfilled
// which can lead to crashes in release mode.
use core::ptr::NonNull;
core::slice::from_raw_parts(
NonNull::<u8>::dangling().as_ptr(),
bad_len
)
};
assert_eq!(
Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice),
Err(ValueTooBigError{
actual: bad_len,
max_allowed: (core::u32::MAX - 8) as usize,
value_type: ValueType::Icmpv6PayloadLength,
})
);
}
// non error case
assert_eq!(
Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(),
Icmpv6Header {
icmp_type,
checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
}
);
}
}
proptest! {
#[test]
fn from_slice(
icmp_type in icmpv6_type_any(),
checksum in any::<u16>(),
) {
let bytes = {
Icmpv6Header {
icmp_type: icmp_type.clone(),
checksum,
}.to_bytes()
};
// ok case
{
let result = Icmpv6Header::from_slice(&bytes).unwrap();
assert_eq!(
Icmpv6Header{
icmp_type,
checksum,
},
result.0,
);
assert_eq!(&bytes[8..], result.1);
}
// size error case
for length in 0..8 {
assert_eq!(
Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(),
err::LenError{
required_len: bytes.len(),
len: length,
len_source: LenSource::Slice,
layer: err::Layer::Icmpv6,
layer_start_offset: 0
}
);
}
}
}
proptest! {
#[test]
fn read(
icmp_type in icmpv6_type_any(),
checksum in any::<u16>(),
) {
let header = Icmpv6Header {
icmp_type: icmp_type.clone(),
checksum,
};
let bytes = header.to_bytes();
// ok case
{
let mut cursor = std::io::Cursor::new(&bytes);
let result = Icmpv6Header::read(&mut cursor).unwrap();
assert_eq!(header, result,);
assert_eq!(header.header_len() as u64, cursor.position());
}
// size error case
for length in 0..header.header_len() {
let mut cursor = std::io::Cursor::new(&bytes[..length]);
assert!(Icmpv6Header::read(&mut cursor).is_err());
}
}
}
proptest! {
#[test]
fn write(
icmp_type in icmpv6_type_any(),
checksum in any::<u16>(),
bad_len in 0..8usize
) {
// normal case
{
let mut buffer = Vec::with_capacity(icmp_type.header_len());
let header = Icmpv6Header {
icmp_type,
checksum,
};
header.write(&mut buffer).unwrap();
assert_eq!(
&header.to_bytes(),
&buffer[..]
);
}
// error case
{
let mut buffer = [0u8;Icmpv6Header::MAX_LEN];
let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]);
Icmpv6Header {
icmp_type,
checksum,
}.write(&mut writer).unwrap_err();
}
}
}
proptest! {
#[test]
fn header_len(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
assert_eq!(
icmp_type.header_len(),
Icmpv6Header{
icmp_type,
checksum
}.header_len()
);
}
}
proptest! {
#[test]
fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
assert_eq!(
icmp_type.fixed_payload_size(),
Icmpv6Header{
icmp_type,
checksum
}.fixed_payload_size()
);
}
}
proptest! {
#[test]
#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
fn update_checksum(
ip_header in ipv6_any(),
icmp_type in icmpv6_type_any(),
start_checksum in any::<u16>(),
// max length is u32::MAX - header_len (7)
bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
payload in proptest::collection::vec(any::<u8>(), 0..1024)
) {
// error case
{
// SAFETY: In case the error is not triggered
// a segmentation fault will be triggered.
let too_big_slice = unsafe {
//NOTE: The pointer must be initialized with a non null value
// otherwise a key constraint of slices is not fulfilled
// which can lead to crashes in release mode.
use core::ptr::NonNull;
core::slice::from_raw_parts(
NonNull::<u8>::dangling().as_ptr(),
bad_len
)
};
assert_eq!(
Icmpv6Header{
icmp_type,
checksum: 0
}.update_checksum(ip_header.source, ip_header.destination, too_big_slice),
Err(ValueTooBigError{
actual: bad_len,
max_allowed: (u32::MAX - 8) as usize,
value_type: ValueType::Icmpv6PayloadLength
})
);
}
// normal case
assert_eq!(
{
let mut header = Icmpv6Header{
icmp_type,
checksum: start_checksum,
};
header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap();
header
},
Icmpv6Header{
icmp_type,
checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
}
);
}
}
proptest! {
#[test]
fn to_bytes(
checksum in any::<u16>(),
rand_u32 in any::<u32>(),
rand_4bytes in any::<[u8;4]>(),
) {
use Icmpv6Type::*;
let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
let mut bytes = ArrayVec::<u8, { Icmpv6Header::MAX_LEN }>::new();
bytes.push(type_u8);
bytes.push(code_u8);
bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap();
bytes.try_extend_from_slice(&bytes5to8).unwrap();
bytes
};
let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
with_5to8_bytes(type_u8, code_u8, [0;4])
};
// destination unreachable
for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES {
assert_eq!(
Icmpv6Header{
icmp_type: DestinationUnreachable(code),
checksum
}.to_bytes(),
simple_bytes(TYPE_DST_UNREACH, code_u8)
);
}
// packet too big
assert_eq!(
Icmpv6Header{
icmp_type: PacketTooBig{ mtu: rand_u32 },
checksum
}.to_bytes(),
with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes())
);
// time exceeded
for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES {
assert_eq!(
Icmpv6Header{
icmp_type: TimeExceeded(code),
checksum
}.to_bytes(),
simple_bytes(TYPE_TIME_EXCEEDED, code_u8)
);
}
// parameter problem
for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES {
assert_eq!(
Icmpv6Header{
icmp_type: ParameterProblem(
ParameterProblemHeader{
code,
pointer: rand_u32,
}
),
checksum
}.to_bytes(),
with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes())
);
}
// echo request
assert_eq!(
Icmpv6Header{
icmp_type: EchoRequest(IcmpEchoHeader {
id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
}),
checksum
}.to_bytes(),
with_5to8_bytes(TYPE_ECHO_REQUEST, 0, rand_4bytes)
);
// echo reply
assert_eq!(
Icmpv6Header{
icmp_type: EchoReply(IcmpEchoHeader {
id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
}),
checksum
}.to_bytes(),
with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes)
);
// unknown
for type_u8 in 0..=u8::MAX {
for code_u8 in 0..=u8::MAX {
assert_eq!(
Icmpv6Header{
icmp_type: Unknown {
type_u8,
code_u8,
bytes5to8: rand_4bytes,
},
checksum
}.to_bytes(),
with_5to8_bytes(type_u8, code_u8, rand_4bytes)
);
}
}
}
}
#[test]
fn debug() {
let t = Icmpv6Type::Unknown {
type_u8: 0,
code_u8: 1,
bytes5to8: [2, 3, 4, 5],
};
assert_eq!(
format!(
"{:?}",
Icmpv6Header {
icmp_type: t.clone(),
checksum: 7
}
),
format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7)
);
}
proptest! {
#[test]
fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
let header = Icmpv6Header{ icmp_type, checksum };
assert_eq!(header, header.clone());
}
}
}