use crate::{icmpv4::*, *}; /// A slice containing an ICMPv4 network package. /// /// Struct allows the selective read of fields in the ICMPv4 /// packet. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Icmpv4Slice<'a> { pub(crate) slice: &'a [u8], } impl<'a> Icmpv4Slice<'a> { /// Creates a slice containing an ICMPv4 packet. /// /// # Errors /// /// The function will return an `Err` `err::LenError` /// if the given slice is too small or does not match the expected /// length in case of a timestamp message. #[inline] pub fn from_slice(slice: &'a [u8]) -> Result, err::LenError> { // check length if slice.len() < Icmpv4Header::MIN_LEN { return Err(err::LenError { required_len: Icmpv4Header::MIN_LEN, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::Icmpv4, layer_start_offset: 0, }); } // SAFETY: // Safe as it is previously checked that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). let icmp_type: u8 = unsafe { *slice.get_unchecked(0) }; let icmp_code: u8 = unsafe { *slice.get_unchecked(1) }; // check type specific length match icmp_type { TYPE_TIMESTAMP => { if 0 == icmp_code && TimestampMessage::LEN != slice.len() { return Err(err::LenError { required_len: TimestampMessage::LEN, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::Icmpv4Timestamp, layer_start_offset: 0, }); } } TYPE_TIMESTAMP_REPLY => { if 0 == icmp_code && TimestampMessage::LEN != slice.len() { return Err(err::LenError { required_len: TimestampMessage::LEN, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::Icmpv4TimestampReply, layer_start_offset: 0, }); } } _ => {} } //done Ok(Icmpv4Slice { slice }) } /// Decode the header values into an [`Icmpv4Header`] struct. #[inline] pub fn header(&self) -> Icmpv4Header { let icmp_type = self.icmp_type(); Icmpv4Header { icmp_type, checksum: self.checksum(), } } /// Number of bytes/octets that will be converted into a /// [`Icmpv4Header`] when [`Icmpv4Slice::header`] gets called. #[inline] pub fn header_len(&self) -> usize { match self.type_u8() { TYPE_TIMESTAMP | TYPE_TIMESTAMP_REPLY => { if 0 == self.code_u8() { TimestampMessage::LEN } else { 8 } } _ => 8, } } /// Decode the header values (excluding the checksum) into an [`Icmpv4Type`] enum. pub fn icmp_type(&self) -> Icmpv4Type { use Icmpv4Type::*; unsafe fn timestamp_message(ptr: *const u8) -> TimestampMessage { TimestampMessage { id: get_unchecked_be_u16(ptr.add(4)), seq: get_unchecked_be_u16(ptr.add(6)), originate_timestamp: get_unchecked_be_u32(ptr.add(8)), receive_timestamp: get_unchecked_be_u32(ptr.add(12)), transmit_timestamp: get_unchecked_be_u32(ptr.add(16)), } } match self.type_u8() { TYPE_ECHO_REPLY => { if 0 == self.code_u8() { return EchoReply(IcmpEchoHeader::from_bytes(self.bytes5to8())); } } TYPE_DEST_UNREACH => { use DestUnreachableHeader::*; match self.code_u8() { CODE_DST_UNREACH_NET => return DestinationUnreachable(Network), CODE_DST_UNREACH_HOST => return DestinationUnreachable(Host), CODE_DST_UNREACH_PROTOCOL => return DestinationUnreachable(Protocol), CODE_DST_UNREACH_PORT => return DestinationUnreachable(Port), CODE_DST_UNREACH_NEED_FRAG => { return DestinationUnreachable(FragmentationNeeded { // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). next_hop_mtu: unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(6)) }, }); } CODE_DST_UNREACH_SOURCE_ROUTE_FAILED => { return DestinationUnreachable(SourceRouteFailed) } CODE_DST_UNREACH_NET_UNKNOWN => return DestinationUnreachable(NetworkUnknown), CODE_DST_UNREACH_HOST_UNKNOWN => return DestinationUnreachable(HostUnknown), CODE_DST_UNREACH_ISOLATED => return DestinationUnreachable(Isolated), CODE_DST_UNREACH_NET_PROHIB => { return DestinationUnreachable(NetworkProhibited) } CODE_DST_UNREACH_HOST_PROHIB => return DestinationUnreachable(HostProhibited), CODE_DST_UNREACH_TOS_NET => return DestinationUnreachable(TosNetwork), CODE_DST_UNREACH_TOS_HOST => return DestinationUnreachable(TosHost), CODE_DST_UNREACH_FILTER_PROHIB => { return DestinationUnreachable(FilterProhibited) } CODE_DST_UNREACH_HOST_PRECEDENCE_VIOLATION => { return DestinationUnreachable(HostPrecedenceViolation) } CODE_DST_UNREACH_PRECEDENCE_CUTOFF => { return DestinationUnreachable(PrecedenceCutoff) } _ => {} } } TYPE_REDIRECT => { use RedirectCode::*; let code = match self.code_u8() { CODE_REDIRECT_FOR_NETWORK => Some(RedirectForNetwork), CODE_REDIRECT_FOR_HOST => Some(RedirectForHost), CODE_REDIRECT_TYPE_OF_SERVICE_AND_NETWORK => { Some(RedirectForTypeOfServiceAndNetwork) } CODE_REDIRECT_TYPE_OF_SERVICE_AND_HOST => Some(RedirectForTypeOfServiceAndHost), _ => None, }; if let Some(code) = code { return Redirect(RedirectHeader { code, gateway_internet_address: self.bytes5to8(), }); } } TYPE_ECHO_REQUEST => { if 0 == self.code_u8() { return EchoRequest(IcmpEchoHeader::from_bytes(self.bytes5to8())); } } TYPE_TIME_EXCEEDED => { use TimeExceededCode::*; match self.code_u8() { CODE_TIME_EXCEEDED_TTL_EXCEEDED_IN_TRANSIT => { return TimeExceeded(TtlExceededInTransit); } CODE_TIME_EXCEEDED_FRAG_REASSEMBLY_TIME_EXCEEDED => { return TimeExceeded(FragmentReassemblyTimeExceeded); } _ => {} } } TYPE_PARAMETER_PROBLEM => { use ParameterProblemHeader::*; match self.code_u8() { CODE_PARAMETER_PROBLEM_POINTER_INDICATES_ERROR => { return ParameterProblem(PointerIndicatesError( // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). unsafe { *self.slice.get_unchecked(4) }, )); } CODE_PARAMETER_PROBLEM_MISSING_REQUIRED_OPTION => { return ParameterProblem(MissingRequiredOption); } CODE_PARAMETER_PROBLEM_BAD_LENGTH => { return ParameterProblem(BadLength); } _ => {} } } TYPE_TIMESTAMP => { if 0 == self.code_u8() { // SAFETY: // Safe as the contructor checks that the slice has // the length of TimestampMessage::SERIALIZED_SIZE (20). unsafe { return TimestampRequest(timestamp_message(self.slice.as_ptr())); } } } TYPE_TIMESTAMP_REPLY => { if 0 == self.code_u8() { // SAFETY: // Safe as the contructor checks that the slice has // the length of TimestampMessage::SERIALIZED_SIZE (20). unsafe { return TimestampReply(timestamp_message(self.slice.as_ptr())); } } } _ => {} } Unknown { type_u8: self.type_u8(), code_u8: self.code_u8(), bytes5to8: self.bytes5to8(), } } /// Returns "type" value in the ICMPv4 header. #[inline] pub fn type_u8(&self) -> u8 { // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). unsafe { *self.slice.get_unchecked(0) } } /// Returns "code" value in the ICMPv4 header. #[inline] pub fn code_u8(&self) -> u8 { // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). unsafe { *self.slice.get_unchecked(1) } } /// Returns "checksum" value in the ICMPv4 header. #[inline] pub fn checksum(&self) -> u16 { // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } } /// Returns the bytes from position 4 till and including the 8th position /// in the ICMPv4 header. /// /// These bytes located at th 5th, 6th, 7th and 8th position of the ICMP /// packet can depending on the ICMPv4 type and code contain additional data. #[inline] pub fn bytes5to8(&self) -> [u8; 4] { // SAFETY: // Safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN (8). unsafe { [ *self.slice.get_unchecked(4), *self.slice.get_unchecked(5), *self.slice.get_unchecked(6), *self.slice.get_unchecked(7), ] } } /// Returns a slice to the bytes not covered by `.header()`. /// /// The contents of the slice returned by `payload()` depends on the type /// and code of the ICMP packet: /// /// | `.header().icmp_type` or `.icmp_type()` | Payload Content | /// |--------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| /// | [`Icmpv4Type::EchoReply`]
[`Icmpv4Type::EchoRequest`]
| Data part of the echo message | /// | [`Icmpv4Type::DestinationUnreachable`]
[`Icmpv4Type::Redirect`]
[`Icmpv4Type::TimeExceeded`]
[`Icmpv4Type::ParameterProblem`]
| Internet Header + 64 bits of Original Data Datagram causing the ICMP message | /// | [`Icmpv4Type::TimestampRequest`]
[`Icmpv4Type::TimestampReply`]
| Nothing | /// | [`Icmpv4Type::Unknown`] | Everything after the 8th byte/octet of the ICMP packet. | #[inline] pub fn payload(&self) -> &'a [u8] { // explicitly inlined the code to determine the // length of the payload to make the cecking of the // usafe code easier. let header_len = match self.type_u8() { // SAFETY: // Length safe as the contructor checks that the slice has // the length of TimestampMessage::SERIALIZED_SIZE (20) // for the messages types TYPE_TIMESTAMP and TYPE_TIMESTAMP_REPLY. TYPE_TIMESTAMP | TYPE_TIMESTAMP_REPLY => { if 0 == self.code_u8() { TimestampMessage::LEN } else { 8 } } // SAFETY: // Length safe as the contructor checks that the slice has // at least the length of Icmpv4Header::MIN_LEN(8) for // all message types. _ => 8, }; // SAFETY: // Lengths have been depending on type in the constructor of the // ICMPv4Slice. unsafe { core::slice::from_raw_parts( self.slice.as_ptr().add(header_len), self.slice.len() - header_len, ) } } /// Returns the slice containing the ICMPv4 packet. #[inline] pub fn slice(&self) -> &'a [u8] { self.slice } } #[cfg(test)] mod test { use super::*; use alloc::{format, vec::Vec}; use proptest::prelude::*; #[test] fn from_slice() { // normal case { let bytes = [0u8; 8]; let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!(slice.slice(), &bytes); } // smaller then min size error for bad_len in 0..8 { let bytes = [0u8; 8]; assert_eq!( Icmpv4Slice::from_slice(&bytes[..bad_len]).unwrap_err(), err::LenError { required_len: Icmpv4Header::MIN_LEN, len: bad_len, len_source: LenSource::Slice, layer: err::Layer::Icmpv4, layer_start_offset: 0, } ); } // timestamp tests for ts_type_u8 in [TYPE_TIMESTAMP, TYPE_TIMESTAMP_REPLY] { let bytes = { let mut bytes = [0u8; 26]; bytes[0] = ts_type_u8; bytes }; // valid timestamps { let slice = Icmpv4Slice::from_slice(&bytes[..20]).unwrap(); assert_eq!(slice.slice(), &bytes[..20]); } // too short timestamps for bad_len in 8..20 { assert_eq!( Icmpv4Slice::from_slice(&bytes[..bad_len]).unwrap_err(), err::LenError { required_len: TimestampMessage::LEN, len: bad_len, len_source: LenSource::Slice, layer: if ts_type_u8 == TYPE_TIMESTAMP { err::Layer::Icmpv4Timestamp } else { err::Layer::Icmpv4TimestampReply }, layer_start_offset: 0, } ); } // too large timestamps for bad_len in 21..26 { assert_eq!( Icmpv4Slice::from_slice(&bytes[..bad_len]).unwrap_err(), err::LenError { required_len: TimestampMessage::LEN, len: bad_len, len_source: LenSource::Slice, layer: if ts_type_u8 == TYPE_TIMESTAMP { err::Layer::Icmpv4Timestamp } else { err::Layer::Icmpv4TimestampReply }, layer_start_offset: 0, } ); } // timestamp with a non zero code for code_u8 in 1..=u8::MAX { let mut bytes = [0u8; 20]; bytes[0] = ts_type_u8; bytes[1] = code_u8; let slice = Icmpv4Slice::from_slice(&bytes[..8]).unwrap(); assert_eq!(slice.slice(), &bytes[..8]); } } } proptest! { #[test] fn header(bytes in any::<[u8;20]>()) { let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( Icmpv4Header { icmp_type: slice.icmp_type(), checksum: slice.checksum(), }, slice.header() ); } } #[test] fn header_len() { use Icmpv4Type::*; let dummy_ts = TimestampMessage { id: 0, seq: 0, originate_timestamp: 0, receive_timestamp: 0, transmit_timestamp: 0, }; let dummy_echo = IcmpEchoHeader { id: 0, seq: 0 }; let dummy_redirect = RedirectHeader { code: RedirectCode::RedirectForNetwork, gateway_internet_address: [0; 4], }; let tests = [ (Unknown { type_u8: u8::MAX, code_u8: 0, bytes5to8: [0; 4], }), (EchoReply(dummy_echo)), (DestinationUnreachable(DestUnreachableHeader::Network)), (Redirect(dummy_redirect)), (EchoRequest(dummy_echo)), (TimeExceeded(TimeExceededCode::TtlExceededInTransit)), (ParameterProblem(ParameterProblemHeader::BadLength)), (TimestampRequest(dummy_ts.clone())), // check that a non zero code value return 8 (Unknown { type_u8: TYPE_TIMESTAMP, code_u8: 1, bytes5to8: [0; 4], }), (TimestampReply(dummy_ts)), // check that a non zero code value return 8 (Unknown { type_u8: TYPE_TIMESTAMP_REPLY, code_u8: 1, bytes5to8: [0; 4], }), ]; for t in tests { assert_eq!( t.header_len(), Icmpv4Slice::from_slice(&Icmpv4Header::new(t).to_bytes()) .unwrap() .header_len() ); } } proptest! { #[test] fn icmp_type(base_bytes in any::<[u8;20]>()) { use Icmpv4Type::*; let gen_bytes = |type_u8: u8, code_u8: u8| -> [u8;20] { let mut bytes = base_bytes; bytes[0] = type_u8; bytes[1] = code_u8; bytes }; let assert_unknown = |type_u8: u8, code_u8: u8| { let bytes = gen_bytes(type_u8, code_u8); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), Unknown{ type_u8, code_u8, bytes5to8: slice.bytes5to8(), } ); }; // unknown types for type_u8 in 0..=u8::MAX{ match type_u8 { TYPE_ECHO_REPLY | TYPE_DEST_UNREACH | TYPE_REDIRECT | TYPE_ECHO_REQUEST | TYPE_TIME_EXCEEDED | TYPE_PARAMETER_PROBLEM | TYPE_TIMESTAMP | TYPE_TIMESTAMP_REPLY => {}, type_u8 => { assert_unknown(type_u8, base_bytes[1]); } } } // echo reply { // matching code { let bytes = gen_bytes(TYPE_ECHO_REPLY, 0); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), EchoReply(IcmpEchoHeader::from_bytes(slice.bytes5to8())) ); } // unknown code for unknow_code in 1..=u8::MAX { assert_unknown(TYPE_ECHO_REPLY, unknow_code); } } // destination unreachable { use DestUnreachableHeader::*; // trivial code values { let trivial_tests = [ (CODE_DST_UNREACH_NET, Network), (CODE_DST_UNREACH_HOST, Host), (CODE_DST_UNREACH_PROTOCOL, Protocol), (CODE_DST_UNREACH_PORT, Port), // need frag skipped as contains an additional value (CODE_DST_UNREACH_SOURCE_ROUTE_FAILED, SourceRouteFailed), (CODE_DST_UNREACH_NET_UNKNOWN, NetworkUnknown), (CODE_DST_UNREACH_HOST_UNKNOWN, HostUnknown), (CODE_DST_UNREACH_ISOLATED, Isolated), (CODE_DST_UNREACH_NET_PROHIB, NetworkProhibited), (CODE_DST_UNREACH_HOST_PROHIB, HostProhibited), (CODE_DST_UNREACH_TOS_NET, TosNetwork), (CODE_DST_UNREACH_TOS_HOST, TosHost), (CODE_DST_UNREACH_FILTER_PROHIB, FilterProhibited), (CODE_DST_UNREACH_HOST_PRECEDENCE_VIOLATION, HostPrecedenceViolation), (CODE_DST_UNREACH_PRECEDENCE_CUTOFF, PrecedenceCutoff), ]; for (code_u8, expected) in trivial_tests { let bytes = gen_bytes(TYPE_DEST_UNREACH, code_u8); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), DestinationUnreachable(expected) ); } } // need frag { let bytes = gen_bytes(TYPE_DEST_UNREACH, CODE_DST_UNREACH_NEED_FRAG); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), DestinationUnreachable(FragmentationNeeded { next_hop_mtu: u16::from_be_bytes([bytes[6], bytes[7]]) }) ); } // unknown codes for unknow_code in 16..=u8::MAX { assert_unknown(TYPE_ECHO_REPLY, unknow_code); } } // redirect { use RedirectCode::*; // known codes { let trivial_tests = [ (CODE_REDIRECT_FOR_NETWORK, RedirectForNetwork), (CODE_REDIRECT_FOR_HOST, RedirectForHost), (CODE_REDIRECT_TYPE_OF_SERVICE_AND_NETWORK, RedirectForTypeOfServiceAndNetwork), (CODE_REDIRECT_TYPE_OF_SERVICE_AND_HOST, RedirectForTypeOfServiceAndHost), ]; for (code_u8, expected) in trivial_tests { let bytes = gen_bytes(TYPE_REDIRECT, code_u8); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), Redirect(RedirectHeader{ code: expected, gateway_internet_address: slice.bytes5to8(), }) ); } } // unknown codes for unknow_code in 4..=u8::MAX { assert_unknown(TYPE_REDIRECT, unknow_code); } } // echo request { // matching code { let bytes = gen_bytes(TYPE_ECHO_REQUEST, 0); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), EchoRequest(IcmpEchoHeader::from_bytes(slice.bytes5to8())) ); } // unknown code for unknow_code in 1..=u8::MAX { assert_unknown(TYPE_ECHO_REQUEST, unknow_code); } } // time exceeded { use TimeExceededCode::*; // known codes { let trivial_tests = [ (CODE_TIME_EXCEEDED_TTL_EXCEEDED_IN_TRANSIT, TtlExceededInTransit), (CODE_TIME_EXCEEDED_FRAG_REASSEMBLY_TIME_EXCEEDED, FragmentReassemblyTimeExceeded), ]; for (code_u8, expected) in trivial_tests { let bytes = gen_bytes(TYPE_TIME_EXCEEDED, code_u8); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), TimeExceeded(expected) ); } } // unknown code for unknow_code in 2..=u8::MAX { assert_unknown(TYPE_TIME_EXCEEDED, unknow_code); } } // parameter porblem { use ParameterProblemHeader::*; // trivial code values { let trivial_tests = [ (CODE_PARAMETER_PROBLEM_MISSING_REQUIRED_OPTION, MissingRequiredOption), (CODE_PARAMETER_PROBLEM_BAD_LENGTH, BadLength), ]; for (code_u8, expected) in trivial_tests { let bytes = gen_bytes(TYPE_PARAMETER_PROBLEM, code_u8); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), ParameterProblem(expected) ); } } // with pointer { let bytes = gen_bytes(TYPE_PARAMETER_PROBLEM, CODE_PARAMETER_PROBLEM_POINTER_INDICATES_ERROR); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), ParameterProblem(PointerIndicatesError(bytes[4])) ); } // unknown codes for unknow_code in 3..=u8::MAX { assert_unknown(TYPE_PARAMETER_PROBLEM, unknow_code); } } // timestamp { // matching code { let bytes = gen_bytes(TYPE_TIMESTAMP, 0); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), TimestampRequest(TimestampMessage::from_bytes([ bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19], ])) ); } // unknown code for unknow_code in 1..=u8::MAX { assert_unknown(TYPE_TIMESTAMP, unknow_code); } } // timestamp reply { // matching code { let bytes = gen_bytes(TYPE_TIMESTAMP_REPLY, 0); let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( slice.icmp_type(), TimestampReply(TimestampMessage::from_bytes([ bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19], ])) ); } // unknown code for unknow_code in 1..=u8::MAX { assert_unknown(TYPE_TIMESTAMP_REPLY, unknow_code); } } } } proptest! { #[test] fn type_u8(bytes in any::<[u8;20]>()) { assert_eq!( bytes[0], Icmpv4Slice::from_slice(&bytes).unwrap().type_u8(), ); } } proptest! { #[test] fn code_u8(bytes in any::<[u8;20]>()) { assert_eq!( bytes[1], Icmpv4Slice::from_slice(&bytes).unwrap().code_u8(), ); } } proptest! { #[test] fn checksum(bytes in any::<[u8;20]>()) { assert_eq!( u16::from_be_bytes([bytes[2], bytes[3]]), Icmpv4Slice::from_slice(&bytes).unwrap().checksum(), ); } } proptest! { #[test] fn bytes5to8(bytes in any::<[u8;20]>()) { assert_eq!( [bytes[4], bytes[5], bytes[6], bytes[7]], Icmpv4Slice::from_slice(&bytes).unwrap().bytes5to8(), ); } } proptest! { #[test] fn payload( payload in proptest::collection::vec(any::(), 8..26) ) { use Icmpv4Type::*; let dummy_ts = TimestampMessage{ id: 0, seq: 0, originate_timestamp: 0, receive_timestamp: 0, transmit_timestamp: 0, }; let dummy_echo = IcmpEchoHeader{ id: 0, seq: 0, }; let dummy_redirect = RedirectHeader{ code: RedirectCode::RedirectForNetwork, gateway_internet_address: [0;4], }; // tests with variable payloads { let var_tests = [ Unknown{type_u8: 0, code_u8: 0, bytes5to8: [0;4]}, EchoReply(dummy_echo), DestinationUnreachable(DestUnreachableHeader::Network), Redirect(dummy_redirect), EchoRequest(dummy_echo), TimeExceeded(TimeExceededCode::TtlExceededInTransit), ParameterProblem(ParameterProblemHeader::BadLength), // timestamps with non-zero code values Unknown{type_u8: TYPE_TIMESTAMP, code_u8: 1, bytes5to8: [0;4]}, Unknown{type_u8: TYPE_TIMESTAMP_REPLY, code_u8: 1, bytes5to8: [0;4]}, ]; for t in var_tests { let mut bytes = Vec::with_capacity(t.header_len() + payload.len()); Icmpv4Header::new(t.clone()).write(&mut bytes).unwrap(); bytes.extend_from_slice(&payload); assert_eq!( &payload[..], Icmpv4Slice::from_slice(&bytes).unwrap().payload() ); } } // tests with fixed payload sizes { let fixed_tests = [ (0, TimestampRequest(dummy_ts.clone())), (0, TimestampReply(dummy_ts)), ]; for t in fixed_tests { let mut bytes = Vec::with_capacity(t.1.header_len() + t.0); Icmpv4Header::new(t.1.clone()).write(&mut bytes).unwrap(); bytes.extend_from_slice(&payload[..t.0]); assert_eq!( &payload[..t.0], Icmpv4Slice::from_slice(&bytes).unwrap().payload() ); } } } } proptest! { #[test] fn slice(bytes in proptest::collection::vec(any::(), 20..1024)) { let slice = if bytes[0] == TYPE_TIMESTAMP || bytes[0] == TYPE_TIMESTAMP_REPLY { &bytes[..20] } else { &bytes[..] }; assert_eq!( slice, Icmpv4Slice::from_slice(slice).unwrap().slice(), ); } } proptest! { #[test] fn clone_eq(bytes in any::<[u8;20]>()) { let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!(slice, slice.clone()); } } proptest! { #[test] fn debug(bytes in any::<[u8;20]>()) { let slice = Icmpv4Slice::from_slice(&bytes).unwrap(); assert_eq!( format!("{:?}", slice), format!("Icmpv4Slice {{ slice: {:?} }}", &bytes[..]) ); } } }