Skip to main content

stackforge_core/layer/tcp/
options.rs

1//! TCP options parsing and building.
2//!
3//! TCP options follow the header and are variable-length.
4//! Maximum options length is 40 bytes (60 byte header - 20 byte minimum).
5//!
6//! # Option Format
7//!
8//! Single-byte options (EOL, NOP):
9//! ```text
10//! +--------+
11//! |  Kind  |
12//! +--------+
13//! ```
14//!
15//! Multi-byte options:
16//! ```text
17//! +--------+--------+--------...
18//! |  Kind  | Length | Data
19//! +--------+--------+--------...
20//! ```
21
22use crate::layer::field::FieldError;
23
24/// Maximum length of TCP options (in bytes).
25pub const MAX_OPTIONS_LEN: usize = 40;
26
27/// TCP option kinds.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[repr(u8)]
30pub enum TcpOptionKind {
31    /// End of Option List (RFC 793)
32    Eol = 0,
33    /// No Operation (RFC 793)
34    Nop = 1,
35    /// Maximum Segment Size (RFC 793)
36    Mss = 2,
37    /// Window Scale (RFC 7323)
38    WScale = 3,
39    /// SACK Permitted (RFC 2018)
40    SackOk = 4,
41    /// SACK (RFC 2018)
42    Sack = 5,
43    /// Timestamps (RFC 7323)
44    Timestamp = 8,
45    /// Alternate Checksum Request (RFC 1146)
46    AltChkSum = 14,
47    /// Alternate Checksum Data (RFC 1146)
48    AltChkSumOpt = 15,
49    /// MD5 Signature (RFC 2385)
50    Md5 = 19,
51    /// Mood (RFC 5841) - April Fools
52    Mood = 25,
53    /// User Timeout Option (RFC 5482)
54    Uto = 28,
55    /// Authentication Option (RFC 5925)
56    Ao = 29,
57    /// TCP Fast Open (RFC 7413)
58    Tfo = 34,
59    /// Unknown option
60    Unknown(u8),
61}
62
63impl TcpOptionKind {
64    /// Create from raw option kind byte.
65    #[must_use]
66    pub fn from_byte(b: u8) -> Self {
67        match b {
68            0 => Self::Eol,
69            1 => Self::Nop,
70            2 => Self::Mss,
71            3 => Self::WScale,
72            4 => Self::SackOk,
73            5 => Self::Sack,
74            8 => Self::Timestamp,
75            14 => Self::AltChkSum,
76            15 => Self::AltChkSumOpt,
77            19 => Self::Md5,
78            25 => Self::Mood,
79            28 => Self::Uto,
80            29 => Self::Ao,
81            34 => Self::Tfo,
82            x => Self::Unknown(x),
83        }
84    }
85
86    /// Convert to raw option kind byte.
87    #[must_use]
88    pub fn to_byte(self) -> u8 {
89        match self {
90            Self::Eol => 0,
91            Self::Nop => 1,
92            Self::Mss => 2,
93            Self::WScale => 3,
94            Self::SackOk => 4,
95            Self::Sack => 5,
96            Self::Timestamp => 8,
97            Self::AltChkSum => 14,
98            Self::AltChkSumOpt => 15,
99            Self::Md5 => 19,
100            Self::Mood => 25,
101            Self::Uto => 28,
102            Self::Ao => 29,
103            Self::Tfo => 34,
104            Self::Unknown(x) => x,
105        }
106    }
107
108    /// Get the name of the option.
109    #[must_use]
110    pub fn name(&self) -> &'static str {
111        match self {
112            Self::Eol => "EOL",
113            Self::Nop => "NOP",
114            Self::Mss => "MSS",
115            Self::WScale => "WScale",
116            Self::SackOk => "SAckOK",
117            Self::Sack => "SAck",
118            Self::Timestamp => "Timestamp",
119            Self::AltChkSum => "AltChkSum",
120            Self::AltChkSumOpt => "AltChkSumOpt",
121            Self::Md5 => "MD5",
122            Self::Mood => "Mood",
123            Self::Uto => "UTO",
124            Self::Ao => "AO",
125            Self::Tfo => "TFO",
126            Self::Unknown(_) => "Unknown",
127        }
128    }
129
130    /// Check if this is a single-byte option (no length/data).
131    #[inline]
132    #[must_use]
133    pub fn is_single_byte(&self) -> bool {
134        matches!(self, Self::Eol | Self::Nop)
135    }
136
137    /// Get the expected fixed length for this option (if any).
138    /// Returns None for variable-length options.
139    #[must_use]
140    pub fn expected_len(&self) -> Option<usize> {
141        match self {
142            Self::Eol | Self::Nop => Some(1),
143            Self::Mss => Some(4),
144            Self::WScale => Some(3),
145            Self::SackOk => Some(2),
146            Self::Timestamp => Some(10),
147            Self::AltChkSum => Some(4), // Variable but typical
148            Self::AltChkSumOpt => Some(2),
149            Self::Md5 => Some(18),
150            Self::Uto => Some(4),
151            Self::Tfo => Some(10), // Can vary (2-18)
152            Self::Sack | Self::Ao | Self::Mood | Self::Unknown(_) => None,
153        }
154    }
155}
156
157impl std::fmt::Display for TcpOptionKind {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            Self::Unknown(x) => write!(f, "Unknown({x})"),
161            _ => write!(f, "{}", self.name()),
162        }
163    }
164}
165
166/// TCP SACK block (pair of sequence numbers).
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct TcpSackBlock {
169    /// Left edge of block
170    pub left: u32,
171    /// Right edge of block
172    pub right: u32,
173}
174
175impl TcpSackBlock {
176    /// Create a new SACK block.
177    #[must_use]
178    pub fn new(left: u32, right: u32) -> Self {
179        Self { left, right }
180    }
181}
182
183/// TCP Timestamp option data.
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct TcpTimestamp {
186    /// Timestamp value
187    pub ts_val: u32,
188    /// Timestamp echo reply
189    pub ts_ecr: u32,
190}
191
192impl TcpTimestamp {
193    /// Create a new timestamp.
194    #[must_use]
195    pub fn new(ts_val: u32, ts_ecr: u32) -> Self {
196        Self { ts_val, ts_ecr }
197    }
198}
199
200/// TCP Authentication Option (AO) value per RFC 5925.
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct TcpAoValue {
203    /// Key ID
204    pub key_id: u8,
205    /// Receive next key ID
206    pub rnext_key_id: u8,
207    /// MAC (Message Authentication Code)
208    pub mac: Vec<u8>,
209}
210
211impl TcpAoValue {
212    /// Create a new AO value.
213    #[must_use]
214    pub fn new(key_id: u8, rnext_key_id: u8, mac: Vec<u8>) -> Self {
215        Self {
216            key_id,
217            rnext_key_id,
218            mac,
219        }
220    }
221}
222
223/// A parsed TCP option.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub enum TcpOption {
226    /// End of Option List (kind 0)
227    Eol,
228
229    /// No Operation / Padding (kind 1)
230    Nop,
231
232    /// Maximum Segment Size (kind 2)
233    Mss(u16),
234
235    /// Window Scale (kind 3)
236    WScale(u8),
237
238    /// SACK Permitted (kind 4)
239    SackOk,
240
241    /// Selective Acknowledgment (kind 5)
242    Sack(Vec<TcpSackBlock>),
243
244    /// Timestamps (kind 8)
245    Timestamp(TcpTimestamp),
246
247    /// Alternate Checksum Request (kind 14)
248    AltChkSum { algorithm: u8, checksum: u16 },
249
250    /// Alternate Checksum Data (kind 15)
251    AltChkSumOpt,
252
253    /// MD5 Signature (kind 19)
254    Md5([u8; 16]),
255
256    /// Mood (kind 25) - RFC 5841
257    Mood(String),
258
259    /// User Timeout Option (kind 28)
260    Uto(u16),
261
262    /// Authentication Option (kind 29)
263    Ao(TcpAoValue),
264
265    /// TCP Fast Open (kind 34)
266    Tfo {
267        /// TFO cookie (optional)
268        cookie: Option<Vec<u8>>,
269    },
270
271    /// Unknown or unimplemented option
272    Unknown { kind: u8, data: Vec<u8> },
273}
274
275impl TcpOption {
276    /// Get the option kind.
277    #[must_use]
278    pub fn kind(&self) -> TcpOptionKind {
279        match self {
280            Self::Eol => TcpOptionKind::Eol,
281            Self::Nop => TcpOptionKind::Nop,
282            Self::Mss(_) => TcpOptionKind::Mss,
283            Self::WScale(_) => TcpOptionKind::WScale,
284            Self::SackOk => TcpOptionKind::SackOk,
285            Self::Sack(_) => TcpOptionKind::Sack,
286            Self::Timestamp(_) => TcpOptionKind::Timestamp,
287            Self::AltChkSum { .. } => TcpOptionKind::AltChkSum,
288            Self::AltChkSumOpt => TcpOptionKind::AltChkSumOpt,
289            Self::Md5(_) => TcpOptionKind::Md5,
290            Self::Mood(_) => TcpOptionKind::Mood,
291            Self::Uto(_) => TcpOptionKind::Uto,
292            Self::Ao(_) => TcpOptionKind::Ao,
293            Self::Tfo { .. } => TcpOptionKind::Tfo,
294            Self::Unknown { kind, .. } => TcpOptionKind::Unknown(*kind),
295        }
296    }
297
298    /// Get the serialized length of this option.
299    #[must_use]
300    pub fn len(&self) -> usize {
301        match self {
302            Self::Eol => 1,
303            Self::Nop => 1,
304            Self::Mss(_) => 4,
305            Self::WScale(_) => 3,
306            Self::SackOk => 2,
307            Self::Sack(blocks) => 2 + blocks.len() * 8,
308            Self::Timestamp(_) => 10,
309            Self::AltChkSum { .. } => 4,
310            Self::AltChkSumOpt => 2,
311            Self::Md5(_) => 18,
312            Self::Mood(s) => 2 + s.len(),
313            Self::Uto(_) => 4,
314            Self::Ao(ao) => 4 + ao.mac.len(),
315            Self::Tfo { cookie: None } => 2,
316            Self::Tfo { cookie: Some(c) } => 2 + c.len(),
317            Self::Unknown { data, .. } => 2 + data.len(),
318        }
319    }
320
321    /// Check if the option has data (for variants with data).
322    #[must_use]
323    pub fn is_empty(&self) -> bool {
324        self.len() == 0
325    }
326
327    /// Serialize the option to bytes.
328    #[must_use]
329    pub fn to_bytes(&self) -> Vec<u8> {
330        match self {
331            Self::Eol => vec![0],
332            Self::Nop => vec![1],
333
334            Self::Mss(mss) => {
335                let mut buf = vec![2, 4];
336                buf.extend_from_slice(&mss.to_be_bytes());
337                buf
338            },
339
340            Self::WScale(scale) => vec![3, 3, *scale],
341
342            Self::SackOk => vec![4, 2],
343
344            Self::Sack(blocks) => {
345                let mut buf = vec![5, (2 + blocks.len() * 8) as u8];
346                for block in blocks {
347                    buf.extend_from_slice(&block.left.to_be_bytes());
348                    buf.extend_from_slice(&block.right.to_be_bytes());
349                }
350                buf
351            },
352
353            Self::Timestamp(ts) => {
354                let mut buf = vec![8, 10];
355                buf.extend_from_slice(&ts.ts_val.to_be_bytes());
356                buf.extend_from_slice(&ts.ts_ecr.to_be_bytes());
357                buf
358            },
359
360            Self::AltChkSum {
361                algorithm,
362                checksum,
363            } => {
364                let mut buf = vec![14, 4, *algorithm];
365                buf.extend_from_slice(&checksum.to_be_bytes());
366                buf
367            },
368
369            Self::AltChkSumOpt => vec![15, 2],
370
371            Self::Md5(sig) => {
372                let mut buf = vec![19, 18];
373                buf.extend_from_slice(sig);
374                buf
375            },
376
377            Self::Mood(mood) => {
378                let mut buf = vec![25, (2 + mood.len()) as u8];
379                buf.extend_from_slice(mood.as_bytes());
380                buf
381            },
382
383            Self::Uto(timeout) => {
384                let mut buf = vec![28, 4];
385                buf.extend_from_slice(&timeout.to_be_bytes());
386                buf
387            },
388
389            Self::Ao(ao) => {
390                let len = 4 + ao.mac.len();
391                let mut buf = vec![29, len as u8, ao.key_id, ao.rnext_key_id];
392                buf.extend_from_slice(&ao.mac);
393                buf
394            },
395
396            Self::Tfo { cookie: None } => vec![34, 2],
397
398            Self::Tfo { cookie: Some(c) } => {
399                let mut buf = vec![34, (2 + c.len()) as u8];
400                buf.extend_from_slice(c);
401                buf
402            },
403
404            Self::Unknown { kind, data } => {
405                let mut buf = vec![*kind, (2 + data.len()) as u8];
406                buf.extend_from_slice(data);
407                buf
408            },
409        }
410    }
411
412    /// Create an MSS option.
413    #[must_use]
414    pub fn mss(mss: u16) -> Self {
415        Self::Mss(mss)
416    }
417
418    /// Create a Window Scale option.
419    #[must_use]
420    pub fn wscale(scale: u8) -> Self {
421        Self::WScale(scale)
422    }
423
424    /// Create a Timestamp option.
425    #[must_use]
426    pub fn timestamp(ts_val: u32, ts_ecr: u32) -> Self {
427        Self::Timestamp(TcpTimestamp::new(ts_val, ts_ecr))
428    }
429
430    /// Create a SACK option.
431    #[must_use]
432    pub fn sack(blocks: Vec<TcpSackBlock>) -> Self {
433        Self::Sack(blocks)
434    }
435}
436
437/// Collection of TCP options.
438#[derive(Debug, Clone, Default, PartialEq, Eq)]
439pub struct TcpOptions {
440    pub options: Vec<TcpOption>,
441}
442
443impl TcpOptions {
444    /// Create empty options.
445    #[must_use]
446    pub fn new() -> Self {
447        Self::default()
448    }
449
450    /// Create from a list of options.
451    #[must_use]
452    pub fn from_vec(options: Vec<TcpOption>) -> Self {
453        Self { options }
454    }
455
456    /// Check if there are no options.
457    #[must_use]
458    pub fn is_empty(&self) -> bool {
459        self.options.is_empty()
460    }
461
462    /// Get the number of options.
463    #[must_use]
464    pub fn len(&self) -> usize {
465        self.options.len()
466    }
467
468    /// Get the total serialized length.
469    #[must_use]
470    pub fn byte_len(&self) -> usize {
471        self.options.iter().map(TcpOption::len).sum()
472    }
473
474    /// Get the padded length (aligned to 4 bytes).
475    #[must_use]
476    pub fn padded_len(&self) -> usize {
477        let len = self.byte_len();
478        (len + 3) & !3
479    }
480
481    /// Add an option.
482    pub fn push(&mut self, option: TcpOption) {
483        self.options.push(option);
484    }
485
486    /// Get an option by kind.
487    #[must_use]
488    pub fn get(&self, kind: TcpOptionKind) -> Option<&TcpOption> {
489        self.options.iter().find(|o| o.kind() == kind)
490    }
491
492    /// Get the MSS value if present.
493    #[must_use]
494    pub fn mss(&self) -> Option<u16> {
495        self.options.iter().find_map(|o| match o {
496            TcpOption::Mss(mss) => Some(*mss),
497            _ => None,
498        })
499    }
500
501    /// Get the Window Scale value if present.
502    #[must_use]
503    pub fn wscale(&self) -> Option<u8> {
504        self.options.iter().find_map(|o| match o {
505            TcpOption::WScale(scale) => Some(*scale),
506            _ => None,
507        })
508    }
509
510    /// Get the Timestamp if present.
511    #[must_use]
512    pub fn timestamp(&self) -> Option<TcpTimestamp> {
513        self.options.iter().find_map(|o| match o {
514            TcpOption::Timestamp(ts) => Some(*ts),
515            _ => None,
516        })
517    }
518
519    /// Check if SACK is permitted.
520    #[must_use]
521    pub fn sack_permitted(&self) -> bool {
522        self.options.iter().any(|o| matches!(o, TcpOption::SackOk))
523    }
524
525    /// Get the SACK blocks if present.
526    #[must_use]
527    pub fn sack_blocks(&self) -> Option<&[TcpSackBlock]> {
528        self.options.iter().find_map(|o| match o {
529            TcpOption::Sack(blocks) => Some(blocks.as_slice()),
530            _ => None,
531        })
532    }
533
534    /// Get the Authentication Option if present.
535    #[must_use]
536    pub fn ao(&self) -> Option<&TcpAoValue> {
537        self.options.iter().find_map(|o| match o {
538            TcpOption::Ao(ao) => Some(ao),
539            _ => None,
540        })
541    }
542
543    /// Get the TFO cookie if present.
544    #[must_use]
545    pub fn tfo_cookie(&self) -> Option<&[u8]> {
546        self.options.iter().find_map(|o| match o {
547            TcpOption::Tfo { cookie: Some(c) } => Some(c.as_slice()),
548            _ => None,
549        })
550    }
551
552    /// Serialize all options to bytes (with padding).
553    #[must_use]
554    pub fn to_bytes(&self) -> Vec<u8> {
555        let mut buf = Vec::new();
556        for opt in &self.options {
557            buf.extend_from_slice(&opt.to_bytes());
558        }
559
560        // Pad to 4-byte boundary
561        let pad = (4 - (buf.len() % 4)) % 4;
562        buf.extend(std::iter::repeat_n(0u8, pad));
563
564        buf
565    }
566
567    /// Serialize to bytes without padding.
568    #[must_use]
569    pub fn to_bytes_unpadded(&self) -> Vec<u8> {
570        let mut buf = Vec::new();
571        for opt in &self.options {
572            buf.extend_from_slice(&opt.to_bytes());
573        }
574        buf
575    }
576}
577
578impl IntoIterator for TcpOptions {
579    type Item = TcpOption;
580    type IntoIter = std::vec::IntoIter<TcpOption>;
581
582    fn into_iter(self) -> Self::IntoIter {
583        self.options.into_iter()
584    }
585}
586
587impl<'a> IntoIterator for &'a TcpOptions {
588    type Item = &'a TcpOption;
589    type IntoIter = std::slice::Iter<'a, TcpOption>;
590
591    fn into_iter(self) -> Self::IntoIter {
592        self.options.iter()
593    }
594}
595
596/// Parse TCP options from bytes.
597pub fn parse_options(data: &[u8]) -> Result<TcpOptions, FieldError> {
598    let mut options = Vec::new();
599    let mut offset = 0;
600
601    while offset < data.len() {
602        let kind = data[offset];
603
604        match kind {
605            // End of Option List
606            0 => {
607                options.push(TcpOption::Eol);
608                break;
609            },
610
611            // NOP
612            1 => {
613                options.push(TcpOption::Nop);
614                offset += 1;
615            },
616
617            // Multi-byte options
618            _ => {
619                if offset + 1 >= data.len() {
620                    return Err(FieldError::InvalidValue(
621                        "option length field missing".to_string(),
622                    ));
623                }
624
625                let length = data[offset + 1] as usize;
626                if length < 2 {
627                    return Err(FieldError::InvalidValue(format!(
628                        "option length {length} is less than minimum (2)"
629                    )));
630                }
631
632                if offset + length > data.len() {
633                    return Err(FieldError::BufferTooShort {
634                        offset,
635                        need: length,
636                        have: data.len() - offset,
637                    });
638                }
639
640                let opt_data = &data[offset..offset + length];
641                let opt = parse_single_option(kind, opt_data)?;
642                options.push(opt);
643
644                offset += length;
645            },
646        }
647    }
648
649    Ok(TcpOptions { options })
650}
651
652/// Parse a single multi-byte option.
653fn parse_single_option(kind: u8, data: &[u8]) -> Result<TcpOption, FieldError> {
654    let length = data[1] as usize;
655    let value = &data[2..length];
656
657    match kind {
658        // MSS
659        2 => {
660            if length != 4 {
661                return Err(FieldError::InvalidValue(format!(
662                    "MSS option length {length} != 4"
663                )));
664            }
665            let mss = u16::from_be_bytes([value[0], value[1]]);
666            Ok(TcpOption::Mss(mss))
667        },
668
669        // Window Scale
670        3 => {
671            if length != 3 {
672                return Err(FieldError::InvalidValue(format!(
673                    "WScale option length {length} != 3"
674                )));
675            }
676            Ok(TcpOption::WScale(value[0]))
677        },
678
679        // SACK Permitted
680        4 => {
681            if length != 2 {
682                return Err(FieldError::InvalidValue(format!(
683                    "SAckOK option length {length} != 2"
684                )));
685            }
686            Ok(TcpOption::SackOk)
687        },
688
689        // SACK
690        5 => {
691            let block_count = value.len() / 8;
692            let mut blocks = Vec::with_capacity(block_count);
693
694            for chunk in value.chunks_exact(8) {
695                let left = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
696                let right = u32::from_be_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
697                blocks.push(TcpSackBlock::new(left, right));
698            }
699
700            Ok(TcpOption::Sack(blocks))
701        },
702
703        // Timestamps
704        8 => {
705            if length != 10 {
706                return Err(FieldError::InvalidValue(format!(
707                    "Timestamp option length {length} != 10"
708                )));
709            }
710            let ts_val = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
711            let ts_ecr = u32::from_be_bytes([value[4], value[5], value[6], value[7]]);
712            Ok(TcpOption::Timestamp(TcpTimestamp::new(ts_val, ts_ecr)))
713        },
714
715        // Alternate Checksum Request
716        14 => {
717            if length < 3 {
718                return Err(FieldError::InvalidValue(format!(
719                    "AltChkSum option length {length} < 3"
720                )));
721            }
722            let algorithm = value[0];
723            let checksum = if value.len() >= 3 {
724                u16::from_be_bytes([value[1], value[2]])
725            } else {
726                0
727            };
728            Ok(TcpOption::AltChkSum {
729                algorithm,
730                checksum,
731            })
732        },
733
734        // Alternate Checksum Data
735        15 => Ok(TcpOption::AltChkSumOpt),
736
737        // MD5 Signature
738        19 => {
739            if length != 18 {
740                return Err(FieldError::InvalidValue(format!(
741                    "MD5 option length {length} != 18"
742                )));
743            }
744            let mut sig = [0u8; 16];
745            sig.copy_from_slice(value);
746            Ok(TcpOption::Md5(sig))
747        },
748
749        // Mood
750        25 => {
751            let mood = String::from_utf8_lossy(value).to_string();
752            Ok(TcpOption::Mood(mood))
753        },
754
755        // User Timeout Option
756        28 => {
757            if length != 4 {
758                return Err(FieldError::InvalidValue(format!(
759                    "UTO option length {length} != 4"
760                )));
761            }
762            let timeout = u16::from_be_bytes([value[0], value[1]]);
763            Ok(TcpOption::Uto(timeout))
764        },
765
766        // Authentication Option
767        29 => {
768            if length < 4 {
769                return Err(FieldError::InvalidValue(format!(
770                    "AO option length {length} < 4"
771                )));
772            }
773            let key_id = value[0];
774            let rnext_key_id = value[1];
775            let mac = value[2..].to_vec();
776            Ok(TcpOption::Ao(TcpAoValue::new(key_id, rnext_key_id, mac)))
777        },
778
779        // TCP Fast Open
780        34 => {
781            let cookie = if value.is_empty() {
782                None
783            } else {
784                Some(value.to_vec())
785            };
786            Ok(TcpOption::Tfo { cookie })
787        },
788
789        // Unknown option
790        _ => Ok(TcpOption::Unknown {
791            kind,
792            data: value.to_vec(),
793        }),
794    }
795}
796
797/// Builder for TCP options.
798#[derive(Debug, Clone, Default)]
799pub struct TcpOptionsBuilder {
800    options: Vec<TcpOption>,
801}
802
803impl TcpOptionsBuilder {
804    /// Create a new builder.
805    #[must_use]
806    pub fn new() -> Self {
807        Self::default()
808    }
809
810    /// Add a NOP (padding).
811    #[must_use]
812    pub fn nop(mut self) -> Self {
813        self.options.push(TcpOption::Nop);
814        self
815    }
816
817    /// Add an End of Option List marker.
818    #[must_use]
819    pub fn eol(mut self) -> Self {
820        self.options.push(TcpOption::Eol);
821        self
822    }
823
824    /// Add an MSS option.
825    #[must_use]
826    pub fn mss(mut self, mss: u16) -> Self {
827        self.options.push(TcpOption::Mss(mss));
828        self
829    }
830
831    /// Add a Window Scale option.
832    #[must_use]
833    pub fn wscale(mut self, scale: u8) -> Self {
834        self.options.push(TcpOption::WScale(scale));
835        self
836    }
837
838    /// Add SACK Permitted option.
839    #[must_use]
840    pub fn sack_ok(mut self) -> Self {
841        self.options.push(TcpOption::SackOk);
842        self
843    }
844
845    /// Add a SACK option.
846    #[must_use]
847    pub fn sack(mut self, blocks: Vec<TcpSackBlock>) -> Self {
848        self.options.push(TcpOption::Sack(blocks));
849        self
850    }
851
852    /// Add a Timestamp option.
853    #[must_use]
854    pub fn timestamp(mut self, ts_val: u32, ts_ecr: u32) -> Self {
855        self.options
856            .push(TcpOption::Timestamp(TcpTimestamp::new(ts_val, ts_ecr)));
857        self
858    }
859
860    /// Add a TFO (TCP Fast Open) option with cookie.
861    #[must_use]
862    pub fn tfo(mut self, cookie: Option<Vec<u8>>) -> Self {
863        self.options.push(TcpOption::Tfo { cookie });
864        self
865    }
866
867    /// Add an Authentication Option.
868    #[must_use]
869    pub fn ao(mut self, key_id: u8, rnext_key_id: u8, mac: Vec<u8>) -> Self {
870        self.options
871            .push(TcpOption::Ao(TcpAoValue::new(key_id, rnext_key_id, mac)));
872        self
873    }
874
875    /// Add an MD5 signature option.
876    #[must_use]
877    pub fn md5(mut self, signature: [u8; 16]) -> Self {
878        self.options.push(TcpOption::Md5(signature));
879        self
880    }
881
882    /// Add a custom option.
883    #[must_use]
884    pub fn option(mut self, option: TcpOption) -> Self {
885        self.options.push(option);
886        self
887    }
888
889    /// Build the options.
890    #[must_use]
891    pub fn build(self) -> TcpOptions {
892        TcpOptions {
893            options: self.options,
894        }
895    }
896}
897
898/// Get the TCP-AO (Authentication Option) from a parsed options list.
899#[must_use]
900pub fn get_tcp_ao(options: &TcpOptions) -> Option<&TcpAoValue> {
901    options.ao()
902}
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907
908    #[test]
909    fn test_parse_nop_eol() {
910        let data = [1, 1, 1, 0]; // 3 NOPs + EOL
911        let opts = parse_options(&data).unwrap();
912
913        assert_eq!(opts.len(), 4);
914        assert!(matches!(opts.options[0], TcpOption::Nop));
915        assert!(matches!(opts.options[1], TcpOption::Nop));
916        assert!(matches!(opts.options[2], TcpOption::Nop));
917        assert!(matches!(opts.options[3], TcpOption::Eol));
918    }
919
920    #[test]
921    fn test_parse_mss() {
922        let data = [
923            2, 4, // MSS option, length 4
924            0x05, 0xB4, // MSS = 1460
925        ];
926
927        let opts = parse_options(&data).unwrap();
928        assert_eq!(opts.len(), 1);
929        assert_eq!(opts.mss(), Some(1460));
930    }
931
932    #[test]
933    fn test_parse_wscale() {
934        let data = [
935            3, 3, // WScale option, length 3
936            7, // scale = 7
937        ];
938
939        let opts = parse_options(&data).unwrap();
940        assert_eq!(opts.len(), 1);
941        assert_eq!(opts.wscale(), Some(7));
942    }
943
944    #[test]
945    fn test_parse_timestamp() {
946        let data = [
947            8, 10, // Timestamp option, length 10
948            0x00, 0x00, 0x10, 0x00, // ts_val
949            0x00, 0x00, 0x20, 0x00, // ts_ecr
950        ];
951
952        let opts = parse_options(&data).unwrap();
953        assert_eq!(opts.len(), 1);
954
955        let ts = opts.timestamp().unwrap();
956        assert_eq!(ts.ts_val, 0x1000);
957        assert_eq!(ts.ts_ecr, 0x2000);
958    }
959
960    #[test]
961    fn test_parse_sack() {
962        let data = [
963            5, 18, // SACK option, length 18 (2 blocks)
964            0x00, 0x00, 0x10, 0x00, // left1
965            0x00, 0x00, 0x20, 0x00, // right1
966            0x00, 0x00, 0x30, 0x00, // left2
967            0x00, 0x00, 0x40, 0x00, // right2
968        ];
969
970        let opts = parse_options(&data).unwrap();
971        assert_eq!(opts.len(), 1);
972
973        let blocks = opts.sack_blocks().unwrap();
974        assert_eq!(blocks.len(), 2);
975        assert_eq!(blocks[0].left, 0x1000);
976        assert_eq!(blocks[0].right, 0x2000);
977        assert_eq!(blocks[1].left, 0x3000);
978        assert_eq!(blocks[1].right, 0x4000);
979    }
980
981    #[test]
982    fn test_parse_sack_ok() {
983        let data = [4, 2]; // SACK Permitted
984
985        let opts = parse_options(&data).unwrap();
986        assert_eq!(opts.len(), 1);
987        assert!(opts.sack_permitted());
988    }
989
990    #[test]
991    fn test_parse_ao() {
992        let data = [
993            29, 6, // AO option, length 6
994            1, // key_id
995            2, // rnext_key_id
996            0xAB, 0xCD, // MAC (2 bytes)
997        ];
998
999        let opts = parse_options(&data).unwrap();
1000        assert_eq!(opts.len(), 1);
1001
1002        let ao = opts.ao().unwrap();
1003        assert_eq!(ao.key_id, 1);
1004        assert_eq!(ao.rnext_key_id, 2);
1005        assert_eq!(ao.mac, vec![0xAB, 0xCD]);
1006    }
1007
1008    #[test]
1009    fn test_serialize_options() {
1010        let opts = TcpOptionsBuilder::new()
1011            .mss(1460)
1012            .wscale(7)
1013            .sack_ok()
1014            .timestamp(1000, 2000)
1015            .build();
1016
1017        let bytes = opts.to_bytes();
1018
1019        // Should be padded to 4-byte boundary
1020        assert_eq!(bytes.len() % 4, 0);
1021
1022        // Parse back
1023        let parsed = parse_options(&bytes).unwrap();
1024        assert_eq!(parsed.mss(), Some(1460));
1025        assert_eq!(parsed.wscale(), Some(7));
1026        assert!(parsed.sack_permitted());
1027
1028        let ts = parsed.timestamp().unwrap();
1029        assert_eq!(ts.ts_val, 1000);
1030        assert_eq!(ts.ts_ecr, 2000);
1031    }
1032
1033    #[test]
1034    fn test_option_kind_properties() {
1035        assert!(TcpOptionKind::Eol.is_single_byte());
1036        assert!(TcpOptionKind::Nop.is_single_byte());
1037        assert!(!TcpOptionKind::Mss.is_single_byte());
1038
1039        assert_eq!(TcpOptionKind::Mss.expected_len(), Some(4));
1040        assert_eq!(TcpOptionKind::Timestamp.expected_len(), Some(10));
1041        assert_eq!(TcpOptionKind::Sack.expected_len(), None);
1042    }
1043
1044    #[test]
1045    fn test_typical_syn_options() {
1046        // Typical SYN packet options: MSS, SACK Permitted, Timestamp, NOP, Window Scale
1047        let opts = TcpOptionsBuilder::new()
1048            .mss(1460)
1049            .sack_ok()
1050            .timestamp(12345, 0)
1051            .nop()
1052            .wscale(7)
1053            .build();
1054
1055        let bytes = opts.to_bytes();
1056
1057        // Parse back and verify
1058        let parsed = parse_options(&bytes).unwrap();
1059        assert_eq!(parsed.mss(), Some(1460));
1060        assert!(parsed.sack_permitted());
1061        assert_eq!(parsed.wscale(), Some(7));
1062
1063        let ts = parsed.timestamp().unwrap();
1064        assert_eq!(ts.ts_val, 12345);
1065        assert_eq!(ts.ts_ecr, 0);
1066    }
1067
1068    #[test]
1069    fn test_tfo_option() {
1070        let cookie = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
1071        let opts = TcpOptionsBuilder::new().tfo(Some(cookie.clone())).build();
1072
1073        let bytes = opts.to_bytes();
1074        let parsed = parse_options(&bytes).unwrap();
1075
1076        assert_eq!(parsed.tfo_cookie(), Some(cookie.as_slice()));
1077    }
1078
1079    #[test]
1080    fn test_md5_option() {
1081        let sig = [1u8; 16];
1082        let opts = TcpOptionsBuilder::new().md5(sig).build();
1083
1084        let bytes = opts.to_bytes();
1085        let parsed = parse_options(&bytes).unwrap();
1086
1087        if let Some(TcpOption::Md5(parsed_sig)) = parsed.get(TcpOptionKind::Md5) {
1088            assert_eq!(*parsed_sig, sig);
1089        } else {
1090            panic!("Expected MD5 option");
1091        }
1092    }
1093}