packet_strata/packet/tunnel/
stt.rs

1//! STT (Stateless Transport Tunneling) protocol parser
2//!
3//! This module implements parsing for STT as defined in the VMware STT specification
4//! (draft-davie-stt). STT is a tunneling protocol that encapsulates L2 frames using
5//! a TCP-like header format for NIC offload compatibility.
6//!
7//! # STT Header Format
8//!
9//! STT uses a pseudo-TCP header followed by an STT-specific header:
10//!
11//! ```text
12//! TCP-like header (20 bytes):
13//!  0                   1                   2                   3
14//!  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
15//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16//! |          Source Port          |       Destination Port        |
17//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18//! |                        Sequence Number                        |
19//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20//! |                    Acknowledgment Number                      |
21//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22//! |  Data |           |U|A|P|R|S|F|                               |
23//! | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
24//! |       |           |G|K|H|T|N|N|                               |
25//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26//! |           Checksum            |         Urgent Pointer        |
27//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28//!
29//! STT Frame Header (18 bytes):
30//!  0                   1                   2                   3
31//!  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
32//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33//! |  Version      | Flags         |  L4 Offset    |  Reserved     |
34//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35//! |    Max Segment Size           |       PCP |V|     VLAN ID     |
36//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37//! |                                                               |
38//! +                       Context ID (64 bits)                    +
39//! |                                                               |
40//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
41//! |     Padding   |    Padding    |
42//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43//! ```
44//!
45//! # Ports
46//!
47//! - STT default port: TCP port 7471
48//!
49//! # Examples
50//!
51//! ## Basic STT parsing
52//!
53//! ```
54//! use packet_strata::packet::tunnel::stt::SttHeader;
55//! use packet_strata::packet::HeaderParser;
56//!
57//! // STT frame header
58//! let packet = vec![
59//!     0x00,        // Version: 0
60//!     0x00,        // Flags: 0
61//!     0x00,        // L4 Offset: 0
62//!     0x00,        // Reserved
63//!     0x05, 0xDC,  // MSS: 1500
64//!     0x00, 0x64,  // VLAN (V=0, VID=100)
65//!     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  // Context ID: 1
66//!     0x00, 0x00,  // Padding
67//!     // Inner Ethernet frame follows...
68//! ];
69//!
70//! let (header, payload) = SttHeader::from_bytes(&packet).unwrap();
71//! assert_eq!(header.version(), 0);
72//! assert_eq!(header.mss(), 1500);
73//! assert_eq!(header.context_id(), 1);
74//! ```
75//!
76//! ## STT with VLAN tag
77//!
78//! ```
79//! use packet_strata::packet::tunnel::stt::SttHeader;
80//! use packet_strata::packet::HeaderParser;
81//!
82//! // STT with VLAN tag present
83//! let packet = vec![
84//!     0x00,        // Version: 0
85//!     0x00,        // Flags
86//!     0x00,        // L4 Offset
87//!     0x00,        // Reserved
88//!     0x05, 0xDC,  // MSS: 1500
89//!     0x10, 0x64,  // PCP=0, V=1, VID=100
90//!     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,  // Context ID: 10
91//!     0x00, 0x00,  // Padding
92//! ];
93//!
94//! let (header, _) = SttHeader::from_bytes(&packet).unwrap();
95//! assert!(header.has_vlan());
96//! assert_eq!(header.vlan_id(), Some(100));
97//! ```
98
99use std::fmt::{self, Formatter};
100
101use zerocopy::byteorder::{BigEndian, U16, U64};
102use zerocopy::{FromBytes, IntoBytes, Unaligned};
103
104use crate::packet::{HeaderParser, PacketHeader};
105
106/// STT default TCP port
107pub const STT_PORT: u16 = 7471;
108
109/// Check if port is STT
110#[inline]
111pub fn is_stt_port(port: u16) -> bool {
112    port == STT_PORT
113}
114
115/// STT Version
116pub const STT_VERSION: u8 = 0;
117
118/// STT Flags
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub struct SttFlags(pub u8);
121
122impl SttFlags {
123    /// Checksum verified flag
124    pub const CSUM_VERIFIED: u8 = 0x01;
125    /// Checksum partial flag (indicates partial checksum in payload)
126    pub const CSUM_PARTIAL: u8 = 0x02;
127    /// IPv4 payload
128    pub const IPV4: u8 = 0x04;
129    /// TCP payload
130    pub const TCP: u8 = 0x08;
131    /// Checksum present in inner header
132    pub const CSUM_PRESENT: u8 = 0x10;
133
134    /// Create new flags
135    #[inline]
136    pub const fn new(value: u8) -> Self {
137        Self(value)
138    }
139
140    /// Check if checksum verified flag is set
141    #[inline]
142    pub fn is_csum_verified(&self) -> bool {
143        self.0 & Self::CSUM_VERIFIED != 0
144    }
145
146    /// Check if checksum partial flag is set
147    #[inline]
148    pub fn is_csum_partial(&self) -> bool {
149        self.0 & Self::CSUM_PARTIAL != 0
150    }
151
152    /// Check if IPv4 flag is set
153    #[inline]
154    pub fn is_ipv4(&self) -> bool {
155        self.0 & Self::IPV4 != 0
156    }
157
158    /// Check if TCP flag is set
159    #[inline]
160    pub fn is_tcp(&self) -> bool {
161        self.0 & Self::TCP != 0
162    }
163
164    /// Check if checksum present flag is set
165    #[inline]
166    pub fn is_csum_present(&self) -> bool {
167        self.0 & Self::CSUM_PRESENT != 0
168    }
169}
170
171impl fmt::Display for SttFlags {
172    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
173        let mut flags = Vec::new();
174
175        if self.is_csum_verified() {
176            flags.push("CSUM_VERIFIED");
177        }
178        if self.is_csum_partial() {
179            flags.push("CSUM_PARTIAL");
180        }
181        if self.is_ipv4() {
182            flags.push("IPV4");
183        }
184        if self.is_tcp() {
185            flags.push("TCP");
186        }
187        if self.is_csum_present() {
188            flags.push("CSUM_PRESENT");
189        }
190
191        if flags.is_empty() {
192            write!(f, "none")
193        } else {
194            write!(f, "{}", flags.join(","))
195        }
196    }
197}
198
199/// STT Frame Header structure (18 bytes)
200///
201/// This is the STT-specific header that follows the TCP-like header.
202/// The TCP-like header is handled separately as it resembles a standard TCP header.
203#[repr(C, packed)]
204#[derive(
205    FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
206)]
207pub struct SttHeader {
208    version: u8,
209    flags: u8,
210    l4_offset: u8,
211    reserved: u8,
212    mss: U16<BigEndian>,
213    vlan_tci: U16<BigEndian>,
214    context_id: U64<BigEndian>,
215    padding: U16<BigEndian>,
216}
217
218impl SttHeader {
219    /// VLAN present flag in vlan_tci (bit 12)
220    pub const VLAN_PRESENT: u16 = 0x1000;
221
222    /// VLAN ID mask (bits 0-11)
223    pub const VLAN_ID_MASK: u16 = 0x0FFF;
224
225    /// PCP mask (bits 13-15)
226    pub const PCP_MASK: u16 = 0xE000;
227    pub const PCP_SHIFT: u16 = 13;
228
229    /// Header size
230    pub const HEADER_SIZE: usize = 18;
231
232    #[allow(unused)]
233    const NAME: &'static str = "SttHeader";
234
235    /// Returns the STT version (should be 0)
236    #[inline]
237    pub fn version(&self) -> u8 {
238        self.version
239    }
240
241    /// Returns the flags byte
242    #[inline]
243    pub fn flags_raw(&self) -> u8 {
244        self.flags
245    }
246
247    /// Returns the flags as SttFlags struct
248    #[inline]
249    pub fn flags(&self) -> SttFlags {
250        SttFlags::new(self.flags)
251    }
252
253    /// Returns the L4 offset (offset to L4 header in inner packet)
254    ///
255    /// This is the offset from the start of the inner Ethernet frame
256    /// to the start of the L4 (TCP/UDP) header, in 2-byte units.
257    #[inline]
258    pub fn l4_offset(&self) -> u8 {
259        self.l4_offset
260    }
261
262    /// Returns the L4 offset in bytes
263    #[inline]
264    pub fn l4_offset_bytes(&self) -> usize {
265        self.l4_offset as usize * 2
266    }
267
268    /// Returns the Maximum Segment Size (MSS)
269    #[inline]
270    pub fn mss(&self) -> u16 {
271        self.mss.get()
272    }
273
274    /// Returns the raw VLAN TCI field
275    #[inline]
276    pub fn vlan_tci_raw(&self) -> u16 {
277        self.vlan_tci.get()
278    }
279
280    /// Returns true if VLAN is present
281    #[inline]
282    pub fn has_vlan(&self) -> bool {
283        self.vlan_tci.get() & Self::VLAN_PRESENT != 0
284    }
285
286    /// Returns the VLAN ID if present
287    #[inline]
288    pub fn vlan_id(&self) -> Option<u16> {
289        if self.has_vlan() {
290            Some(self.vlan_tci.get() & Self::VLAN_ID_MASK)
291        } else {
292            None
293        }
294    }
295
296    /// Returns the PCP (Priority Code Point) value
297    #[inline]
298    pub fn pcp(&self) -> u8 {
299        ((self.vlan_tci.get() & Self::PCP_MASK) >> Self::PCP_SHIFT) as u8
300    }
301
302    /// Returns the 64-bit Context ID (virtual network identifier)
303    #[inline]
304    pub fn context_id(&self) -> u64 {
305        self.context_id.get()
306    }
307
308    /// Validates the STT header
309    #[inline]
310    fn is_valid(&self) -> bool {
311        // Version should be 0
312        self.version == STT_VERSION
313    }
314}
315
316impl PacketHeader for SttHeader {
317    const NAME: &'static str = "SttHeader";
318    /// Inner type - context ID
319    type InnerType = u64;
320
321    #[inline]
322    fn inner_type(&self) -> Self::InnerType {
323        self.context_id()
324    }
325
326    /// Returns the total header length (always 18 bytes)
327    #[inline]
328    fn total_len(&self, _buf: &[u8]) -> usize {
329        Self::HEADER_SIZE
330    }
331
332    /// Validates the STT header
333    #[inline]
334    fn is_valid(&self) -> bool {
335        self.is_valid()
336    }
337}
338
339impl HeaderParser for SttHeader {
340    type Output<'a> = &'a SttHeader;
341
342    #[inline]
343    fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
344        header
345    }
346}
347
348impl fmt::Display for SttHeader {
349    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
350        write!(
351            f,
352            "STT v{} ctx=0x{:016x} mss={} flags=[{}]",
353            self.version(),
354            self.context_id(),
355            self.mss(),
356            self.flags()
357        )?;
358
359        if let Some(vid) = self.vlan_id() {
360            write!(f, " vlan={}", vid)?;
361        }
362
363        Ok(())
364    }
365}
366
367/// STT TCP-like header (pseudo-TCP header used for offloading)
368///
369/// This header precedes the STT frame header and is designed to
370/// leverage NIC TCP segmentation offload (TSO) capabilities.
371#[repr(C, packed)]
372#[derive(
373    FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
374)]
375pub struct SttTcpHeader {
376    src_port: U16<BigEndian>,
377    dst_port: U16<BigEndian>,
378    seq: [u8; 4], // Used for fragmentation
379    ack: [u8; 4], // Used for fragmentation
380    data_offset_flags: U16<BigEndian>,
381    window: U16<BigEndian>,
382    checksum: U16<BigEndian>,
383    urgent: U16<BigEndian>,
384}
385
386impl SttTcpHeader {
387    /// STT TCP header size
388    pub const HEADER_SIZE: usize = 20;
389
390    /// Returns the source port
391    #[inline]
392    pub fn src_port(&self) -> u16 {
393        self.src_port.get()
394    }
395
396    /// Returns the destination port (should be 7471 for STT)
397    #[inline]
398    pub fn dst_port(&self) -> u16 {
399        self.dst_port.get()
400    }
401
402    /// Returns the sequence number (used for fragmentation info)
403    ///
404    /// In STT, this contains fragmentation offset information.
405    #[inline]
406    pub fn sequence(&self) -> u32 {
407        u32::from_be_bytes(self.seq)
408    }
409
410    /// Returns the acknowledgment number
411    ///
412    /// In STT, this contains fragment ID and fragmentation info.
413    #[inline]
414    pub fn acknowledgment(&self) -> u32 {
415        u32::from_be_bytes(self.ack)
416    }
417
418    /// Returns the data offset (in 32-bit words)
419    #[inline]
420    pub fn data_offset(&self) -> u8 {
421        ((self.data_offset_flags.get() >> 12) & 0x0F) as u8
422    }
423
424    /// Returns the data offset in bytes
425    #[inline]
426    pub fn data_offset_bytes(&self) -> usize {
427        self.data_offset() as usize * 4
428    }
429
430    /// Returns the TCP flags
431    #[inline]
432    pub fn tcp_flags(&self) -> u8 {
433        (self.data_offset_flags.get() & 0x003F) as u8
434    }
435
436    /// Returns the window size
437    #[inline]
438    pub fn window(&self) -> u16 {
439        self.window.get()
440    }
441
442    /// Returns the checksum
443    #[inline]
444    pub fn checksum(&self) -> u16 {
445        self.checksum.get()
446    }
447
448    /// Returns the urgent pointer
449    #[inline]
450    pub fn urgent(&self) -> u16 {
451        self.urgent.get()
452    }
453
454    /// Check if this is an STT packet (destination port is 7471)
455    #[inline]
456    pub fn is_stt(&self) -> bool {
457        self.dst_port() == STT_PORT
458    }
459
460    /// Extract fragment offset from sequence number
461    ///
462    /// The fragment offset is stored in the sequence number field
463    /// and represents the byte offset within the original frame.
464    #[inline]
465    pub fn fragment_offset(&self) -> u32 {
466        self.sequence()
467    }
468
469    /// Extract total frame length from acknowledgment number
470    ///
471    /// The total frame length is stored in bits 16-31 of the ack field.
472    #[inline]
473    pub fn total_length(&self) -> u16 {
474        (self.acknowledgment() >> 16) as u16
475    }
476
477    /// Extract fragment ID from acknowledgment number
478    ///
479    /// The fragment ID is stored in bits 0-15 of the ack field.
480    #[inline]
481    pub fn fragment_id(&self) -> u16 {
482        (self.acknowledgment() & 0xFFFF) as u16
483    }
484}
485
486impl PacketHeader for SttTcpHeader {
487    const NAME: &'static str = "SttTcpHeader";
488    type InnerType = u16;
489
490    #[inline]
491    fn inner_type(&self) -> Self::InnerType {
492        self.dst_port()
493    }
494
495    #[inline]
496    fn total_len(&self, _buf: &[u8]) -> usize {
497        Self::HEADER_SIZE
498    }
499
500    #[inline]
501    fn is_valid(&self) -> bool {
502        // Data offset must be at least 5 (20 bytes)
503        self.data_offset() >= 5
504    }
505}
506
507impl HeaderParser for SttTcpHeader {
508    type Output<'a> = &'a SttTcpHeader;
509
510    #[inline]
511    fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
512        header
513    }
514}
515
516impl fmt::Display for SttTcpHeader {
517    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
518        write!(
519            f,
520            "STT-TCP {}:{} frag_id={} frag_off={} total_len={}",
521            self.src_port(),
522            self.dst_port(),
523            self.fragment_id(),
524            self.fragment_offset(),
525            self.total_length()
526        )
527    }
528}
529
530/// Complete STT packet (TCP-like header + STT header)
531#[derive(Debug, Clone)]
532pub struct SttPacket<'a> {
533    /// The TCP-like header
534    pub tcp_header: &'a SttTcpHeader,
535    /// The STT frame header
536    pub stt_header: &'a SttHeader,
537    /// The inner payload (Ethernet frame)
538    pub payload: &'a [u8],
539}
540
541impl<'a> SttPacket<'a> {
542    /// Parse a complete STT packet from buffer
543    ///
544    /// Expects the buffer to start with the TCP-like header, followed by
545    /// the STT frame header, and then the inner Ethernet frame.
546    pub fn parse(buf: &'a [u8]) -> Option<Self> {
547        if buf.len() < SttTcpHeader::HEADER_SIZE + SttHeader::HEADER_SIZE {
548            return None;
549        }
550
551        let (tcp_ref, rest) = zerocopy::Ref::<_, SttTcpHeader>::from_prefix(buf).ok()?;
552        let tcp_header = zerocopy::Ref::into_ref(tcp_ref);
553
554        let (stt_ref, payload) = zerocopy::Ref::<_, SttHeader>::from_prefix(rest).ok()?;
555        let stt_header = zerocopy::Ref::into_ref(stt_ref);
556
557        // Validate
558        if stt_header.version() != STT_VERSION {
559            return None;
560        }
561
562        Some(SttPacket {
563            tcp_header,
564            stt_header,
565            payload,
566        })
567    }
568
569    /// Returns the context ID (virtual network identifier)
570    #[inline]
571    pub fn context_id(&self) -> u64 {
572        self.stt_header.context_id()
573    }
574
575    /// Returns the VLAN ID if present
576    #[inline]
577    pub fn vlan_id(&self) -> Option<u16> {
578        self.stt_header.vlan_id()
579    }
580
581    /// Returns true if this is a fragmented packet
582    #[inline]
583    pub fn is_fragmented(&self) -> bool {
584        self.tcp_header.fragment_offset() != 0
585            || self.payload.len() < self.tcp_header.total_length() as usize
586    }
587
588    /// Returns the total combined header length
589    #[inline]
590    pub fn header_length(&self) -> usize {
591        SttTcpHeader::HEADER_SIZE + SttHeader::HEADER_SIZE
592    }
593}
594
595impl fmt::Display for SttPacket<'_> {
596    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
597        write!(
598            f,
599            "STT ctx=0x{:016x} mss={} frag_id={}",
600            self.stt_header.context_id(),
601            self.stt_header.mss(),
602            self.tcp_header.fragment_id()
603        )?;
604
605        if let Some(vid) = self.vlan_id() {
606            write!(f, " vlan={}", vid)?;
607        }
608
609        if self.is_fragmented() {
610            write!(
611                f,
612                " frag_off={} total={}",
613                self.tcp_header.fragment_offset(),
614                self.tcp_header.total_length()
615            )?;
616        }
617
618        Ok(())
619    }
620}
621
622#[cfg(test)]
623mod tests {
624    use super::*;
625
626    #[test]
627    fn test_stt_header_size() {
628        assert_eq!(std::mem::size_of::<SttHeader>(), 18);
629        assert_eq!(SttHeader::FIXED_LEN, 18);
630        assert_eq!(std::mem::size_of::<SttTcpHeader>(), 20);
631        assert_eq!(SttTcpHeader::FIXED_LEN, 20);
632    }
633
634    #[test]
635    fn test_stt_header_basic() {
636        let packet = vec![
637            0x00, // Version: 0
638            0x00, // Flags: 0
639            0x00, // L4 Offset: 0
640            0x00, // Reserved
641            0x05, 0xDC, // MSS: 1500
642            0x00, 0x00, // VLAN (V=0)
643            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Context ID: 1
644            0x00, 0x00, // Padding
645            // Payload
646            0xFF, 0xFF, 0xFF, 0xFF,
647        ];
648
649        let (header, payload) = SttHeader::from_bytes(&packet).unwrap();
650        assert_eq!(header.version(), 0);
651        assert_eq!(header.flags_raw(), 0);
652        assert_eq!(header.l4_offset(), 0);
653        assert_eq!(header.mss(), 1500);
654        assert!(!header.has_vlan());
655        assert_eq!(header.vlan_id(), None);
656        assert_eq!(header.context_id(), 1);
657        assert_eq!(payload.len(), 4);
658    }
659
660    #[test]
661    fn test_stt_header_with_vlan() {
662        let packet = vec![
663            0x00, // Version
664            0x00, // Flags
665            0x00, // L4 Offset
666            0x00, // Reserved
667            0x05, 0xDC, // MSS: 1500
668            0x10, 0x64, // VLAN: V=1, VID=100
669            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, // Context ID: 10
670            0x00, 0x00, // Padding
671        ];
672
673        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
674        assert!(header.has_vlan());
675        assert_eq!(header.vlan_id(), Some(100));
676        assert_eq!(header.context_id(), 10);
677    }
678
679    #[test]
680    fn test_stt_header_with_pcp() {
681        let packet = vec![
682            0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0xF0, 0x64, // PCP=7, V=1, VID=100
683            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
684        ];
685
686        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
687        assert_eq!(header.pcp(), 7);
688        assert!(header.has_vlan());
689        assert_eq!(header.vlan_id(), Some(100));
690    }
691
692    #[test]
693    fn test_stt_header_flags() {
694        let packet = vec![
695            0x00, 0x1F, // All flags set
696            0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
697            0x00, 0x00,
698        ];
699
700        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
701        let flags = header.flags();
702        assert!(flags.is_csum_verified());
703        assert!(flags.is_csum_partial());
704        assert!(flags.is_ipv4());
705        assert!(flags.is_tcp());
706        assert!(flags.is_csum_present());
707    }
708
709    #[test]
710    fn test_stt_header_l4_offset() {
711        let packet = vec![
712            0x00, 0x00, 0x0A, // L4 Offset: 10 (20 bytes)
713            0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
714            0x00,
715        ];
716
717        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
718        assert_eq!(header.l4_offset(), 10);
719        assert_eq!(header.l4_offset_bytes(), 20);
720    }
721
722    #[test]
723    fn test_stt_header_large_context_id() {
724        let packet = vec![
725            0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
726            0xFF, 0xFF, // Max context ID
727            0x00, 0x00,
728        ];
729
730        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
731        assert_eq!(header.context_id(), u64::MAX);
732    }
733
734    #[test]
735    fn test_stt_header_invalid_version() {
736        let packet = vec![
737            0x01, // Version: 1 (invalid)
738            0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
739            0x01, 0x00, 0x00,
740        ];
741
742        let result = SttHeader::from_bytes(&packet);
743        assert!(result.is_err());
744    }
745
746    #[test]
747    fn test_stt_header_too_small() {
748        let packet = vec![0x00, 0x00, 0x00, 0x00]; // Only 4 bytes
749        let result = SttHeader::from_bytes(&packet);
750        assert!(result.is_err());
751    }
752
753    #[test]
754    fn test_stt_header_display() {
755        let packet = vec![
756            0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64, // VLAN=100
757            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
758        ];
759
760        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
761        let display = format!("{}", header);
762        assert!(display.contains("STT"));
763        assert!(display.contains("ctx="));
764        assert!(display.contains("vlan=100"));
765    }
766
767    #[test]
768    fn test_stt_flags_display() {
769        let flags = SttFlags::new(0x1F);
770        let display = format!("{}", flags);
771        assert!(display.contains("CSUM_VERIFIED"));
772        assert!(display.contains("IPV4"));
773        assert!(display.contains("TCP"));
774
775        let empty_flags = SttFlags::new(0);
776        assert_eq!(format!("{}", empty_flags), "none");
777    }
778
779    #[test]
780    fn test_stt_tcp_header_basic() {
781        let packet = vec![
782            0x12, 0x34, // Src port: 0x1234
783            0x1D, 0x2F, // Dst port: 7471 (STT)
784            0x00, 0x00, 0x00, 0x00, // Sequence (frag offset)
785            0x05, 0xDC, 0x00, 0x01, // Ack: total_len=1500, frag_id=1
786            0x50, 0x00, // Data offset=5, flags=0
787            0x00, 0x00, // Window
788            0x00, 0x00, // Checksum
789            0x00, 0x00, // Urgent
790        ];
791
792        let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
793        assert_eq!(header.src_port(), 0x1234);
794        assert_eq!(header.dst_port(), STT_PORT);
795        assert!(header.is_stt());
796        assert_eq!(header.fragment_offset(), 0);
797        assert_eq!(header.total_length(), 1500);
798        assert_eq!(header.fragment_id(), 1);
799        assert_eq!(header.data_offset(), 5);
800        assert_eq!(header.data_offset_bytes(), 20);
801    }
802
803    #[test]
804    fn test_stt_tcp_header_with_fragment() {
805        let packet = vec![
806            0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x10, 0x00, // Frag offset: 4096
807            0x05, 0xDC, 0x00, 0x02, // total_len=1500, frag_id=2
808            0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
809        ];
810
811        let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
812        assert_eq!(header.fragment_offset(), 4096);
813        assert_eq!(header.fragment_id(), 2);
814        assert_eq!(header.total_length(), 1500);
815    }
816
817    #[test]
818    fn test_stt_tcp_header_display() {
819        let packet = vec![
820            0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x01, 0x50, 0x00,
821            0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
822        ];
823
824        let (header, _) = SttTcpHeader::from_bytes(&packet).unwrap();
825        let display = format!("{}", header);
826        assert!(display.contains("STT-TCP"));
827        assert!(display.contains("7471"));
828    }
829
830    #[test]
831    fn test_stt_packet_parse() {
832        let packet = vec![
833            // TCP-like header (20 bytes)
834            0x12, 0x34, // Src port
835            0x1D, 0x2F, // Dst port: 7471
836            0x00, 0x00, 0x00, 0x00, // Sequence
837            0x00, 0x1C, 0x00, 0x01, // Ack: total_len=28, frag_id=1
838            0x50, 0x00, // Data offset=5
839            0x00, 0x00, // Window
840            0x00, 0x00, // Checksum
841            0x00, 0x00, // Urgent
842            // STT header (18 bytes)
843            0x00, // Version
844            0x04, // Flags: IPv4
845            0x00, // L4 Offset
846            0x00, // Reserved
847            0x05, 0xDC, // MSS: 1500
848            0x10, 0x64, // VLAN: V=1, VID=100
849            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, // Context ID: 10
850            0x00, 0x00, // Padding
851            // Payload
852            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dst MAC
853            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Src MAC
854        ];
855
856        let stt_packet = SttPacket::parse(&packet).unwrap();
857        assert_eq!(stt_packet.context_id(), 10);
858        assert_eq!(stt_packet.vlan_id(), Some(100));
859        assert!(stt_packet.stt_header.flags().is_ipv4());
860        assert_eq!(stt_packet.header_length(), 38);
861        assert_eq!(stt_packet.payload.len(), 12);
862    }
863
864    #[test]
865    fn test_stt_packet_fragmented() {
866        let packet = vec![
867            // TCP-like header with fragment offset
868            0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x10, 0x00, // frag offset != 0
869            0x10, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
870            // STT header
871            0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
872            0x00, 0x01, 0x00, 0x00,
873        ];
874
875        let stt_packet = SttPacket::parse(&packet).unwrap();
876        assert!(stt_packet.is_fragmented());
877    }
878
879    #[test]
880    fn test_stt_packet_too_small() {
881        let packet = vec![0x00; 30]; // Less than 38 bytes
882        let result = SttPacket::parse(&packet);
883        assert!(result.is_none());
884    }
885
886    #[test]
887    fn test_stt_packet_display() {
888        let packet = vec![
889            0x12, 0x34, 0x1D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x50, 0x00,
890            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x10, 0x64,
891            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
892        ];
893
894        let stt_packet = SttPacket::parse(&packet).unwrap();
895        let display = format!("{}", stt_packet);
896        assert!(display.contains("STT"));
897        assert!(display.contains("ctx="));
898        assert!(display.contains("vlan=100"));
899    }
900
901    #[test]
902    fn test_stt_port() {
903        assert!(is_stt_port(7471));
904        assert!(!is_stt_port(7472));
905        assert!(!is_stt_port(4789));
906    }
907
908    #[test]
909    fn test_stt_inner_type() {
910        let packet = vec![
911            0x00, 0x00, 0x00, 0x00, 0x05, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34,
912            0x56, 0x78, // Context ID
913            0x00, 0x00,
914        ];
915
916        let (header, _) = SttHeader::from_bytes(&packet).unwrap();
917        assert_eq!(header.inner_type(), 0x12345678);
918    }
919}