use std::fmt::{self, Formatter};
use zerocopy::byteorder::{BigEndian, U16, U64};
use zerocopy::{FromBytes, IntoBytes, Unaligned};
use crate::packet::{HeaderParser, PacketHeader};
pub const STT_PORT: u16 = 7471;
#[inline]
pub fn is_stt_port(port: u16) -> bool {
port == STT_PORT
}
pub const STT_VERSION: u8 = 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SttFlags(pub u8);
impl SttFlags {
pub const CSUM_VERIFIED: u8 = 0x01;
pub const CSUM_PARTIAL: u8 = 0x02;
pub const IPV4: u8 = 0x04;
pub const TCP: u8 = 0x08;
pub const CSUM_PRESENT: u8 = 0x10;
#[inline]
pub const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
pub fn is_csum_verified(&self) -> bool {
self.0 & Self::CSUM_VERIFIED != 0
}
#[inline]
pub fn is_csum_partial(&self) -> bool {
self.0 & Self::CSUM_PARTIAL != 0
}
#[inline]
pub fn is_ipv4(&self) -> bool {
self.0 & Self::IPV4 != 0
}
#[inline]
pub fn is_tcp(&self) -> bool {
self.0 & Self::TCP != 0
}
#[inline]
pub fn is_csum_present(&self) -> bool {
self.0 & Self::CSUM_PRESENT != 0
}
}
impl fmt::Display for SttFlags {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut flags = Vec::new();
if self.is_csum_verified() {
flags.push("CSUM_VERIFIED");
}
if self.is_csum_partial() {
flags.push("CSUM_PARTIAL");
}
if self.is_ipv4() {
flags.push("IPV4");
}
if self.is_tcp() {
flags.push("TCP");
}
if self.is_csum_present() {
flags.push("CSUM_PRESENT");
}
if flags.is_empty() {
write!(f, "none")
} else {
write!(f, "{}", flags.join(","))
}
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct SttHeader {
version: u8,
flags: u8,
l4_offset: u8,
reserved: u8,
mss: U16<BigEndian>,
vlan_tci: U16<BigEndian>,
context_id: U64<BigEndian>,
padding: U16<BigEndian>,
}
impl SttHeader {
pub const VLAN_PRESENT: u16 = 0x1000;
pub const VLAN_ID_MASK: u16 = 0x0FFF;
pub const PCP_MASK: u16 = 0xE000;
pub const PCP_SHIFT: u16 = 13;
pub const HEADER_SIZE: usize = 18;
#[allow(unused)]
const NAME: &'static str = "SttHeader";
#[inline]
pub fn version(&self) -> u8 {
self.version
}
#[inline]
pub fn flags_raw(&self) -> u8 {
self.flags
}
#[inline]
pub fn flags(&self) -> SttFlags {
SttFlags::new(self.flags)
}
#[inline]
pub fn l4_offset(&self) -> u8 {
self.l4_offset
}
#[inline]
pub fn l4_offset_bytes(&self) -> usize {
self.l4_offset as usize * 2
}
#[inline]
pub fn mss(&self) -> u16 {
self.mss.get()
}
#[inline]
pub fn vlan_tci_raw(&self) -> u16 {
self.vlan_tci.get()
}
#[inline]
pub fn has_vlan(&self) -> bool {
self.vlan_tci.get() & Self::VLAN_PRESENT != 0
}
#[inline]
pub fn vlan_id(&self) -> Option<u16> {
if self.has_vlan() {
Some(self.vlan_tci.get() & Self::VLAN_ID_MASK)
} else {
None
}
}
#[inline]
pub fn pcp(&self) -> u8 {
((self.vlan_tci.get() & Self::PCP_MASK) >> Self::PCP_SHIFT) as u8
}
#[inline]
pub fn context_id(&self) -> u64 {
self.context_id.get()
}
#[inline]
fn is_valid(&self) -> bool {
self.version == STT_VERSION
}
}
impl PacketHeader for SttHeader {
const NAME: &'static str = "SttHeader";
type InnerType = u64;
#[inline]
fn inner_type(&self) -> Self::InnerType {
self.context_id()
}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
Self::HEADER_SIZE
}
#[inline]
fn is_valid(&self) -> bool {
self.is_valid()
}
}
impl HeaderParser for SttHeader {
type Output<'a> = &'a SttHeader;
#[inline]
fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
header
}
}
impl fmt::Display for SttHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"STT v{} ctx=0x{:016x} mss={} flags=[{}]",
self.version(),
self.context_id(),
self.mss(),
self.flags()
)?;
if let Some(vid) = self.vlan_id() {
write!(f, " vlan={}", vid)?;
}
Ok(())
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct SttTcpHeader {
src_port: U16<BigEndian>,
dst_port: U16<BigEndian>,
seq: [u8; 4], ack: [u8; 4], data_offset_flags: U16<BigEndian>,
window: U16<BigEndian>,
checksum: U16<BigEndian>,
urgent: U16<BigEndian>,
}
impl SttTcpHeader {
pub const HEADER_SIZE: usize = 20;
#[inline]
pub fn src_port(&self) -> u16 {
self.src_port.get()
}
#[inline]
pub fn dst_port(&self) -> u16 {
self.dst_port.get()
}
#[inline]
pub fn sequence(&self) -> u32 {
u32::from_be_bytes(self.seq)
}
#[inline]
pub fn acknowledgment(&self) -> u32 {
u32::from_be_bytes(self.ack)
}
#[inline]
pub fn data_offset(&self) -> u8 {
((self.data_offset_flags.get() >> 12) & 0x0F) as u8
}
#[inline]
pub fn data_offset_bytes(&self) -> usize {
self.data_offset() as usize * 4
}
#[inline]
pub fn tcp_flags(&self) -> u8 {
(self.data_offset_flags.get() & 0x003F) as u8
}
#[inline]
pub fn window(&self) -> u16 {
self.window.get()
}
#[inline]
pub fn checksum(&self) -> u16 {
self.checksum.get()
}
#[inline]
pub fn urgent(&self) -> u16 {
self.urgent.get()
}
#[inline]
pub fn is_stt(&self) -> bool {
self.dst_port() == STT_PORT
}
#[inline]
pub fn fragment_offset(&self) -> u32 {
self.sequence()
}
#[inline]
pub fn total_length(&self) -> u16 {
(self.acknowledgment() >> 16) as u16
}
#[inline]
pub fn fragment_id(&self) -> u16 {
(self.acknowledgment() & 0xFFFF) as u16
}
}
impl PacketHeader for SttTcpHeader {
const NAME: &'static str = "SttTcpHeader";
type InnerType = u16;
#[inline]
fn inner_type(&self) -> Self::InnerType {
self.dst_port()
}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
Self::HEADER_SIZE
}
#[inline]
fn is_valid(&self) -> bool {
self.data_offset() >= 5
}
}
impl HeaderParser for SttTcpHeader {
type Output<'a> = &'a SttTcpHeader;
#[inline]
fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
header
}
}
impl fmt::Display for SttTcpHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"STT-TCP {}:{} frag_id={} frag_off={} total_len={}",
self.src_port(),
self.dst_port(),
self.fragment_id(),
self.fragment_offset(),
self.total_length()
)
}
}
#[derive(Debug, Clone)]
pub struct SttPacket<'a> {
pub tcp_header: &'a SttTcpHeader,
pub stt_header: &'a SttHeader,
pub payload: &'a [u8],
}
impl<'a> SttPacket<'a> {
pub fn parse(buf: &'a [u8]) -> Option<Self> {
if buf.len() < SttTcpHeader::HEADER_SIZE + SttHeader::HEADER_SIZE {
return None;
}
let (tcp_ref, rest) = zerocopy::Ref::<_, SttTcpHeader>::from_prefix(buf).ok()?;
let tcp_header = zerocopy::Ref::into_ref(tcp_ref);
let (stt_ref, payload) = zerocopy::Ref::<_, SttHeader>::from_prefix(rest).ok()?;
let stt_header = zerocopy::Ref::into_ref(stt_ref);
if stt_header.version() != STT_VERSION {
return None;
}
Some(SttPacket {
tcp_header,
stt_header,
payload,
})
}
#[inline]
pub fn context_id(&self) -> u64 {
self.stt_header.context_id()
}
#[inline]
pub fn vlan_id(&self) -> Option<u16> {
self.stt_header.vlan_id()
}
#[inline]
pub fn is_fragmented(&self) -> bool {
self.tcp_header.fragment_offset() != 0
|| self.payload.len() < self.tcp_header.total_length() as usize
}
#[inline]
pub fn header_length(&self) -> usize {
SttTcpHeader::HEADER_SIZE + SttHeader::HEADER_SIZE
}
}
impl fmt::Display for SttPacket<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"STT ctx=0x{:016x} mss={} frag_id={}",
self.stt_header.context_id(),
self.stt_header.mss(),
self.tcp_header.fragment_id()
)?;
if let Some(vid) = self.vlan_id() {
write!(f, " vlan={}", vid)?;
}
if self.is_fragmented() {
write!(
f,
" frag_off={} total={}",
self.tcp_header.fragment_offset(),
self.tcp_header.total_length()
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stt_header_size() {
assert_eq!(std::mem::size_of::<SttHeader>(), 18);
assert_eq!(SttHeader::FIXED_LEN, 18);
assert_eq!(std::mem::size_of::<SttTcpHeader>(), 20);
assert_eq!(SttTcpHeader::FIXED_LEN, 20);
}
#[test]
fn test_stt_header_basic() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
];
let (header, payload) = SttHeader::from_bytes(&packet).unwrap();
assert_eq!(header.version(), 0);
assert_eq!(header.flags_raw(), 0);
assert_eq!(header.l4_offset(), 0);
assert_eq!(header.mss(), 1500);
assert!(!header.has_vlan());
assert_eq!(header.vlan_id(), None);
assert_eq!(header.context_id(), 1);
assert_eq!(payload.len(), 4);
}
#[test]
fn test_stt_header_with_vlan() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, ];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
assert!(header.has_vlan());
assert_eq!(header.vlan_id(), Some(100));
assert_eq!(header.context_id(), 10);
}
#[test]
fn test_stt_header_with_pcp() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0xF0, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
assert_eq!(header.pcp(), 7);
assert!(header.has_vlan());
assert_eq!(header.vlan_id(), Some(100));
}
#[test]
fn test_stt_header_flags() {
let packet = vec![
0x00, 0x1F, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
let flags = header.flags();
assert!(flags.is_csum_verified());
assert!(flags.is_csum_partial());
assert!(flags.is_ipv4());
assert!(flags.is_tcp());
assert!(flags.is_csum_present());
}
#[test]
fn test_stt_header_l4_offset() {
let packet = vec![
0x00, 0x00, 0x0A, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
assert_eq!(header.l4_offset(), 10);
assert_eq!(header.l4_offset_bytes(), 20);
}
#[test]
fn test_stt_header_large_context_id() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
assert_eq!(header.context_id(), u64::MAX);
}
#[test]
fn test_stt_header_invalid_version() {
let packet = vec![
0x01, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00,
];
let result = SttHeader::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_stt_header_too_small() {
let packet = vec![0x00, 0x00, 0x00, 0x00]; let result = SttHeader::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_stt_header_display() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("STT"));
assert!(display.contains("ctx="));
assert!(display.contains("vlan=100"));
}
#[test]
fn test_stt_flags_display() {
let flags = SttFlags::new(0x1F);
let display = format!("{}", flags);
assert!(display.contains("CSUM_VERIFIED"));
assert!(display.contains("IPV4"));
assert!(display.contains("TCP"));
let empty_flags = SttFlags::new(0);
assert_eq!(format!("{}", empty_flags), "none");
}
#[test]
fn test_stt_tcp_header_basic() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
assert_eq!(header.src_port(), 0x1234);
assert_eq!(header.dst_port(), STT_PORT);
assert!(header.is_stt());
assert_eq!(header.fragment_offset(), 0);
assert_eq!(header.total_length(), 1500);
assert_eq!(header.fragment_id(), 1);
assert_eq!(header.data_offset(), 5);
assert_eq!(header.data_offset_bytes(), 20);
}
#[test]
fn test_stt_tcp_header_with_fragment() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x10, 0x00, 0x05, 0xDC, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
assert_eq!(header.fragment_offset(), 4096);
assert_eq!(header.fragment_id(), 2);
assert_eq!(header.total_length(), 1500);
}
#[test]
fn test_stt_tcp_header_display() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x01, 0x50, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("STT-TCP"));
assert!(display.contains("7471"));
}
#[test]
fn test_stt_packet_parse() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
let stt_packet = SttPacket::parse(&packet).unwrap();
assert_eq!(stt_packet.context_id(), 10);
assert_eq!(stt_packet.vlan_id(), Some(100));
assert!(stt_packet.stt_header.flags().is_ipv4());
assert_eq!(stt_packet.header_length(), 38);
assert_eq!(stt_packet.payload.len(), 12);
}
#[test]
fn test_stt_packet_fragmented() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
];
let stt_packet = SttPacket::parse(&packet).unwrap();
assert!(stt_packet.is_fragmented());
}
#[test]
fn test_stt_packet_too_small() {
let packet = vec![0x00; 30]; let result = SttPacket::parse(&packet);
assert!(result.is_none());
}
#[test]
fn test_stt_packet_display() {
let packet = vec![
0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x50, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
];
let stt_packet = SttPacket::parse(&packet).unwrap();
let display = format!("{}", stt_packet);
assert!(display.contains("STT"));
assert!(display.contains("ctx="));
assert!(display.contains("vlan=100"));
}
#[test]
fn test_stt_port() {
assert!(is_stt_port(7471));
assert!(!is_stt_port(7472));
assert!(!is_stt_port(4789));
}
#[test]
fn test_stt_inner_type() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34,
0x56, 0x78, 0x00, 0x00,
];
let (header, _) = SttHeader::from_bytes(&packet).unwrap();
assert_eq!(header.inner_type(), 0x12345678);
}
}