// Copyright (c) 2026 Cyprien Avico avicocyprien@yahoo.com
//
// Licensed under the MIT License <LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
use std::convert::TryFrom;
use crate::{
checks::transport::tcp::{
validate_tcp_data_offset_available, validate_tcp_data_offset_words, validate_tcp_flags,
validate_tcp_min_length, validate_tcp_reserved,
},
errors::transport::tcp::TcpError,
};
/// Represents a TCP header
#[derive(Debug, PartialEq)]
pub struct TcpHeader<'a> {
pub source_port: u16,
pub destination_port: u16,
pub sequence_number: u32,
pub acknowledgment_number: u32,
pub data_offset: u8, // 4 bits
pub reserved: u8, // 3 bits
pub ns: bool, // 1 bit
pub cwr: bool, // 1 bit
pub ece: bool, // 1 bit
pub urg: bool, // 1 bit
pub ack: bool, // 1 bit
pub psh: bool, // 1 bit
pub rst: bool, // 1 bit
pub syn: bool, // 1 bit
pub fin: bool, // 1 bit
pub window_size: u16,
pub checksum: u16,
pub urgent_pointer: u16,
pub options: &'a [u8],
}
#[cfg_attr(doc, aquamarine::aquamarine)]
/// TCP Packet
///
/// ```mermaid
/// ---
/// title: TcpPacket
/// ---
/// packet-beta
/// 0-15: "Source Port u16"
/// 16-31: "Destination Port u16"
/// 32-63: "Sequence Number u32"
/// 64-95: "Acknowledgment Number u32"
/// 96-99: "Data Offset u4"
/// 100-103: "Reserved/NS u4"
/// 104-111: "Flags u8"
/// 112-127: "Window Size u16"
/// 128-143: "Checksum u16"
/// 144-159: "Urgent Pointer u16"
/// 160-191: "Options / Payload variable"
/// ```
#[derive(Debug)]
pub struct TcpPacket<'a> {
pub header: TcpHeader<'a>,
pub payload: &'a [u8],
}
impl<'a> TryFrom<&'a [u8]> for TcpPacket<'a> {
type Error = TcpError;
fn try_from(packet: &'a [u8]) -> Result<Self, Self::Error> {
validate_tcp_min_length(packet)?;
let data_offset_words = packet[12] >> 4;
validate_tcp_data_offset_words(data_offset_words)?;
let data_offset = (data_offset_words as usize) * 4;
validate_tcp_data_offset_available(packet.len(), data_offset)?;
let reserved = (packet[12] >> 1) & 0x07;
validate_tcp_reserved(reserved)?;
let flags = packet[13];
validate_tcp_flags(flags)?;
let header = TcpHeader {
source_port: u16::from_be_bytes([packet[0], packet[1]]),
destination_port: u16::from_be_bytes([packet[2], packet[3]]),
sequence_number: u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]),
acknowledgment_number: u32::from_be_bytes([
packet[8], packet[9], packet[10], packet[11],
]),
data_offset: data_offset_words,
reserved,
ns: (packet[12] & 0x01) != 0,
cwr: (flags & 0x80) != 0,
ece: (flags & 0x40) != 0,
urg: (flags & 0x20) != 0,
ack: (flags & 0x10) != 0,
psh: (flags & 0x08) != 0,
rst: (flags & 0x04) != 0,
syn: (flags & 0x02) != 0,
fin: (flags & 0x01) != 0,
window_size: u16::from_be_bytes([packet[14], packet[15]]),
checksum: u16::from_be_bytes([packet[16], packet[17]]),
urgent_pointer: u16::from_be_bytes([packet[18], packet[19]]),
options: if data_offset > 20 {
&packet[20..data_offset]
} else {
&[]
},
};
let payload = &packet[data_offset..];
Ok(TcpPacket { header, payload })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tcp_packet_parsing() {
// A simple TCP packet with no options
let tcp_packet = [
// Source port (1234)
0x04, 0xD2, // Destination port (80)
0x00, 0x50, // Sequence number (1)
0x00, 0x00, 0x00, 0x01, // Acknowledgment number (0)
0x00, 0x00, 0x00, 0x00,
// Data offset (5 * 4 = 20 bytes), Reserved, NS=0, CWR=0, ECE=0, URG=0, ACK=1, PSH=0, RST=0, SYN=1, FIN=0
0x50, 0x12, // Window size (8192)
0x20, 0x00, // Checksum (0 for test)
0x00, 0x00, // Urgent pointer (0)
0x00, 0x00, // Payload (4 bytes)
0x01, 0x02, 0x03, 0x04,
];
let tcp = TcpPacket::try_from(&tcp_packet[..]).unwrap();
assert_eq!(tcp.header.source_port, 1234);
assert_eq!(tcp.header.destination_port, 80);
assert_eq!(tcp.header.sequence_number, 1);
assert_eq!(tcp.header.acknowledgment_number, 0);
assert_eq!(tcp.header.data_offset, 5);
assert!(tcp.header.ack);
assert!(tcp.header.syn);
assert!(!tcp.header.fin);
assert_eq!(tcp.header.window_size, 8192);
assert_eq!(tcp.header.checksum, 0);
assert_eq!(tcp.header.urgent_pointer, 0);
assert!(tcp.header.options.is_empty());
assert_eq!(tcp.payload, &[0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_tcp_rst_ack_zero_window_is_valid() {
let packet = [
0xED, 0x11, 0x01, 0xF6, 0x4F, 0x2E, 0xE0, 0xFC, 0x7C, 0x80, 0x69, 0xB6, 0x50, 0x14,
0x00, 0x00, 0x1B, 0x1D, 0x00, 0x00,
];
let tcp = TcpPacket::try_from(&packet[..]).unwrap();
assert_eq!(tcp.header.source_port, 60689);
assert_eq!(tcp.header.destination_port, 502);
assert_eq!(tcp.header.data_offset, 5);
assert!(tcp.header.ack);
assert!(tcp.header.rst);
assert!(!tcp.header.syn);
assert!(!tcp.header.fin);
assert_eq!(tcp.header.window_size, 0);
assert!(tcp.payload.is_empty());
}
#[test]
fn test_tcp_packet_too_short() {
// Only 19 bytes (minimum is 20)
let short_packet = [0u8; 19];
let result = TcpPacket::try_from(&short_packet[..]);
assert!(matches!(result, Err(TcpError::PacketTooShort)));
}
#[test]
fn test_tcp_invalid_data_offset() {
// Create a packet with invalid data offset (1 * 4 = 4 bytes, which is less than minimum 20)
let mut packet = [0u8; 20];
packet[12] = 0x10; // Data offset = 1 (4 bytes)
let result = TcpPacket::try_from(&packet[..]);
assert!(matches!(result, Err(TcpError::InvalidDataOffset(1))));
}
#[test]
fn test_invalid_tcp_packet_form_icmp() {
// This is an invalid TCP packet that should fail parsing
let hex_str = "85001c45000000000101a2afb2c15c03";
let packet_data = hex::decode(hex_str).expect("Failed to decode hex string");
// The packet is too short (15 bytes) to be a valid TCP header (minimum 20 bytes)
let result = TcpPacket::try_from(packet_data.as_slice());
assert!(matches!(result, Err(TcpError::PacketTooShort)));
}
#[test]
fn test_invalid_tcp_packet_form_icmp_with_data() {
// This is an invalid TCP packet that should fail parsing
let hex_str = "c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727374757677";
let packet_data = hex::decode(hex_str).expect("Failed to decode hex string");
// The packet is too short (15 bytes) to be a valid TCP header (minimum 20 bytes)
let result = TcpPacket::try_from(packet_data.as_slice());
println!("result: {:?}", result);
assert!(matches!(result, Err(TcpError::InvalidHeaderLength)));
}
}