use crate::{ err::{ValueTooBigError, ValueType}, *, }; /// Slice containing the TCP header & payload. #[derive(Clone, Eq, PartialEq)] pub struct TcpSlice<'a> { header_len: usize, slice: &'a [u8], } impl<'a> TcpSlice<'a> { /// Try creating a [`TcpSlice`] from a slice containing the /// TCP header and the TCP payload. pub fn from_slice(slice: &'a [u8]) -> Result, err::tcp::HeaderSliceError> { use err::tcp::{HeaderError::*, HeaderSliceError::*}; // check length if slice.len() < TcpHeader::MIN_LEN { return Err(Len(err::LenError { required_len: TcpHeader::MIN_LEN, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::TcpHeader, layer_start_offset: 0, })); } // SAFETY: // Safe as it is checked at the start of the function that the // length of the slice is at least TcpHeader::MIN_LEN (20). let header_len = unsafe { // The length of the TCP header can be determined via // the data offset field of the TCP header. "data offset" // stores the offset in 4 byte steps from the start of the // header to the payload of the header. // // "data offset" is stored in the upper 4 bits // (aka 0b1111_0000) of byte 12. To get to total length // in bytes of the header data offset has to be multiplied // by 4. So the naive version to get the length of // the header would be: // // ``` // let data_offset = (*slice.get_unchecked(12) & 0xf0) >> 4; // let len = data_offset * 4; // ``` // // But a multiplication by 4 can be replaced by 2 // left shift: // // ``` // let data_offset = (*slice.get_unchecked(12) & 0xf0) >> 4; // let len = data_offset << 2; // ``` // // And finally the shifts can be combined to one: // // ``` // let len = (*slice.get_unchecked(12) & 0xf0) >> 2; // ``` usize::from((*slice.get_unchecked(12) & 0xf0) >> 2) }; if header_len < TcpHeader::MIN_LEN { Err(Content(DataOffsetTooSmall { data_offset: (header_len >> 2) as u8, })) } else if slice.len() < header_len { Err(Len(err::LenError { required_len: header_len, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::TcpHeader, layer_start_offset: 0, })) } else { //done Ok(TcpSlice::<'a> { header_len, slice }) } } /// Returns the slice containing the TCP header and payload. #[inline] pub fn slice(&self) -> &'a [u8] { self.slice } /// Returns the slice containing the TCP header /// (including options). #[inline] pub fn header_slice(&self) -> &'a [u8] { unsafe { // SAFETY: Safe as the slice was verified // to be at least header_len long. core::slice::from_raw_parts(self.slice.as_ptr(), self.header_len) } } /// Returns the slice containing the TCP payload. #[inline] pub fn payload(&self) -> &'a [u8] { unsafe { // SAFETY: Safe as the slice was verified // to be at least header_len long. core::slice::from_raw_parts( self.slice.as_ptr().add(self.header_len), self.slice.len() - self.header_len, ) } } /// Length of the TCP header (including TCP options). #[inline] pub const fn header_len(&self) -> usize { self.header_len } /// Read the destination port number in the TCP header. #[inline] pub fn source_port(&self) -> u16 { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { get_unchecked_be_u16(self.slice.as_ptr()) } } /// Read the destination port number in the TCP header. #[inline] pub fn destination_port(&self) -> u16 { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } } /// Read the sequence number in the TCP header. /// /// If SYN is present the sequence number is the initial sequence number (ISN) /// and the first data octet is ISN+1. /// \[copied from RFC 793, page 16\] #[inline] pub fn sequence_number(&self) -> u32 { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { get_unchecked_be_u32(self.slice.as_ptr().add(4)) } } /// Reads the acknowledgment number in the TCP header. /// /// If the ACK control bit is set this field contains the value of the /// next sequence number the sender of the segment is expecting to /// receive. /// /// Once a connection is established this is always sent. #[inline] pub fn acknowledgment_number(&self) -> u32 { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { get_unchecked_be_u32(self.slice.as_ptr().add(8)) } } /// Read the number of 32 bit words in the TCP Header. /// /// This indicates where the payload begins. The TCP header /// (even one including options) is an integral number of 32 /// bits long. #[inline] pub fn data_offset(&self) -> u8 { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { (*self.slice.get_unchecked(12) & 0b1111_0000) >> 4 } } /// ECN-nonce - concealment protection (experimental: see RFC 3540) #[inline] pub fn ns(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(12) & 0b0000_0001) } } /// Read the fin flag (no more data from sender). #[inline] pub fn fin(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0000_0001) } } /// Reads the syn flag (synchronize sequence numbers). #[inline] pub fn syn(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0000_0010) } } /// Reads the rst flag (reset the connection). #[inline] pub fn rst(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0000_0100) } } /// Reads the psh flag (push function). #[inline] pub fn psh(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0000_1000) } } /// Reads the ack flag (acknowledgment field significant). #[inline] pub fn ack(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0001_0000) } } /// Reads the urg flag (Urgent Pointer field significant). #[inline] pub fn urg(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0010_0000) } } /// Read the ECN-Echo flag (RFC 3168). #[inline] pub fn ece(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b0100_0000) } } /// Reads the cwr flag (Congestion Window Reduced). /// /// This flag is set by the sending host to indicate that it received a TCP /// segment with the ECE flag set and had responded in congestion control /// mechanism (added to header by RFC 3168). #[inline] pub fn cwr(&self) -> bool { // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { 0 != (*self.slice.get_unchecked(13) & 0b1000_0000) } } /// The number of data octets beginning with the one indicated in the /// acknowledgment field which the sender of this segment is willing to /// accept. #[inline] pub fn window_size(&self) -> u16 { u16::from_be_bytes( // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { [*self.slice.get_unchecked(14), *self.slice.get_unchecked(15)] }, ) } /// Checksum (16 bit one's complement) of the pseudo ip header, this tcp header and the payload. #[inline] pub fn checksum(&self) -> u16 { u16::from_be_bytes( // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { [*self.slice.get_unchecked(16), *self.slice.get_unchecked(17)] }, ) } /// This field communicates the current value of the urgent pointer as a /// positive offset from the sequence number in this segment. /// /// The urgent pointer points to the sequence number of the octet following /// the urgent data. This field is only be interpreted in segments with /// the URG control bit set. #[inline] pub fn urgent_pointer(&self) -> u16 { u16::from_be_bytes( // SAFETY: // Constructor checks that the slice has at least the length // of 20. unsafe { [*self.slice.get_unchecked(18), *self.slice.get_unchecked(19)] }, ) } /// Options of the header #[inline] pub fn options(&self) -> &[u8] { &self.slice[TcpHeader::MIN_LEN..self.header_len] } /// Returns an iterator that allows to iterate through all known TCP header options. #[inline] pub fn options_iterator(&self) -> TcpOptionsIterator { TcpOptionsIterator::from_slice(self.options()) } /// Decode all the fields and copy the results to a /// [`crate::TcpHeader`]` struct. pub fn to_header(&self) -> TcpHeader { TcpHeader { source_port: self.source_port(), destination_port: self.destination_port(), sequence_number: self.sequence_number(), acknowledgment_number: self.acknowledgment_number(), ns: self.ns(), fin: self.fin(), syn: self.syn(), rst: self.rst(), psh: self.psh(), ack: self.ack(), ece: self.ece(), urg: self.urg(), cwr: self.cwr(), window_size: self.window_size(), checksum: self.checksum(), urgent_pointer: self.urgent_pointer(), options: { let options_slice = self.options(); let mut options = TcpOptions { len: options_slice.len() as u8, buf: [0; 40], }; options.buf[..options_slice.len()].clone_from_slice(options_slice); options }, } } /// Calculates the checksum for the current header in ipv4 mode and /// returns the result. This does NOT set the checksum. pub fn calc_checksum_ipv4( &self, source_ip: [u8; 4], destination_ip: [u8; 4], ) -> Result> { // check that the total length fits into the field if usize::from(u16::MAX) < self.slice.len() { return Err(ValueTooBigError { actual: self.slice.len(), max_allowed: usize::from(u16::MAX), value_type: ValueType::TcpPayloadLengthIpv4, }); } // calculate the checksum Ok(self.calc_checksum_post_ip( checksum::Sum16BitWords::new() .add_4bytes(source_ip) .add_4bytes(destination_ip) .add_2bytes([0, ip_number::TCP.0]) .add_2bytes((self.slice.len() as u16).to_be_bytes()), )) } /// Calculates the checksum for the current header in ipv6 mode and /// returns the result. This does NOT set the checksum. pub fn calc_checksum_ipv6( &self, source: [u8; 16], destination: [u8; 16], ) -> Result> { // check that the total length fits into the field #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] if (u32::MAX as usize) < self.slice.len() { return Err(ValueTooBigError { actual: self.slice.len(), max_allowed: (u32::MAX as usize), value_type: ValueType::TcpPayloadLengthIpv6, }); } // calculate the checksum Ok(self.calc_checksum_post_ip( checksum::Sum16BitWords::new() .add_16bytes(source) .add_16bytes(destination) .add_2bytes([0, ip_number::TCP.0]) .add_4bytes((self.slice.len() as u32).to_be_bytes()), )) } /// This method takes the sum of the pseudo ip header and calculates the rest of the checksum. fn calc_checksum_post_ip(&self, ip_pseudo_header_sum: checksum::Sum16BitWords) -> u16 { ip_pseudo_header_sum .add_slice(&self.slice[..16]) //until checksum .add_slice(&self.slice[18..]) .ones_complement() .to_be() } } impl core::fmt::Debug for TcpSlice<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("TcpSlice") .field("header", &self.to_header()) .field("payload", &self.payload()) .finish() } } #[cfg(test)] mod test { use super::*; use crate::test_gens::*; use alloc::{format, vec::Vec}; use proptest::prelude::*; proptest! { #[test] fn debug_clone_eq( tcp in tcp_any() ) { let payload: [u8;4] = [1,2,3,4]; let mut data = Vec::with_capacity( tcp.header_len() as usize + payload.len() ); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); // decode packet let slice = TcpSlice::from_slice(&data).unwrap(); // check debug output prop_assert_eq!( format!("{:?}", slice), format!( "TcpSlice {{ header: {:?}, payload: {:?} }}", &tcp, &payload[..] ) ); prop_assert_eq!(slice.clone(), slice); } } proptest! { #[test] fn from_slice( tcp in tcp_any() ) { use err::tcp::{HeaderError::*, HeaderSliceError::*}; let payload: [u8;4] = [1,2,3,4]; let data = { let mut data = Vec::with_capacity( tcp.header_len() as usize + payload.len() ); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); data }; // normal decode { let slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!(&slice.to_header(), &tcp); assert_eq!(slice.payload(), &payload); } // too little data to even decode the header for len in 0..(tcp.header_len() as usize) { assert_eq!( TcpSlice::from_slice(&data[..len]).unwrap_err(), Len(err::LenError { required_len: if len < TcpHeader::MIN_LEN { TcpHeader::MIN_LEN } else { tcp.header_len() }, len, len_source: LenSource::Slice, layer: err::Layer::TcpHeader, layer_start_offset: 0, }) ); } // data offset smaller then minimum header size { let mut broken_data = data.clone(); for data_offset in 0..TcpHeader::MIN_DATA_OFFSET { // inject a bad data offset broken_data[12] = data_offset << 4 | ( broken_data[12] & 0b0000_1111); assert_eq!( TcpSlice::from_slice(&broken_data).unwrap_err(), Content(DataOffsetTooSmall { data_offset }) ); } } } } proptest! { #[test] fn getters( tcp in tcp_any() ) { let payload: [u8;4] = [1,2,3,4]; let data = { let mut data = Vec::with_capacity( tcp.header_len() as usize + payload.len() ); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); data }; let slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!(slice.slice(), &data); assert_eq!(slice.header_slice(), &data[..tcp.header_len()]); assert_eq!(slice.payload(), &data[tcp.header_len()..]); assert_eq!(slice.header_len(), tcp.header_len()); assert_eq!(slice.source_port(), tcp.source_port); assert_eq!(slice.destination_port(), tcp.destination_port); assert_eq!(slice.sequence_number(), tcp.sequence_number); assert_eq!(slice.acknowledgment_number(), tcp.acknowledgment_number); assert_eq!(slice.data_offset(), tcp.data_offset()); assert_eq!(slice.ns(), tcp.ns); assert_eq!(slice.fin(), tcp.fin); assert_eq!(slice.syn(), tcp.syn); assert_eq!(slice.rst(), tcp.rst); assert_eq!(slice.psh(), tcp.psh); assert_eq!(slice.ack(), tcp.ack); assert_eq!(slice.urg(), tcp.urg); assert_eq!(slice.ece(), tcp.ece); assert_eq!(slice.cwr(), tcp.cwr); assert_eq!(slice.window_size(), tcp.window_size); assert_eq!(slice.checksum(), tcp.checksum); assert_eq!(slice.urgent_pointer(), tcp.urgent_pointer); assert_eq!(slice.options(), tcp.options.as_slice()); assert_eq!(slice.options_iterator(), tcp.options_iterator()); assert_eq!(slice.to_header(), tcp); } } #[test] fn calc_checksum_ipv4() { use TcpOptionElement::*; // checksum == 0xf (no carries) (aka sum == 0xffff) { let payload = [1, 2, 3, 4, 5, 6, 7, 8]; let tcp = TcpHeader::new(0, 0, 40905, 0); let mut data = Vec::with_capacity(tcp.header_len() + payload.len()); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); let tcp_slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!(Ok(0x0), tcp_slice.calc_checksum_ipv4([0; 4], [0; 4])); } // a header with options { let payload = [1, 2, 3, 4, 5, 6, 7, 8]; let mut tcp = TcpHeader::new(69, 42, 0x24900448, 0x3653); tcp.urgent_pointer = 0xE26E; tcp.ns = true; tcp.fin = true; tcp.syn = true; tcp.rst = true; tcp.psh = true; tcp.ack = true; tcp.ece = true; tcp.urg = true; tcp.cwr = true; tcp.set_options(&[Noop, Noop, Noop, Noop, Timestamp(0x4161008, 0x84161708)]) .unwrap(); let mut data = Vec::with_capacity(tcp.header_len() + payload.len()); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); let tcp_slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!( Ok(0xdeeb), tcp_slice.calc_checksum_ipv4([192, 168, 1, 42], [192, 168, 1, 1]) ); } //a header with an uneven number of options { let payload = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let mut tcp = TcpHeader::new(69, 42, 0x24900448, 0x3653); tcp.urgent_pointer = 0xE26E; tcp.ns = true; tcp.fin = true; tcp.syn = true; tcp.rst = true; tcp.psh = true; tcp.ack = true; tcp.ece = true; tcp.urg = true; tcp.cwr = true; tcp.set_options(&[Noop, Noop, Noop, Noop, Timestamp(0x4161008, 0x84161708)]) .unwrap(); let mut data = Vec::with_capacity(tcp.header_len() + payload.len()); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); let slice = TcpSlice::from_slice(&data[..]).unwrap(); assert_eq!( Ok(0xd5ea), slice.calc_checksum_ipv4([192, 168, 1, 42], [192, 168, 1, 1]) ); } // value error { // write the tcp header let tcp: TcpHeader = Default::default(); let mut data = Vec::with_capacity(usize::from(core::u16::MAX) + 1); data.extend_from_slice(&tcp.to_bytes()); data.resize(usize::from(core::u16::MAX) + 1, 0); // payload let slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!( slice.calc_checksum_ipv4([0; 4], [0; 4]), Err(ValueTooBigError { actual: data.len(), max_allowed: usize::from(core::u16::MAX), value_type: ValueType::TcpPayloadLengthIpv4, }) ); } } #[test] fn calc_checksum_ipv6() { use crate::TcpOptionElement::*; // ok case { let payload = [51, 52, 53, 54, 55, 56, 57, 58]; let mut tcp = TcpHeader::new(69, 42, 0x24900448, 0x3653); tcp.urgent_pointer = 0xE26E; tcp.ns = true; tcp.fin = true; tcp.syn = true; tcp.rst = true; tcp.psh = true; tcp.ack = true; tcp.ece = true; tcp.urg = true; tcp.cwr = true; tcp.set_options(&[Noop, Noop, Noop, Noop, Timestamp(0x4161008, 0x84161708)]) .unwrap(); let mut data = Vec::with_capacity(tcp.header_len() + payload.len()); data.extend_from_slice(&tcp.to_bytes()); data.extend_from_slice(&payload); let slice = TcpSlice::from_slice(&data).unwrap(); assert_eq!( Ok(0x786e), slice.calc_checksum_ipv6( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,], ) ); } // error #[cfg(target_pointer_width = "64")] { let slice = TcpSlice { header_len: TcpHeader::MIN_LEN, // lets create a slice of that size that points to zero // (as most systems can not allocate blocks of the size of u32::MAX) 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::::dangling().as_ptr(), (core::u32::MAX as usize) + 1, ) }, }; // expect an length error assert_eq!( slice.calc_checksum_ipv6( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,], ), Err(ValueTooBigError { actual: (core::u32::MAX as usize) + 1, max_allowed: core::u32::MAX as usize, value_type: ValueType::TcpPayloadLengthIpv6, }) ); } } }