Skip to main content

stackforge_core/layer/ipv4/
options.rs

1//! IPv4 options parsing and building.
2//!
3//! IP options follow the header and are variable-length.
4//! Maximum options length is 40 bytes (60 byte header - 20 byte minimum).
5
6use std::net::Ipv4Addr;
7
8use crate::layer::field::FieldError;
9
10/// Maximum length of IP options (in bytes).
11pub const MAX_OPTIONS_LEN: usize = 40;
12
13/// IP option class (bits 6-7 of option type).
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[repr(u8)]
16pub enum Ipv4OptionClass {
17    /// Control options
18    Control = 0,
19    /// Reserved (1)
20    Reserved1 = 1,
21    /// Debugging and measurement
22    DebuggingMeasurement = 2,
23    /// Reserved (3)
24    Reserved3 = 3,
25}
26
27impl Ipv4OptionClass {
28    #[inline]
29    pub fn from_type(opt_type: u8) -> Self {
30        match (opt_type >> 5) & 0x03 {
31            0 => Self::Control,
32            1 => Self::Reserved1,
33            2 => Self::DebuggingMeasurement,
34            3 => Self::Reserved3,
35            _ => unreachable!(),
36        }
37    }
38}
39
40/// Well-known IP option types.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[repr(u8)]
43pub enum Ipv4OptionType {
44    /// End of Option List
45    EndOfList = 0,
46    /// No Operation (padding)
47    Nop = 1,
48    /// Security (RFC 1108)
49    Security = 130,
50    /// Loose Source and Record Route
51    Lsrr = 131,
52    /// Internet Timestamp
53    Timestamp = 68,
54    /// Extended Security (RFC 1108)
55    ExtendedSecurity = 133,
56    /// Commercial Security
57    CommercialSecurity = 134,
58    /// Record Route
59    RecordRoute = 7,
60    /// Stream ID
61    StreamId = 136,
62    /// Strict Source and Record Route
63    Ssrr = 137,
64    /// Experimental Measurement
65    ExperimentalMeasurement = 10,
66    /// MTU Probe
67    MtuProbe = 11,
68    /// MTU Reply
69    MtuReply = 12,
70    /// Traceroute
71    Traceroute = 82,
72    /// Address Extension
73    AddressExtension = 147,
74    /// Router Alert
75    RouterAlert = 148,
76    /// Selective Directed Broadcast
77    SelectiveDirectedBroadcast = 149,
78    /// Unknown option
79    Unknown(u8),
80}
81
82impl Ipv4OptionType {
83    /// Create from raw option type byte.
84    pub fn from_byte(b: u8) -> Self {
85        match b {
86            0 => Self::EndOfList,
87            1 => Self::Nop,
88            7 => Self::RecordRoute,
89            10 => Self::ExperimentalMeasurement,
90            11 => Self::MtuProbe,
91            12 => Self::MtuReply,
92            68 => Self::Timestamp,
93            82 => Self::Traceroute,
94            130 => Self::Security,
95            131 => Self::Lsrr,
96            133 => Self::ExtendedSecurity,
97            134 => Self::CommercialSecurity,
98            136 => Self::StreamId,
99            137 => Self::Ssrr,
100            147 => Self::AddressExtension,
101            148 => Self::RouterAlert,
102            149 => Self::SelectiveDirectedBroadcast,
103            x => Self::Unknown(x),
104        }
105    }
106
107    /// Convert to raw option type byte.
108    pub fn to_byte(self) -> u8 {
109        match self {
110            Self::EndOfList => 0,
111            Self::Nop => 1,
112            Self::RecordRoute => 7,
113            Self::ExperimentalMeasurement => 10,
114            Self::MtuProbe => 11,
115            Self::MtuReply => 12,
116            Self::Timestamp => 68,
117            Self::Traceroute => 82,
118            Self::Security => 130,
119            Self::Lsrr => 131,
120            Self::ExtendedSecurity => 133,
121            Self::CommercialSecurity => 134,
122            Self::StreamId => 136,
123            Self::Ssrr => 137,
124            Self::AddressExtension => 147,
125            Self::RouterAlert => 148,
126            Self::SelectiveDirectedBroadcast => 149,
127            Self::Unknown(x) => x,
128        }
129    }
130
131    /// Get the name of the option.
132    pub fn name(&self) -> &'static str {
133        match self {
134            Self::EndOfList => "EOL",
135            Self::Nop => "NOP",
136            Self::Security => "Security",
137            Self::Lsrr => "LSRR",
138            Self::Timestamp => "Timestamp",
139            Self::ExtendedSecurity => "Extended Security",
140            Self::CommercialSecurity => "Commercial Security",
141            Self::RecordRoute => "Record Route",
142            Self::StreamId => "Stream ID",
143            Self::Ssrr => "SSRR",
144            Self::ExperimentalMeasurement => "Experimental Measurement",
145            Self::MtuProbe => "MTU Probe",
146            Self::MtuReply => "MTU Reply",
147            Self::Traceroute => "Traceroute",
148            Self::AddressExtension => "Address Extension",
149            Self::RouterAlert => "Router Alert",
150            Self::SelectiveDirectedBroadcast => "Selective Directed Broadcast",
151            Self::Unknown(_) => "Unknown",
152        }
153    }
154
155    /// Check if this option should be copied on fragmentation.
156    #[inline]
157    pub fn is_copied(&self) -> bool {
158        (self.to_byte() & 0x80) != 0
159    }
160
161    /// Check if this is a single-byte option (no length/value).
162    #[inline]
163    pub fn is_single_byte(&self) -> bool {
164        matches!(self, Self::EndOfList | Self::Nop)
165    }
166}
167
168impl std::fmt::Display for Ipv4OptionType {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        match self {
171            Self::Unknown(x) => write!(f, "Unknown({})", x),
172            _ => write!(f, "{}", self.name()),
173        }
174    }
175}
176
177/// A parsed IP option.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub enum Ipv4Option {
180    /// End of Option List (type 0)
181    EndOfList,
182
183    /// No Operation / Padding (type 1)
184    Nop,
185
186    /// Record Route (type 7)
187    RecordRoute { pointer: u8, route: Vec<Ipv4Addr> },
188
189    /// Loose Source and Record Route (type 131)
190    Lsrr { pointer: u8, route: Vec<Ipv4Addr> },
191
192    /// Strict Source and Record Route (type 137)
193    Ssrr { pointer: u8, route: Vec<Ipv4Addr> },
194
195    /// Internet Timestamp (type 68)
196    Timestamp {
197        pointer: u8,
198        overflow: u8,
199        flag: u8,
200        data: Vec<(Option<Ipv4Addr>, u32)>,
201    },
202
203    /// Security (type 130)
204    Security {
205        security: u16,
206        compartment: u16,
207        handling_restrictions: u16,
208        transmission_control_code: [u8; 3],
209    },
210
211    /// Stream ID (type 136)
212    StreamId { id: u16 },
213
214    /// MTU Probe (type 11)
215    MtuProbe { mtu: u16 },
216
217    /// MTU Reply (type 12)
218    MtuReply { mtu: u16 },
219
220    /// Traceroute (type 82)
221    Traceroute {
222        id: u16,
223        outbound_hops: u16,
224        return_hops: u16,
225        originator: Ipv4Addr,
226    },
227
228    /// Router Alert (type 148)
229    RouterAlert { value: u16 },
230
231    /// Address Extension (type 147)
232    AddressExtension {
233        src_ext: Ipv4Addr,
234        dst_ext: Ipv4Addr,
235    },
236
237    /// Unknown or unimplemented option
238    Unknown { option_type: u8, data: Vec<u8> },
239}
240
241impl Ipv4Option {
242    /// Get the option type.
243    pub fn option_type(&self) -> Ipv4OptionType {
244        match self {
245            Self::EndOfList => Ipv4OptionType::EndOfList,
246            Self::Nop => Ipv4OptionType::Nop,
247            Self::RecordRoute { .. } => Ipv4OptionType::RecordRoute,
248            Self::Lsrr { .. } => Ipv4OptionType::Lsrr,
249            Self::Ssrr { .. } => Ipv4OptionType::Ssrr,
250            Self::Timestamp { .. } => Ipv4OptionType::Timestamp,
251            Self::Security { .. } => Ipv4OptionType::Security,
252            Self::StreamId { .. } => Ipv4OptionType::StreamId,
253            Self::MtuProbe { .. } => Ipv4OptionType::MtuProbe,
254            Self::MtuReply { .. } => Ipv4OptionType::MtuReply,
255            Self::Traceroute { .. } => Ipv4OptionType::Traceroute,
256            Self::RouterAlert { .. } => Ipv4OptionType::RouterAlert,
257            Self::AddressExtension { .. } => Ipv4OptionType::AddressExtension,
258            Self::Unknown { option_type, .. } => Ipv4OptionType::Unknown(*option_type),
259        }
260    }
261
262    /// Get the serialized length of this option.
263    pub fn len(&self) -> usize {
264        match self {
265            Self::EndOfList => 1,
266            Self::Nop => 1,
267            Self::RecordRoute { route, .. }
268            | Self::Lsrr { route, .. }
269            | Self::Ssrr { route, .. } => 3 + route.len() * 4,
270            Self::Timestamp { data, flag, .. } => 4 + data.len() * if *flag == 0 { 4 } else { 8 },
271            Self::Security { .. } => 11,
272            Self::StreamId { .. } => 4,
273            Self::MtuProbe { .. } | Self::MtuReply { .. } => 4,
274            Self::Traceroute { .. } => 12,
275            Self::RouterAlert { .. } => 4,
276            Self::AddressExtension { .. } => 10,
277            Self::Unknown { data, .. } => 2 + data.len(),
278        }
279    }
280
281    /// Check if the option is empty (for variants with data).
282    pub fn is_empty(&self) -> bool {
283        self.len() == 0
284    }
285
286    /// Serialize the option to bytes.
287    pub fn to_bytes(&self) -> Vec<u8> {
288        match self {
289            Self::EndOfList => vec![0],
290            Self::Nop => vec![1],
291
292            Self::RecordRoute { pointer, route } => {
293                let mut buf = vec![7, (3 + route.len() * 4) as u8, *pointer];
294                for ip in route {
295                    buf.extend_from_slice(&ip.octets());
296                }
297                buf
298            }
299
300            Self::Lsrr { pointer, route } => {
301                let mut buf = vec![131, (3 + route.len() * 4) as u8, *pointer];
302                for ip in route {
303                    buf.extend_from_slice(&ip.octets());
304                }
305                buf
306            }
307
308            Self::Ssrr { pointer, route } => {
309                let mut buf = vec![137, (3 + route.len() * 4) as u8, *pointer];
310                for ip in route {
311                    buf.extend_from_slice(&ip.octets());
312                }
313                buf
314            }
315
316            Self::Timestamp {
317                pointer,
318                overflow,
319                flag,
320                data,
321            } => {
322                let entry_size = if *flag == 0 { 4 } else { 8 };
323                let mut buf = vec![
324                    68,
325                    (4 + data.len() * entry_size) as u8,
326                    *pointer,
327                    (*overflow << 4) | (*flag & 0x0F),
328                ];
329                for (ip, ts) in data {
330                    if let Some(addr) = ip {
331                        buf.extend_from_slice(&addr.octets());
332                    }
333                    buf.extend_from_slice(&ts.to_be_bytes());
334                }
335                buf
336            }
337
338            Self::Security {
339                security,
340                compartment,
341                handling_restrictions,
342                transmission_control_code,
343            } => {
344                let mut buf = vec![130, 11];
345                buf.extend_from_slice(&security.to_be_bytes());
346                buf.extend_from_slice(&compartment.to_be_bytes());
347                buf.extend_from_slice(&handling_restrictions.to_be_bytes());
348                buf.extend_from_slice(transmission_control_code);
349                buf
350            }
351
352            Self::StreamId { id } => {
353                let mut buf = vec![136, 4];
354                buf.extend_from_slice(&id.to_be_bytes());
355                buf
356            }
357
358            Self::MtuProbe { mtu } => {
359                let mut buf = vec![11, 4];
360                buf.extend_from_slice(&mtu.to_be_bytes());
361                buf
362            }
363
364            Self::MtuReply { mtu } => {
365                let mut buf = vec![12, 4];
366                buf.extend_from_slice(&mtu.to_be_bytes());
367                buf
368            }
369
370            Self::Traceroute {
371                id,
372                outbound_hops,
373                return_hops,
374                originator,
375            } => {
376                let mut buf = vec![82, 12];
377                buf.extend_from_slice(&id.to_be_bytes());
378                buf.extend_from_slice(&outbound_hops.to_be_bytes());
379                buf.extend_from_slice(&return_hops.to_be_bytes());
380                buf.extend_from_slice(&originator.octets());
381                buf
382            }
383
384            Self::RouterAlert { value } => {
385                let mut buf = vec![148, 4];
386                buf.extend_from_slice(&value.to_be_bytes());
387                buf
388            }
389
390            Self::AddressExtension { src_ext, dst_ext } => {
391                let mut buf = vec![147, 10];
392                buf.extend_from_slice(&src_ext.octets());
393                buf.extend_from_slice(&dst_ext.octets());
394                buf
395            }
396
397            Self::Unknown { option_type, data } => {
398                let mut buf = vec![*option_type, (2 + data.len()) as u8];
399                buf.extend_from_slice(data);
400                buf
401            }
402        }
403    }
404}
405
406/// Collection of IP options.
407#[derive(Debug, Clone, Default, PartialEq, Eq)]
408pub struct Ipv4Options {
409    pub options: Vec<Ipv4Option>,
410}
411
412impl Ipv4Options {
413    /// Create empty options.
414    pub fn new() -> Self {
415        Self::default()
416    }
417
418    /// Create from a list of options.
419    pub fn from_vec(options: Vec<Ipv4Option>) -> Self {
420        Self { options }
421    }
422
423    /// Check if there are no options.
424    pub fn is_empty(&self) -> bool {
425        self.options.is_empty()
426    }
427
428    /// Get the number of options.
429    pub fn len(&self) -> usize {
430        self.options.len()
431    }
432
433    /// Get the total serialized length.
434    pub fn byte_len(&self) -> usize {
435        self.options.iter().map(|o| o.len()).sum()
436    }
437
438    /// Get the padded length (aligned to 4 bytes).
439    pub fn padded_len(&self) -> usize {
440        let len = self.byte_len();
441        (len + 3) & !3
442    }
443
444    /// Add an option.
445    pub fn push(&mut self, option: Ipv4Option) {
446        self.options.push(option);
447    }
448
449    /// Get source route options (LSRR or SSRR).
450    pub fn source_route(&self) -> Option<&Ipv4Option> {
451        self.options
452            .iter()
453            .find(|o| matches!(o, Ipv4Option::Lsrr { .. } | Ipv4Option::Ssrr { .. }))
454    }
455
456    /// Get the final destination from source route options.
457    pub fn final_destination(&self) -> Option<Ipv4Addr> {
458        self.source_route().and_then(|opt| match opt {
459            Ipv4Option::Lsrr { route, .. } | Ipv4Option::Ssrr { route, .. } => {
460                route.last().copied()
461            }
462            _ => None,
463        })
464    }
465
466    /// Serialize all options to bytes (with padding).
467    pub fn to_bytes(&self) -> Vec<u8> {
468        let mut buf = Vec::new();
469        for opt in &self.options {
470            buf.extend_from_slice(&opt.to_bytes());
471        }
472
473        // Pad to 4-byte boundary
474        let pad = (4 - (buf.len() % 4)) % 4;
475        buf.extend(std::iter::repeat(0u8).take(pad));
476
477        buf
478    }
479
480    /// Filter options that should be copied on fragmentation.
481    pub fn copied_options(&self) -> Self {
482        Self {
483            options: self
484                .options
485                .iter()
486                .filter(|o| o.option_type().is_copied())
487                .cloned()
488                .collect(),
489        }
490    }
491}
492
493impl IntoIterator for Ipv4Options {
494    type Item = Ipv4Option;
495    type IntoIter = std::vec::IntoIter<Ipv4Option>;
496
497    fn into_iter(self) -> Self::IntoIter {
498        self.options.into_iter()
499    }
500}
501
502impl<'a> IntoIterator for &'a Ipv4Options {
503    type Item = &'a Ipv4Option;
504    type IntoIter = std::slice::Iter<'a, Ipv4Option>;
505
506    fn into_iter(self) -> Self::IntoIter {
507        self.options.iter()
508    }
509}
510
511/// Parse IP options from bytes.
512pub fn parse_options(data: &[u8]) -> Result<Ipv4Options, FieldError> {
513    let mut options = Vec::new();
514    let mut offset = 0;
515
516    while offset < data.len() {
517        let opt_type = data[offset];
518
519        match opt_type {
520            // End of Option List
521            0 => {
522                options.push(Ipv4Option::EndOfList);
523                break;
524            }
525
526            // NOP
527            1 => {
528                options.push(Ipv4Option::Nop);
529                offset += 1;
530            }
531
532            // Multi-byte options
533            _ => {
534                if offset + 1 >= data.len() {
535                    return Err(FieldError::InvalidValue(
536                        "option length field missing".to_string(),
537                    ));
538                }
539
540                let length = data[offset + 1] as usize;
541                if length < 2 {
542                    return Err(FieldError::InvalidValue(format!(
543                        "option length {} is less than minimum (2)",
544                        length
545                    )));
546                }
547
548                if offset + length > data.len() {
549                    return Err(FieldError::BufferTooShort {
550                        offset,
551                        need: length,
552                        have: data.len() - offset,
553                    });
554                }
555
556                let opt_data = &data[offset..offset + length];
557                let opt = parse_single_option(opt_type, opt_data)?;
558                options.push(opt);
559
560                offset += length;
561            }
562        }
563    }
564
565    Ok(Ipv4Options { options })
566}
567
568/// Parse a single multi-byte option.
569fn parse_single_option(opt_type: u8, data: &[u8]) -> Result<Ipv4Option, FieldError> {
570    let length = data[1] as usize;
571
572    match opt_type {
573        // Record Route
574        7 => {
575            if length < 3 {
576                return Err(FieldError::InvalidValue(
577                    "Record Route option too short".to_string(),
578                ));
579            }
580            let pointer = data[2];
581            let route = parse_ip_list(&data[3..length]);
582            Ok(Ipv4Option::RecordRoute { pointer, route })
583        }
584
585        // LSRR
586        131 => {
587            if length < 3 {
588                return Err(FieldError::InvalidValue(
589                    "LSRR option too short".to_string(),
590                ));
591            }
592            let pointer = data[2];
593            let route = parse_ip_list(&data[3..length]);
594            Ok(Ipv4Option::Lsrr { pointer, route })
595        }
596
597        // SSRR
598        137 => {
599            if length < 3 {
600                return Err(FieldError::InvalidValue(
601                    "SSRR option too short".to_string(),
602                ));
603            }
604            let pointer = data[2];
605            let route = parse_ip_list(&data[3..length]);
606            Ok(Ipv4Option::Ssrr { pointer, route })
607        }
608
609        // Timestamp
610        68 => {
611            if length < 4 {
612                return Err(FieldError::InvalidValue(
613                    "Timestamp option too short".to_string(),
614                ));
615            }
616            let pointer = data[2];
617            let oflw_flag = data[3];
618            let overflow = oflw_flag >> 4;
619            let flag = oflw_flag & 0x0F;
620
621            let timestamps = parse_timestamps(&data[4..length], flag)?;
622            Ok(Ipv4Option::Timestamp {
623                pointer,
624                overflow,
625                flag,
626                data: timestamps,
627            })
628        }
629
630        // Security
631        130 => {
632            if length != 11 {
633                return Err(FieldError::InvalidValue(format!(
634                    "Security option length {} != 11",
635                    length
636                )));
637            }
638            let security = u16::from_be_bytes([data[2], data[3]]);
639            let compartment = u16::from_be_bytes([data[4], data[5]]);
640            let handling_restrictions = u16::from_be_bytes([data[6], data[7]]);
641            let mut tcc = [0u8; 3];
642            tcc.copy_from_slice(&data[8..11]);
643
644            Ok(Ipv4Option::Security {
645                security,
646                compartment,
647                handling_restrictions,
648                transmission_control_code: tcc,
649            })
650        }
651
652        // Stream ID
653        136 => {
654            if length != 4 {
655                return Err(FieldError::InvalidValue(format!(
656                    "Stream ID option length {} != 4",
657                    length
658                )));
659            }
660            let id = u16::from_be_bytes([data[2], data[3]]);
661            Ok(Ipv4Option::StreamId { id })
662        }
663
664        // MTU Probe
665        11 => {
666            if length != 4 {
667                return Err(FieldError::InvalidValue(format!(
668                    "MTU Probe option length {} != 4",
669                    length
670                )));
671            }
672            let mtu = u16::from_be_bytes([data[2], data[3]]);
673            Ok(Ipv4Option::MtuProbe { mtu })
674        }
675
676        // MTU Reply
677        12 => {
678            if length != 4 {
679                return Err(FieldError::InvalidValue(format!(
680                    "MTU Reply option length {} != 4",
681                    length
682                )));
683            }
684            let mtu = u16::from_be_bytes([data[2], data[3]]);
685            Ok(Ipv4Option::MtuReply { mtu })
686        }
687
688        // Traceroute
689        82 => {
690            if length != 12 {
691                return Err(FieldError::InvalidValue(format!(
692                    "Traceroute option length {} != 12",
693                    length
694                )));
695            }
696            let id = u16::from_be_bytes([data[2], data[3]]);
697            let outbound_hops = u16::from_be_bytes([data[4], data[5]]);
698            let return_hops = u16::from_be_bytes([data[6], data[7]]);
699            let originator = Ipv4Addr::new(data[8], data[9], data[10], data[11]);
700
701            Ok(Ipv4Option::Traceroute {
702                id,
703                outbound_hops,
704                return_hops,
705                originator,
706            })
707        }
708
709        // Router Alert
710        148 => {
711            if length != 4 {
712                return Err(FieldError::InvalidValue(format!(
713                    "Router Alert option length {} != 4",
714                    length
715                )));
716            }
717            let value = u16::from_be_bytes([data[2], data[3]]);
718            Ok(Ipv4Option::RouterAlert { value })
719        }
720
721        // Address Extension
722        147 => {
723            if length != 10 {
724                return Err(FieldError::InvalidValue(format!(
725                    "Address Extension option length {} != 10",
726                    length
727                )));
728            }
729            let src_ext = Ipv4Addr::new(data[2], data[3], data[4], data[5]);
730            let dst_ext = Ipv4Addr::new(data[6], data[7], data[8], data[9]);
731            Ok(Ipv4Option::AddressExtension { src_ext, dst_ext })
732        }
733
734        // Unknown option
735        _ => Ok(Ipv4Option::Unknown {
736            option_type: opt_type,
737            data: data[2..length].to_vec(),
738        }),
739    }
740}
741
742/// Parse a list of IP addresses from option data.
743fn parse_ip_list(data: &[u8]) -> Vec<Ipv4Addr> {
744    data.chunks_exact(4)
745        .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
746        .collect()
747}
748
749/// Parse timestamp data based on flag.
750fn parse_timestamps(data: &[u8], flag: u8) -> Result<Vec<(Option<Ipv4Addr>, u32)>, FieldError> {
751    match flag {
752        // Timestamps only
753        0 => {
754            let timestamps: Vec<_> = data
755                .chunks_exact(4)
756                .map(|c| {
757                    let ts = u32::from_be_bytes([c[0], c[1], c[2], c[3]]);
758                    (None, ts)
759                })
760                .collect();
761            Ok(timestamps)
762        }
763
764        // IP + Timestamp pairs
765        1 | 3 => {
766            if data.len() % 8 != 0 {
767                return Err(FieldError::InvalidValue(
768                    "Timestamp data not aligned to 8 bytes".to_string(),
769                ));
770            }
771            let timestamps: Vec<_> = data
772                .chunks_exact(8)
773                .map(|c| {
774                    let ip = Ipv4Addr::new(c[0], c[1], c[2], c[3]);
775                    let ts = u32::from_be_bytes([c[4], c[5], c[6], c[7]]);
776                    (Some(ip), ts)
777                })
778                .collect();
779            Ok(timestamps)
780        }
781
782        _ => Err(FieldError::InvalidValue(format!(
783            "Unknown timestamp flag: {}",
784            flag
785        ))),
786    }
787}
788
789/// Builder for IP options.
790#[derive(Debug, Clone, Default)]
791pub struct Ipv4OptionsBuilder {
792    options: Vec<Ipv4Option>,
793}
794
795impl Ipv4OptionsBuilder {
796    /// Create a new builder.
797    pub fn new() -> Self {
798        Self::default()
799    }
800
801    /// Add an End of Option List marker.
802    pub fn eol(mut self) -> Self {
803        self.options.push(Ipv4Option::EndOfList);
804        self
805    }
806
807    /// Add a NOP (padding).
808    pub fn nop(mut self) -> Self {
809        self.options.push(Ipv4Option::Nop);
810        self
811    }
812
813    /// Add a Record Route option.
814    pub fn record_route(mut self, route: Vec<Ipv4Addr>) -> Self {
815        self.options.push(Ipv4Option::RecordRoute {
816            pointer: 4, // First slot
817            route,
818        });
819        self
820    }
821
822    /// Add a Loose Source Route option.
823    pub fn lsrr(mut self, route: Vec<Ipv4Addr>) -> Self {
824        self.options.push(Ipv4Option::Lsrr { pointer: 4, route });
825        self
826    }
827
828    /// Add a Strict Source Route option.
829    pub fn ssrr(mut self, route: Vec<Ipv4Addr>) -> Self {
830        self.options.push(Ipv4Option::Ssrr { pointer: 4, route });
831        self
832    }
833
834    /// Add a Timestamp option (timestamps only).
835    pub fn timestamp(mut self) -> Self {
836        self.options.push(Ipv4Option::Timestamp {
837            pointer: 5,
838            overflow: 0,
839            flag: 0,
840            data: vec![],
841        });
842        self
843    }
844
845    /// Add a Timestamp option with IP addresses.
846    pub fn timestamp_with_addresses(mut self, prespecified: bool) -> Self {
847        self.options.push(Ipv4Option::Timestamp {
848            pointer: 9,
849            overflow: 0,
850            flag: if prespecified { 3 } else { 1 },
851            data: vec![],
852        });
853        self
854    }
855
856    /// Add a Router Alert option.
857    pub fn router_alert(mut self, value: u16) -> Self {
858        self.options.push(Ipv4Option::RouterAlert { value });
859        self
860    }
861
862    /// Add a custom option.
863    pub fn option(mut self, option: Ipv4Option) -> Self {
864        self.options.push(option);
865        self
866    }
867
868    /// Build the options.
869    pub fn build(self) -> Ipv4Options {
870        Ipv4Options {
871            options: self.options,
872        }
873    }
874}
875
876#[cfg(test)]
877mod tests {
878    use super::*;
879
880    #[test]
881    fn test_parse_nop_eol() {
882        let data = [1, 1, 1, 0]; // 3 NOPs + EOL
883        let opts = parse_options(&data).unwrap();
884
885        assert_eq!(opts.len(), 4);
886        assert!(matches!(opts.options[0], Ipv4Option::Nop));
887        assert!(matches!(opts.options[1], Ipv4Option::Nop));
888        assert!(matches!(opts.options[2], Ipv4Option::Nop));
889        assert!(matches!(opts.options[3], Ipv4Option::EndOfList));
890    }
891
892    #[test]
893    fn test_parse_record_route() {
894        let data = [
895            7,  // Type: Record Route
896            11, // Length: 11 bytes
897            4,  // Pointer: first slot
898            192, 168, 1, 1, // First IP
899            192, 168, 1, 2, // Second IP
900        ];
901
902        let opts = parse_options(&data).unwrap();
903        assert_eq!(opts.len(), 1);
904
905        if let Ipv4Option::RecordRoute { pointer, route } = &opts.options[0] {
906            assert_eq!(*pointer, 4);
907            assert_eq!(route.len(), 2);
908            assert_eq!(route[0], Ipv4Addr::new(192, 168, 1, 1));
909            assert_eq!(route[1], Ipv4Addr::new(192, 168, 1, 2));
910        } else {
911            panic!("Expected RecordRoute option");
912        }
913    }
914
915    #[test]
916    fn test_parse_timestamp() {
917        let data = [
918            68,   // Type: Timestamp
919            12,   // Length: 12 bytes
920            5,    // Pointer
921            0x01, // Overflow=0, Flag=1 (IP + timestamp)
922            192, 168, 1, 1, // IP
923            0x00, 0x00, 0x10, 0x00, // Timestamp
924        ];
925
926        let opts = parse_options(&data).unwrap();
927        assert_eq!(opts.len(), 1);
928
929        if let Ipv4Option::Timestamp {
930            pointer,
931            overflow,
932            flag,
933            data: ts_data,
934        } = &opts.options[0]
935        {
936            assert_eq!(*pointer, 5);
937            assert_eq!(*overflow, 0);
938            assert_eq!(*flag, 1);
939
940            assert_eq!(ts_data.len(), 1);
941            assert_eq!(ts_data[0].0, Some(Ipv4Addr::new(192, 168, 1, 1)));
942            assert_eq!(ts_data[0].1, 0x1000);
943        }
944    }
945
946    #[test]
947    fn test_parse_router_alert() {
948        let data = [
949            148,  // Type: Router Alert
950            4,    // Length
951            0x00, // Value high
952            0x00, // Value low
953        ];
954
955        let opts = parse_options(&data).unwrap();
956        assert_eq!(opts.len(), 1);
957
958        if let Ipv4Option::RouterAlert { value } = opts.options[0] {
959            assert_eq!(value, 0);
960        } else {
961            panic!("Expected RouterAlert option");
962        }
963    }
964
965    #[test]
966    fn test_serialize_options() {
967        let opts = Ipv4OptionsBuilder::new()
968            .nop()
969            .router_alert(0)
970            .nop()
971            .build();
972
973        let bytes = opts.to_bytes();
974
975        // Should be padded to 4-byte boundary
976        assert_eq!(bytes.len() % 4, 0);
977
978        // Parse back
979        let parsed = parse_options(&bytes).unwrap();
980        assert!(matches!(parsed.options[0], Ipv4Option::Nop));
981        assert!(matches!(
982            parsed.options[1],
983            Ipv4Option::RouterAlert { value: 0 }
984        ));
985    }
986
987    #[test]
988    fn test_option_type_properties() {
989        // LSRR should be copied
990        assert!(Ipv4OptionType::Lsrr.is_copied());
991        // Record Route should not be copied
992        assert!(!Ipv4OptionType::RecordRoute.is_copied());
993        // NOP is single byte
994        assert!(Ipv4OptionType::Nop.is_single_byte());
995        // Timestamp is not single byte
996        assert!(!Ipv4OptionType::Timestamp.is_single_byte());
997    }
998
999    #[test]
1000    fn test_final_destination() {
1001        let opts = Ipv4OptionsBuilder::new()
1002            .lsrr(vec![
1003                Ipv4Addr::new(10, 0, 0, 1),
1004                Ipv4Addr::new(10, 0, 0, 2),
1005                Ipv4Addr::new(10, 0, 0, 3),
1006            ])
1007            .build();
1008
1009        assert_eq!(opts.final_destination(), Some(Ipv4Addr::new(10, 0, 0, 3)));
1010    }
1011
1012    #[test]
1013    fn test_copied_options() {
1014        let opts = Ipv4OptionsBuilder::new()
1015            .record_route(vec![]) // Not copied
1016            .lsrr(vec![Ipv4Addr::new(10, 0, 0, 1)]) // Copied
1017            .nop() // Not copied
1018            .build();
1019
1020        let copied = opts.copied_options();
1021        assert_eq!(copied.len(), 1);
1022        assert!(matches!(copied.options[0], Ipv4Option::Lsrr { .. }));
1023    }
1024}