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