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