diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs b/etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs index 94287468..b4388223 100644 --- a/etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs +++ b/etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs @@ -45,10 +45,7 @@ impl Icmpv6Payload { /// Write the fixed payload bytes to the writer. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write( - &self, - writer: &mut T, - ) -> Result<(), std::io::Error> { + pub fn write(&self, writer: &mut T) -> Result<(), std::io::Error> { match self { Icmpv6Payload::RouterSolicitation(value) => writer.write_all(&value.to_bytes()), Icmpv6Payload::RouterAdvertisement(value) => writer.write_all(&value.to_bytes()), @@ -62,7 +59,6 @@ impl Icmpv6Payload { #[cfg(test)] mod tests { use super::*; - use alloc::format; use proptest::prelude::*; #[test] diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/destination_unreachable_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/destination_unreachable_payload_slice.rs new file mode 100644 index 00000000..44e6a1b7 --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/destination_unreachable_payload_slice.rs @@ -0,0 +1,55 @@ +use crate::{err, LaxIpSlice}; + +/// Borrowed payload of a Destination Unreachable message (RFC 4443, Section 3.1). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Unused | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | As much of invoking packet | +/// + as possible without the ICMPv6 packet + +/// | exceeding the minimum IPv6 MTU [IPv6] | +/// ``` +/// +/// In this crate, `Type`, `Code`, and `Unused` are represented by +/// [`crate::Icmpv6Type::DestinationUnreachable`]. This slice represents the +/// invoking packet bytes after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DestinationUnreachablePayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> DestinationUnreachablePayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the invoking packet bytes carried by the message. + pub fn invoking_packet(&self) -> &'a [u8] { + self.slice + } + + /// Decodes the invoking packet bytes as a lax IP slice. + pub fn as_lax_ip_slice( + &self, + ) -> Result< + ( + LaxIpSlice<'a>, + Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, + ), + err::ip::LaxHeaderSliceError, + > { + LaxIpSlice::from_slice(self.invoking_packet()) + } +} diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_reply_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_reply_payload_slice.rs new file mode 100644 index 00000000..c2a8cbe9 --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_reply_payload_slice.rs @@ -0,0 +1,41 @@ +use crate::err; + +/// Borrowed payload of an Echo Reply message (RFC 4443, Section 4.2). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Identifier | Sequence Number | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Data ... +/// +-+-+-+-+-+-+-+-+- +/// ``` +/// +/// In this crate, `Type`, `Code`, `Identifier`, and `Sequence Number` are +/// represented by [`crate::Icmpv6Type::EchoReply`]. This slice represents +/// the echoed data after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EchoReplyPayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> EchoReplyPayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the echoed data bytes. + pub fn data(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_request_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_request_payload_slice.rs new file mode 100644 index 00000000..15802d19 --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/echo_request_payload_slice.rs @@ -0,0 +1,41 @@ +use crate::err; + +/// Borrowed payload of an Echo Request message (RFC 4443, Section 4.1). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Identifier | Sequence Number | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Data ... +/// +-+-+-+-+-+-+-+-+- +/// ``` +/// +/// In this crate, `Type`, `Code`, `Identifier`, and `Sequence Number` are +/// represented by [`crate::Icmpv6Type::EchoRequest`]. This slice represents +/// the echoed data after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EchoRequestPayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> EchoRequestPayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the echoed data bytes. + pub fn data(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/mod.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/mod.rs index 55c14adb..c31af967 100644 --- a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/mod.rs +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/mod.rs @@ -1,3 +1,21 @@ +mod destination_unreachable_payload_slice; +pub use destination_unreachable_payload_slice::*; + +mod packet_too_big_payload_slice; +pub use packet_too_big_payload_slice::*; + +mod time_exceeded_payload_slice; +pub use time_exceeded_payload_slice::*; + +mod parameter_problem_payload_slice; +pub use parameter_problem_payload_slice::*; + +mod echo_request_payload_slice; +pub use echo_request_payload_slice::*; + +mod echo_reply_payload_slice; +pub use echo_reply_payload_slice::*; + mod neighbor_advertisement_payload_slice; pub use neighbor_advertisement_payload_slice::*; @@ -19,6 +37,18 @@ use crate::{err, icmpv6::Icmpv6Payload}; #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum Icmpv6PayloadSlice<'a> { + /// Payload of a Destination Unreachable message. + DestinationUnreachable(DestinationUnreachablePayloadSlice<'a>), + /// Payload of a Packet Too Big message. + PacketTooBig(PacketTooBigPayloadSlice<'a>), + /// Payload of a Time Exceeded message. + TimeExceeded(TimeExceededPayloadSlice<'a>), + /// Payload of a Parameter Problem message. + ParameterProblem(ParameterProblemPayloadSlice<'a>), + /// Payload of an Echo Request message. + EchoRequest(EchoRequestPayloadSlice<'a>), + /// Payload of an Echo Reply message. + EchoReply(EchoReplyPayloadSlice<'a>), /// Payload of a Router Solicitation message. RouterSolicitation(RouterSolicitationPayloadSlice<'a>), /// Payload of a Router Advertisement message. @@ -42,6 +72,24 @@ impl<'a> Icmpv6PayloadSlice<'a> { use crate::Icmpv6Type::*; Ok(match icmp_type { + DestinationUnreachable(_) => Icmpv6PayloadSlice::DestinationUnreachable( + DestinationUnreachablePayloadSlice::from_slice(payload)?, + ), + PacketTooBig { mtu: _ } => { + Icmpv6PayloadSlice::PacketTooBig(PacketTooBigPayloadSlice::from_slice(payload)?) + } + TimeExceeded(_) => { + Icmpv6PayloadSlice::TimeExceeded(TimeExceededPayloadSlice::from_slice(payload)?) + } + ParameterProblem(_) => Icmpv6PayloadSlice::ParameterProblem( + ParameterProblemPayloadSlice::from_slice(payload)?, + ), + EchoRequest(_) => { + Icmpv6PayloadSlice::EchoRequest(EchoRequestPayloadSlice::from_slice(payload)?) + } + EchoReply(_) => { + Icmpv6PayloadSlice::EchoReply(EchoReplyPayloadSlice::from_slice(payload)?) + } RouterSolicitation => Icmpv6PayloadSlice::RouterSolicitation( RouterSolicitationPayloadSlice::from_slice(payload)?, ), @@ -66,27 +114,50 @@ impl<'a> Icmpv6PayloadSlice<'a> { ) -> Result, err::LenError> { use crate::icmpv6::*; - // For the currently modeled ND message payloads (RS/RA/NS/NA/Redirect), - // RFC 4861 validation rules require code 0 (quote: "- ICMP Code is 0."). - // See sections 6.1.1, 6.1.2, 7.1.1, 7.1.2, and 8.1. - if 0 != code_u8 { - return Ok(Icmpv6PayloadSlice::Raw(payload)); - } - match type_u8 { - TYPE_ROUTER_SOLICITATION => Ok(Icmpv6PayloadSlice::RouterSolicitation( - RouterSolicitationPayloadSlice::from_slice(payload)?, + TYPE_DST_UNREACH if DestUnreachableCode::from_u8(code_u8).is_some() => { + Ok(Icmpv6PayloadSlice::DestinationUnreachable( + DestinationUnreachablePayloadSlice::from_slice(payload)?, + )) + } + TYPE_PACKET_TOO_BIG if 0 == code_u8 => Ok(Icmpv6PayloadSlice::PacketTooBig( + PacketTooBigPayloadSlice::from_slice(payload)?, )), - TYPE_ROUTER_ADVERTISEMENT => Ok(Icmpv6PayloadSlice::RouterAdvertisement( - RouterAdvertisementPayloadSlice::from_slice(payload)?, + TYPE_TIME_EXCEEDED if TimeExceededCode::from_u8(code_u8).is_some() => Ok( + Icmpv6PayloadSlice::TimeExceeded(TimeExceededPayloadSlice::from_slice(payload)?), + ), + TYPE_PARAMETER_PROBLEM if ParameterProblemCode::from_u8(code_u8).is_some() => { + Ok(Icmpv6PayloadSlice::ParameterProblem( + ParameterProblemPayloadSlice::from_slice(payload)?, + )) + } + TYPE_ECHO_REQUEST if 0 == code_u8 => Ok(Icmpv6PayloadSlice::EchoRequest( + EchoRequestPayloadSlice::from_slice(payload)?, )), - TYPE_NEIGHBOR_SOLICITATION => Ok(Icmpv6PayloadSlice::NeighborSolicitation( - NeighborSolicitationPayloadSlice::from_slice(payload)?, + TYPE_ECHO_REPLY if 0 == code_u8 => Ok(Icmpv6PayloadSlice::EchoReply( + EchoReplyPayloadSlice::from_slice(payload)?, )), - TYPE_NEIGHBOR_ADVERTISEMENT => Ok(Icmpv6PayloadSlice::NeighborAdvertisement( - NeighborAdvertisementPayloadSlice::from_slice(payload)?, + // RFC 4861 sections 6.1.1, 6.1.2, 7.1.1, 7.1.2 and 8.1: + // "ICMP Code is 0." + TYPE_ROUTER_SOLICITATION if 0 == code_u8 => Ok(Icmpv6PayloadSlice::RouterSolicitation( + RouterSolicitationPayloadSlice::from_slice(payload)?, )), - TYPE_REDIRECT_MESSAGE => Ok(Icmpv6PayloadSlice::Redirect( + TYPE_ROUTER_ADVERTISEMENT if 0 == code_u8 => { + Ok(Icmpv6PayloadSlice::RouterAdvertisement( + RouterAdvertisementPayloadSlice::from_slice(payload)?, + )) + } + TYPE_NEIGHBOR_SOLICITATION if 0 == code_u8 => { + Ok(Icmpv6PayloadSlice::NeighborSolicitation( + NeighborSolicitationPayloadSlice::from_slice(payload)?, + )) + } + TYPE_NEIGHBOR_ADVERTISEMENT if 0 == code_u8 => { + Ok(Icmpv6PayloadSlice::NeighborAdvertisement( + NeighborAdvertisementPayloadSlice::from_slice(payload)?, + )) + } + TYPE_REDIRECT_MESSAGE if 0 == code_u8 => Ok(Icmpv6PayloadSlice::Redirect( RedirectPayloadSlice::from_slice(payload)?, )), _ => Ok(Icmpv6PayloadSlice::Raw(payload)), @@ -96,6 +167,12 @@ impl<'a> Icmpv6PayloadSlice<'a> { /// Returns the full borrowed payload bytes. pub fn slice(&self) -> &'a [u8] { match self { + Icmpv6PayloadSlice::DestinationUnreachable(value) => value.slice(), + Icmpv6PayloadSlice::PacketTooBig(value) => value.slice(), + Icmpv6PayloadSlice::TimeExceeded(value) => value.slice(), + Icmpv6PayloadSlice::ParameterProblem(value) => value.slice(), + Icmpv6PayloadSlice::EchoRequest(value) => value.slice(), + Icmpv6PayloadSlice::EchoReply(value) => value.slice(), Icmpv6PayloadSlice::RouterSolicitation(value) => value.slice(), Icmpv6PayloadSlice::RouterAdvertisement(value) => value.slice(), Icmpv6PayloadSlice::NeighborSolicitation(value) => value.slice(), @@ -111,6 +188,12 @@ impl<'a> Icmpv6PayloadSlice<'a> { /// the second tuple element contains the remaining unparsed bytes. pub fn to_payload(&self) -> Option<(Icmpv6Payload, &'a [u8])> { match self { + Icmpv6PayloadSlice::DestinationUnreachable(_) + | Icmpv6PayloadSlice::PacketTooBig(_) + | Icmpv6PayloadSlice::TimeExceeded(_) + | Icmpv6PayloadSlice::ParameterProblem(_) + | Icmpv6PayloadSlice::EchoRequest(_) + | Icmpv6PayloadSlice::EchoReply(_) => None, Icmpv6PayloadSlice::RouterSolicitation(value) => { let (payload, options) = value.to_payload(); Some((Icmpv6Payload::RouterSolicitation(payload), options)) @@ -139,15 +222,57 @@ impl<'a> Icmpv6PayloadSlice<'a> { #[cfg(test)] mod tests { use super::*; - use crate::{err, err::Layer, LenSource}; use crate::icmpv6::{ NeighborAdvertisementPayload, NeighborSolicitationPayload, RedirectPayload, RouterAdvertisementPayload, RouterSolicitationPayload, }; + use crate::{err, err::Layer, LenSource}; use core::net::Ipv6Addr; use proptest::prelude::*; proptest! { + #[test] + fn destination_unreachable(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = DestinationUnreachablePayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.invoking_packet(), &slice[..]); + } + + #[test] + fn packet_too_big(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = PacketTooBigPayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.invoking_packet(), &slice[..]); + } + + #[test] + fn time_exceeded(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = TimeExceededPayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.invoking_packet(), &slice[..]); + } + + #[test] + fn parameter_problem(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = ParameterProblemPayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.invoking_packet(), &slice[..]); + } + + #[test] + fn echo_request(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = EchoRequestPayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.data(), &slice[..]); + } + + #[test] + fn echo_reply(slice in proptest::collection::vec(any::(), 0..64)) { + let actual = EchoReplyPayloadSlice::from_slice(&slice).unwrap(); + assert_eq!(actual.slice(), &slice[..]); + assert_eq!(actual.data(), &slice[..]); + } + #[test] fn router_solicitation(slice in proptest::collection::vec(any::(), 0..64)) { let actual = RouterSolicitationPayloadSlice::from_slice(&slice).unwrap(); @@ -295,4 +420,121 @@ mod tests { RedirectPayloadSlice::from_slice(&[0; 31]) ); } + + #[test] + fn from_type_u8() { + use crate::icmpv6::*; + + let payload = [1, 2, 3, 4]; + + assert_eq!( + Icmpv6PayloadSlice::DestinationUnreachable( + DestinationUnreachablePayloadSlice::from_slice(&payload).unwrap() + ), + Icmpv6PayloadSlice::from_type_u8(TYPE_DST_UNREACH, CODE_DST_UNREACH_PORT, &payload) + .unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_DST_UNREACH, u8::MAX, &payload).unwrap() + ); + + assert_eq!( + Icmpv6PayloadSlice::PacketTooBig( + PacketTooBigPayloadSlice::from_slice(&payload).unwrap() + ), + Icmpv6PayloadSlice::from_type_u8(TYPE_PACKET_TOO_BIG, 0, &payload).unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_PACKET_TOO_BIG, 1, &payload).unwrap() + ); + + assert_eq!( + Icmpv6PayloadSlice::TimeExceeded( + TimeExceededPayloadSlice::from_slice(&payload).unwrap() + ), + Icmpv6PayloadSlice::from_type_u8( + TYPE_TIME_EXCEEDED, + CODE_TIME_EXCEEDED_HOP_LIMIT_EXCEEDED, + &payload + ) + .unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_TIME_EXCEEDED, 2, &payload).unwrap() + ); + + assert_eq!( + Icmpv6PayloadSlice::ParameterProblem( + ParameterProblemPayloadSlice::from_slice(&payload).unwrap() + ), + Icmpv6PayloadSlice::from_type_u8( + TYPE_PARAMETER_PROBLEM, + CODE_PARAM_PROBLEM_UNRECOG_IPV6_OPTION, + &payload + ) + .unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_PARAMETER_PROBLEM, u8::MAX, &payload).unwrap() + ); + + assert_eq!( + Icmpv6PayloadSlice::EchoRequest(EchoRequestPayloadSlice::from_slice(&payload).unwrap()), + Icmpv6PayloadSlice::from_type_u8(TYPE_ECHO_REQUEST, 0, &payload).unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_ECHO_REQUEST, 1, &payload).unwrap() + ); + + assert_eq!( + Icmpv6PayloadSlice::EchoReply(EchoReplyPayloadSlice::from_slice(&payload).unwrap()), + Icmpv6PayloadSlice::from_type_u8(TYPE_ECHO_REPLY, 0, &payload).unwrap() + ); + assert_eq!( + Icmpv6PayloadSlice::Raw(&payload), + Icmpv6PayloadSlice::from_type_u8(TYPE_ECHO_REPLY, 1, &payload).unwrap() + ); + } + + #[test] + fn as_lax_ip_slice() { + let invoking_packet = [ + 0x45, 0, 0, 20, 0, 0, 0, 0, 64, 17, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, + ]; + let expected = crate::LaxIpSlice::from_slice(&invoking_packet).unwrap(); + + assert_eq!( + DestinationUnreachablePayloadSlice::from_slice(&invoking_packet) + .unwrap() + .as_lax_ip_slice() + .unwrap(), + expected + ); + assert_eq!( + PacketTooBigPayloadSlice::from_slice(&invoking_packet) + .unwrap() + .as_lax_ip_slice() + .unwrap(), + expected + ); + assert_eq!( + TimeExceededPayloadSlice::from_slice(&invoking_packet) + .unwrap() + .as_lax_ip_slice() + .unwrap(), + expected + ); + assert_eq!( + ParameterProblemPayloadSlice::from_slice(&invoking_packet) + .unwrap() + .as_lax_ip_slice() + .unwrap(), + expected + ); + } } diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/packet_too_big_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/packet_too_big_payload_slice.rs new file mode 100644 index 00000000..fa0afd49 --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/packet_too_big_payload_slice.rs @@ -0,0 +1,55 @@ +use crate::{err, LaxIpSlice}; + +/// Borrowed payload of a Packet Too Big message (RFC 4443, Section 3.2). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | MTU | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | As much of invoking packet | +/// + as possible without the ICMPv6 packet + +/// | exceeding the minimum IPv6 MTU [IPv6] | +/// ``` +/// +/// In this crate, `Type`, `Code`, and `MTU` are represented by +/// [`crate::Icmpv6Type::PacketTooBig`]. This slice represents the invoking +/// packet bytes after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PacketTooBigPayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> PacketTooBigPayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the invoking packet bytes carried by the message. + pub fn invoking_packet(&self) -> &'a [u8] { + self.slice + } + + /// Decodes the invoking packet bytes as a lax IP slice. + pub fn as_lax_ip_slice( + &self, + ) -> Result< + ( + LaxIpSlice<'a>, + Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, + ), + err::ip::LaxHeaderSliceError, + > { + LaxIpSlice::from_slice(self.invoking_packet()) + } +} diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/parameter_problem_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/parameter_problem_payload_slice.rs new file mode 100644 index 00000000..ccf599fb --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/parameter_problem_payload_slice.rs @@ -0,0 +1,55 @@ +use crate::{err, LaxIpSlice}; + +/// Borrowed payload of a Parameter Problem message (RFC 4443, Section 3.4). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Pointer | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | As much of invoking packet | +/// + as possible without the ICMPv6 packet + +/// | exceeding the minimum IPv6 MTU [IPv6] | +/// ``` +/// +/// In this crate, `Type`, `Code`, and `Pointer` are represented by +/// [`crate::Icmpv6Type::ParameterProblem`]. This slice represents the invoking +/// packet bytes after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParameterProblemPayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> ParameterProblemPayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the invoking packet bytes carried by the message. + pub fn invoking_packet(&self) -> &'a [u8] { + self.slice + } + + /// Decodes the invoking packet bytes as a lax IP slice. + pub fn as_lax_ip_slice( + &self, + ) -> Result< + ( + LaxIpSlice<'a>, + Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, + ), + err::ip::LaxHeaderSliceError, + > { + LaxIpSlice::from_slice(self.invoking_packet()) + } +} diff --git a/etherparse/src/transport/icmpv6/icmpv6_payload_slice/time_exceeded_payload_slice.rs b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/time_exceeded_payload_slice.rs new file mode 100644 index 00000000..05925538 --- /dev/null +++ b/etherparse/src/transport/icmpv6/icmpv6_payload_slice/time_exceeded_payload_slice.rs @@ -0,0 +1,55 @@ +use crate::{err, LaxIpSlice}; + +/// Borrowed payload of a Time Exceeded message (RFC 4443, Section 3.3). +/// +/// The full packet layout is: +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type | Code | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Unused | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | As much of invoking packet | +/// + as possible without the ICMPv6 packet + +/// | exceeding the minimum IPv6 MTU [IPv6] | +/// ``` +/// +/// In this crate, `Type`, `Code`, and `Unused` are represented by +/// [`crate::Icmpv6Type::TimeExceeded`]. This slice represents the invoking +/// packet bytes after that fixed part. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TimeExceededPayloadSlice<'a> { + slice: &'a [u8], +} + +impl<'a> TimeExceededPayloadSlice<'a> { + /// Creates a payload slice from the bytes after the ICMPv6 header. + pub fn from_slice(slice: &'a [u8]) -> Result { + Ok(Self { slice }) + } + + /// Returns the full payload slice. + pub fn slice(&self) -> &'a [u8] { + self.slice + } + + /// Returns the invoking packet bytes carried by the message. + pub fn invoking_packet(&self) -> &'a [u8] { + self.slice + } + + /// Decodes the invoking packet bytes as a lax IP slice. + pub fn as_lax_ip_slice( + &self, + ) -> Result< + ( + LaxIpSlice<'a>, + Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, + ), + err::ip::LaxHeaderSliceError, + > { + LaxIpSlice::from_slice(self.invoking_packet()) + } +} diff --git a/etherparse/src/transport/icmpv6_slice.rs b/etherparse/src/transport/icmpv6_slice.rs index 2564f719..1053924b 100644 --- a/etherparse/src/transport/icmpv6_slice.rs +++ b/etherparse/src/transport/icmpv6_slice.rs @@ -790,7 +790,20 @@ mod test { let slice = Icmpv6Slice::from_slice(&packet).unwrap(); assert_eq!( slice.payload_slice().unwrap(), - Icmpv6PayloadSlice::Raw(&packet[8..]) + Icmpv6PayloadSlice::EchoRequest( + EchoRequestPayloadSlice::from_slice(&packet[8..]).unwrap() + ) + ); + } + + { + let packet = [TYPE_ECHO_REPLY, 0, 0, 0, 0, 0, 0, 0, 7, 7]; + let slice = Icmpv6Slice::from_slice(&packet).unwrap(); + assert_eq!( + slice.payload_slice().unwrap(), + Icmpv6PayloadSlice::EchoReply( + EchoReplyPayloadSlice::from_slice(&packet[8..]).unwrap() + ) ); } }