Skip to main content

packet_dissector_srv6/
lib.rs

1//! SRv6 Segment Routing Header (SRH) dissector.
2//!
3//! ## References
4//! - RFC 8754: <https://www.rfc-editor.org/rfc/rfc8754>
5//! - RFC 8986 (SRv6 Network Programming): <https://www.rfc-editor.org/rfc/rfc8986>
6//! - RFC 9259 (SRv6 OAM, O-flag): <https://www.rfc-editor.org/rfc/rfc9259>
7//! - RFC 9800 (updates RFC 8754, CSID/REPLACE-CSID): <https://www.rfc-editor.org/rfc/rfc9800>
8//! - RFC 9433 (SRv6 Mobile User Plane): <https://www.rfc-editor.org/rfc/rfc9433>
9
10#![deny(missing_docs)]
11
12use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
13use packet_dissector_core::error::PacketError;
14use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue, FormatContext};
15use packet_dissector_core::packet::DissectBuffer;
16use packet_dissector_core::util::{read_be_u16, read_be_u32, read_ipv6_addr};
17
18/// SRH fixed header size: Next Header (1) + Hdr Ext Len (1) + Routing Type (1)
19/// + Segments Left (1) + Last Entry (1) + Flags (1) + Tag (2) = 8 bytes.
20const SRH_FIXED_SIZE: usize = 8;
21
22/// Size of a single Segment List entry (IPv6 address).
23const SEGMENT_SIZE: usize = 16;
24
25/// Minimum HMAC TLV value length: D-flag+Reserved (2) + HMAC Key ID (4) = 6 bytes.
26/// A conforming HMAC TLV also includes HMAC data (multiples of 8, up to 32 bytes),
27/// but we accept shorter values gracefully per Postel's law.
28const HMAC_TLV_MIN_VALUE_LEN: usize = 6;
29
30/// CSID flavor selection (RFC 9800).
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum CsidFlavor {
33    /// No CSID — classic SRv6 mode.
34    Classic,
35    /// REPLACE-CSID flavor (RFC 9800, Section 4).
36    ///
37    /// Each 128-bit segment list entry is split into `K = 128 / LNFL` slots of
38    /// `LNFL` bits, where `LNFL = locator_node_bits + function_bits`.
39    ReplaceCsid {
40        /// CSID length in bits (equal to LNFL in REPLACE-CSID).
41        csid_bits: u8,
42    },
43    /// NEXT-CSID flavor (RFC 9800, Section 3) — also known as "uSID".
44    ///
45    /// Each 128-bit entry carries a Locator-Block prefix followed by
46    /// `K = (128 - LBL) / LNFL` micro-SIDs packed from the MSB of the
47    /// remaining bits.
48    NextCsid {
49        /// Micro-SID length in bits (equal to LNFL).
50        usid_bits: u8,
51    },
52}
53
54/// Mobile SID encoding configuration (RFC 9433).
55///
56/// Defines how mobile-specific information (IPv4 addresses, Args.Mob.Session,
57/// rate-limiting parameters) is packed into the 128-bit SID beyond the standard
58/// Locator-Block / Locator-Node / Function / Argument decomposition.
59///
60/// These encodings are operator-configurable and the bit widths vary by
61/// deployment.
62// RFC 9433, Sections 6.2–6.8 — https://www.rfc-editor.org/rfc/rfc9433#section-6
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum MobileSidEncoding {
65    /// End.M.GTP4.E (RFC 9433, Section 6.6, Figure 9):
66    /// `SRGW-IPv6-LOC-FUNC | IPv4DA(a) | Args.Mob.Session(b) | 0-padded(c)`
67    ///
68    /// where `a + b + c = 128 − len(LOC-FUNC)`.
69    EndMGtp4E {
70        /// Width of the embedded IPv4 Destination Address in bits.
71        ipv4da_bits: u8,
72        /// Width of the Args.Mob.Session field in bits.
73        args_mob_session_bits: u8,
74    },
75    /// End.M.GTP6.E (RFC 9433, Section 6.5):
76    /// Args.Mob.Session is carried in the Argument portion of the SID.
77    EndMGtp6E,
78    /// H.M.GTP4.D (RFC 9433, Section 6.7, Figure 11):
79    /// `Destination UPF Prefix | IPv4DA(a) | Args.Mob.Session(b) | 0-padded(c)`
80    HmGtp4D {
81        /// Width of the Destination UPF Prefix in bits.
82        prefix_bits: u8,
83        /// Width of the embedded IPv4 Destination Address in bits.
84        ipv4da_bits: u8,
85        /// Width of the Args.Mob.Session field in bits.
86        args_mob_session_bits: u8,
87    },
88    /// End.Limit (RFC 9433, Section 6.8, Figure 12):
89    /// `LOC+FUNC(rate-limit) | group-id(i) | limit-rate(j)`
90    EndLimit {
91        /// Width of the group identifier in bits.
92        group_id_bits: u8,
93        /// Width of the limit rate in bits.
94        limit_rate_bits: u8,
95    },
96}
97
98// IANA SRv6 Endpoint Behaviors (RFC 9433, Section 11)
99// https://www.rfc-editor.org/rfc/rfc9433#section-11
100
101/// End.MAP endpoint behavior value (0x0028).
102pub const BEHAVIOR_END_MAP: u16 = 40;
103/// End.Limit endpoint behavior value (0x0029).
104pub const BEHAVIOR_END_LIMIT: u16 = 41;
105/// End.M.GTP6.D endpoint behavior value (0x0045).
106pub const BEHAVIOR_END_M_GTP6_D: u16 = 69;
107/// End.M.GTP6.Di endpoint behavior value (0x0046).
108pub const BEHAVIOR_END_M_GTP6_DI: u16 = 70;
109/// End.M.GTP6.E endpoint behavior value (0x0047).
110pub const BEHAVIOR_END_M_GTP6_E: u16 = 71;
111/// End.M.GTP4.E endpoint behavior value (0x0048).
112pub const BEHAVIOR_END_M_GTP4_E: u16 = 72;
113
114/// Return a human-readable name for an IANA SRv6 endpoint behavior value
115/// defined in RFC 9433, or `None` if the value is not recognized.
116pub fn endpoint_behavior_name(value: u16) -> Option<&'static str> {
117    match value {
118        BEHAVIOR_END_MAP => Some("End.MAP"),
119        BEHAVIOR_END_LIMIT => Some("End.Limit"),
120        BEHAVIOR_END_M_GTP6_D => Some("End.M.GTP6.D"),
121        BEHAVIOR_END_M_GTP6_DI => Some("End.M.GTP6.Di"),
122        BEHAVIOR_END_M_GTP6_E => Some("End.M.GTP6.E"),
123        BEHAVIOR_END_M_GTP4_E => Some("End.M.GTP4.E"),
124        _ => None,
125    }
126}
127
128/// SRv6 SID structure configuration (RFC 8986, Section 3.1).
129///
130/// Defines the bit-level decomposition of a 128-bit SRv6 SID into its
131/// constituent parts: Locator-Block, Locator-Node, Function, and Argument.
132/// These boundaries are not visible on the wire and require out-of-band
133/// configuration (e.g., from the control plane).
134#[derive(Debug, Clone)]
135pub struct SidStructure {
136    /// Locator-Block length in bits.
137    pub locator_block_bits: u8,
138    /// Locator-Node length in bits.
139    pub locator_node_bits: u8,
140    /// Function length in bits.
141    pub function_bits: u8,
142    /// Argument length in bits (remaining bits up to 128).
143    pub argument_bits: u8,
144    /// CSID flavor (RFC 9800). Use [`CsidFlavor::Classic`] for classic mode.
145    pub csid_flavor: CsidFlavor,
146    /// Optional mobile SID encoding (RFC 9433). When set, each segment in the
147    /// Segment List is additionally decomposed into mobile-specific fields
148    /// (embedded IPv4 address, Args.Mob.Session, or rate-limiting parameters)
149    /// within the `segments_structure` output.
150    pub mobile_encoding: Option<MobileSidEncoding>,
151}
152
153impl SidStructure {
154    /// Creates a new [`SidStructure`] without mobile SID encoding.
155    ///
156    /// This constructor initializes the SRv6 SID decomposition as defined in
157    /// RFC 8986, Section 3.1, and defaults [`SidStructure::mobile_encoding`]
158    /// to [`None`]. Use this method instead of struct literals to avoid
159    /// source breakage when new optional fields are added in the future.
160    pub fn new(
161        locator_block_bits: u8,
162        locator_node_bits: u8,
163        function_bits: u8,
164        argument_bits: u8,
165        csid_flavor: CsidFlavor,
166    ) -> Self {
167        SidStructure {
168            locator_block_bits,
169            locator_node_bits,
170            function_bits,
171            argument_bits,
172            csid_flavor,
173            mobile_encoding: None,
174        }
175    }
176}
177
178/// SRv6 Segment Routing Header dissector.
179///
180/// Registered at Routing Type 4 via `ByIpv6RoutingType` dispatch. Receives the
181/// full Routing Header bytes (including the common fields) from the
182/// `RoutingDissector` dispatcher.
183///
184/// When created with [`Srv6Dissector::with_sid_structure`], each segment in the
185/// Segment List is additionally decomposed into Locator-Block, Locator-Node,
186/// Function, and Argument fields per RFC 8986.
187pub struct Srv6Dissector {
188    sid_structure: Option<SidStructure>,
189}
190
191impl Srv6Dissector {
192    /// Create a new dissector without SID structure analysis.
193    pub fn new() -> Self {
194        Self {
195            sid_structure: None,
196        }
197    }
198
199    /// Create a new dissector with SID structure analysis enabled.
200    pub fn with_sid_structure(s: SidStructure) -> Self {
201        Self {
202            sid_structure: Some(s),
203        }
204    }
205}
206
207impl Default for Srv6Dissector {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213/// Parse SRH TLVs (RFC 8754, Section 2.1).
214///
215/// Iterates over the TLV area and pushes each TLV as an Object container
216/// into `buf`. Known TLV types (Pad1, PadN, HMAC) are parsed semantically;
217/// unknown types are stored as raw bytes.
218fn parse_tlvs<'pkt>(
219    buf: &mut DissectBuffer<'pkt>,
220    data: &'pkt [u8],
221    offset: usize,
222    tlv_start: usize,
223    tlv_end: usize,
224) -> Result<(), PacketError> {
225    let mut cursor = tlv_start;
226
227    while cursor < tlv_end {
228        let type_byte = data[cursor];
229
230        // RFC 8754, Section 2.1.1 — Pad1 TLV (Type 0)
231        if type_byte == 0 {
232            let obj_idx = buf.begin_container(
233                &FD_TLV,
234                FieldValue::Object(0..0),
235                offset + cursor..offset + cursor + 1,
236            );
237            buf.push_field(
238                &TLV_DESCRIPTORS[FD_TLV_TYPE],
239                FieldValue::U8(0),
240                offset + cursor..offset + cursor + 1,
241            );
242            buf.end_container(obj_idx);
243            cursor += 1;
244            continue;
245        }
246
247        // All other TLVs: need at least Type + Length (2 bytes)
248        if cursor + 1 >= tlv_end {
249            return Err(PacketError::Truncated {
250                expected: cursor + 2,
251                actual: tlv_end,
252            });
253        }
254
255        let length = data[cursor + 1] as usize;
256        let value_start = cursor + 2;
257        let value_end = value_start + length;
258
259        if value_end > tlv_end {
260            return Err(PacketError::Truncated {
261                expected: value_end,
262                actual: tlv_end,
263            });
264        }
265
266        let obj_idx = buf.begin_container(
267            &FD_TLV,
268            FieldValue::Object(0..0),
269            offset + cursor..offset + value_end,
270        );
271        buf.push_field(
272            &TLV_DESCRIPTORS[FD_TLV_TYPE],
273            FieldValue::U8(type_byte),
274            offset + cursor..offset + cursor + 1,
275        );
276        buf.push_field(
277            &TLV_DESCRIPTORS[FD_TLV_LENGTH],
278            FieldValue::U8(data[cursor + 1]),
279            offset + cursor + 1..offset + cursor + 2,
280        );
281
282        match type_byte {
283            // RFC 8754, Section 2.1.1 — PadN TLV (Type 4)
284            4 => {
285                buf.push_field(
286                    &TLV_DESCRIPTORS[FD_TLV_PADDING],
287                    FieldValue::Bytes(&data[value_start..value_end]),
288                    offset + value_start..offset + value_end,
289                );
290            }
291            // RFC 8754, Section 2.1.2 — HMAC TLV (Type 5)
292            5 if length >= HMAC_TLV_MIN_VALUE_LEN => {
293                // D-flag (1 bit) + Reserved (15 bits) = 2 bytes
294                let d_flag = (data[value_start] >> 7) & 1;
295                // HMAC Key ID (4 bytes) at value_start+2
296                let key_id = read_be_u32(data, value_start + 2)?;
297                let hmac_start = value_start + 6;
298
299                buf.push_field(
300                    &TLV_DESCRIPTORS[FD_TLV_HMAC_D_FLAG],
301                    FieldValue::U8(d_flag),
302                    offset + value_start..offset + value_start + 2,
303                );
304                buf.push_field(
305                    &TLV_DESCRIPTORS[FD_TLV_HMAC_KEY_ID],
306                    FieldValue::U32(key_id),
307                    offset + value_start + 2..offset + value_start + 6,
308                );
309                if hmac_start < value_end {
310                    buf.push_field(
311                        &TLV_DESCRIPTORS[FD_TLV_HMAC],
312                        FieldValue::Bytes(&data[hmac_start..value_end]),
313                        offset + hmac_start..offset + value_end,
314                    );
315                }
316            }
317            // Unknown TLV or undersized HMAC — store value as raw bytes
318            _ => {
319                buf.push_field(
320                    &TLV_DESCRIPTORS[FD_TLV_VALUE],
321                    FieldValue::Bytes(&data[value_start..value_end]),
322                    offset + value_start..offset + value_end,
323                );
324            }
325        }
326
327        buf.end_container(obj_idx);
328        cursor = value_end;
329    }
330
331    Ok(())
332}
333
334/// Maximum byte length for extracted bit fields (128-bit SID = 16 bytes max).
335const EXTRACT_BITS_MAX: usize = 16;
336
337/// Extract a range of bits from a byte slice into a stack buffer.
338///
339/// Returns `(buffer, length)` where `buffer[..length]` contains the
340/// right-aligned extracted bytes.
341fn extract_bits(data: &[u8], bit_offset: u16, bit_len: u8) -> ([u8; EXTRACT_BITS_MAX], usize) {
342    let mut result = [0u8; EXTRACT_BITS_MAX];
343    if bit_len == 0 {
344        return (result, 0);
345    }
346    let num_bytes = (bit_len as usize).div_ceil(8);
347    for i in 0..bit_len as u16 {
348        let src_bit = bit_offset + i;
349        let src_byte = (src_bit / 8) as usize;
350        let src_bit_pos = 7 - (src_bit % 8);
351        if src_byte < data.len() {
352            let bit_val = (data[src_byte] >> src_bit_pos) & 1;
353            let dst_bit = (bit_len as u16 - 1) - i;
354            let dst_byte = (dst_bit / 8) as usize;
355            let dst_bit_pos = dst_bit % 8;
356            result[num_bytes - 1 - dst_byte] |= bit_val << dst_bit_pos;
357        }
358    }
359    (result, num_bytes)
360}
361
362/// Extracted SID parts as stack-allocated buffers.
363struct SidParts {
364    lb: ([u8; EXTRACT_BITS_MAX], usize),
365    ln: ([u8; EXTRACT_BITS_MAX], usize),
366    func: ([u8; EXTRACT_BITS_MAX], usize),
367    arg: ([u8; EXTRACT_BITS_MAX], usize),
368}
369
370/// Extract SID parts from a 128-bit SID.
371///
372/// Returns `(locator_block, locator_node, function, argument)` as stack buffers,
373/// each right-aligned to the nearest byte boundary.
374fn extract_sid_parts(sid: &[u8], ss: &SidStructure) -> SidParts {
375    let mut bit_offset: u16 = 0;
376    let lb = extract_bits(sid, bit_offset, ss.locator_block_bits);
377    bit_offset += ss.locator_block_bits as u16;
378    let ln = extract_bits(sid, bit_offset, ss.locator_node_bits);
379    bit_offset += ss.locator_node_bits as u16;
380    let func = extract_bits(sid, bit_offset, ss.function_bits);
381    bit_offset += ss.function_bits as u16;
382    let arg = extract_bits(sid, bit_offset, ss.argument_bits);
383    SidParts { lb, ln, func, arg }
384}
385
386/// Decompose segment list entries into CSID containers.
387///
388/// Each 128-bit entry is split into `k` slots of `slot_bits` starting at
389/// `start_bit`. Each slot is extracted as `csid_bits` (which equals the
390/// effective CSID width).
391///
392#[allow(clippy::too_many_arguments)]
393fn decompose_csid_containers<'pkt>(
394    buf: &mut DissectBuffer<'pkt>,
395    data: &'pkt [u8],
396    offset: usize,
397    num_segments: usize,
398    total_len: usize,
399    k: usize,
400    start_bit: usize,
401    slot_bits: usize,
402    csid_bits: u8,
403) {
404    for i in 0..num_segments {
405        let seg_start = SRH_FIXED_SIZE + i * SEGMENT_SIZE;
406        let seg_end = seg_start + SEGMENT_SIZE;
407        if seg_end > total_len {
408            break;
409        }
410        let sid = &data[seg_start..seg_end];
411        let seg_range = offset + seg_start..offset + seg_end;
412
413        // Object: { container_index, csids[] }
414        let obj_idx = buf.begin_container(
415            &CSID_CONTAINER_DESCRIPTORS[FD_CONTAINER_INDEX],
416            FieldValue::Object(0..0),
417            seg_range.clone(),
418        );
419        buf.push_field(
420            &CSID_CONTAINER_DESCRIPTORS[FD_CONTAINER_INDEX],
421            FieldValue::U8(i as u8),
422            seg_range.clone(),
423        );
424        // Array of CSIDs
425        let arr_idx = buf.begin_container(
426            &CSID_CONTAINER_DESCRIPTORS[FD_CSIDS],
427            FieldValue::Array(0..0),
428            seg_range.clone(),
429        );
430        for slot in 0..k {
431            let bit_offset = (start_bit + slot * slot_bits) as u16;
432            let (csid_buf, csid_len) = extract_bits(sid, bit_offset, csid_bits);
433            let scratch_range = buf.push_scratch(&csid_buf[..csid_len]);
434            buf.push_field(
435                &CSID_CONTAINER_DESCRIPTORS[FD_CSIDS],
436                FieldValue::Scratch(scratch_range),
437                seg_range.clone(),
438            );
439        }
440        buf.end_container(arr_idx);
441        buf.end_container(obj_idx);
442    }
443}
444
445/// Minimum bit width required for Args.Mob.Session (RFC 9433, Figure 8).
446///
447/// Layout: QFI (6) + R (1) + U (1) + PDU Session ID (32) = 40 bits.
448const ARGS_MOB_SESSION_MIN_BITS: u8 = 40;
449
450/// Parse Args.Mob.Session from a 128-bit SID at the given bit offset.
451///
452/// Pushes an Object container with QFI, R flag, U flag, and PDU Session ID
453/// fields into `buf`, or does nothing if the available bits are insufficient.
454///
455/// Layout (40 bits):
456/// ```text
457///  0                   1                   2                   3
458///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
459/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
460/// |   QFI     |R|U|                PDU Session ID                 |
461/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
462/// |PDU Sess(cont')|
463/// +-+-+-+-+-+-+-+-+
464/// ```
465// RFC 9433, Section 6.1 — https://www.rfc-editor.org/rfc/rfc9433#section-6.1
466fn push_args_mob_session<'pkt>(
467    buf: &mut DissectBuffer<'pkt>,
468    sid: &[u8],
469    bit_offset: u16,
470    available_bits: u8,
471    offset: usize,
472    seg_range: std::ops::Range<usize>,
473) {
474    if available_bits < ARGS_MOB_SESSION_MIN_BITS {
475        return;
476    }
477
478    // Extract all 40 bits at once to avoid 4 separate allocations.
479    let (raw, _) = extract_bits(sid, bit_offset, ARGS_MOB_SESSION_MIN_BITS);
480    // raw[0]: QFI(6) | R(1) | U(1),  raw[1..5]: PDU Session ID(32)
481    let qfi = (raw[0] >> 2) & 0x3F;
482    let r_flag = (raw[0] >> 1) & 1;
483    let u_flag = raw[0] & 1;
484    let pdu_session_id = read_be_u32(&raw, 1).unwrap_or_default();
485
486    let abs_range = offset + seg_range.start..offset + seg_range.end;
487    let obj_idx = buf.begin_container(
488        &AMS_PARENT_DESCRIPTORS[FD_ARGS_MOB_SESSION],
489        FieldValue::Object(0..0),
490        abs_range.clone(),
491    );
492    buf.push_field(
493        &AMS_DESCRIPTORS[FD_AMS_QFI],
494        FieldValue::U8(qfi),
495        abs_range.clone(),
496    );
497    buf.push_field(
498        &AMS_DESCRIPTORS[FD_AMS_R_FLAG],
499        FieldValue::U8(r_flag),
500        abs_range.clone(),
501    );
502    buf.push_field(
503        &AMS_DESCRIPTORS[FD_AMS_U_FLAG],
504        FieldValue::U8(u_flag),
505        abs_range.clone(),
506    );
507    buf.push_field(
508        &AMS_DESCRIPTORS[FD_AMS_PDU_SESSION_ID],
509        FieldValue::U32(pdu_session_id),
510        abs_range,
511    );
512    buf.end_container(obj_idx);
513}
514
515/// Decompose mobile-specific fields from a 128-bit SID.
516///
517/// Pushes additional fields into the current Object container in `buf`
518/// alongside the standard locator-block / locator-node / function /
519/// argument fields.
520// RFC 9433, Sections 6.5–6.8 — https://www.rfc-editor.org/rfc/rfc9433#section-6
521fn push_mobile_sid<'pkt>(
522    buf: &mut DissectBuffer<'pkt>,
523    sid: &[u8],
524    ss: &SidStructure,
525    encoding: &MobileSidEncoding,
526    offset: usize,
527    seg_range: std::ops::Range<usize>,
528) {
529    let abs_range = offset + seg_range.start..offset + seg_range.end;
530    let loc_func_bits =
531        ss.locator_block_bits as u16 + ss.locator_node_bits as u16 + ss.function_bits as u16;
532
533    /// Extract an embedded IPv4 field from a SID and push it into buf.
534    ///
535    /// Decodes as an IPv4 address only when the field is exactly 32 bits wide
536    /// and byte-aligned; otherwise emits Scratch bytes to avoid misinterpretation.
537    fn push_embedded_ipv4(
538        buf: &mut DissectBuffer<'_>,
539        sid: &[u8],
540        bit_offset: u16,
541        ipv4da_bits: u8,
542        range: std::ops::Range<usize>,
543    ) {
544        let (raw, raw_len) = extract_bits(sid, bit_offset, ipv4da_bits);
545        if ipv4da_bits == 32 && bit_offset % 8 == 0 {
546            let mut addr = [0u8; 4];
547            for (i, b) in raw[..raw_len].iter().take(4).enumerate() {
548                addr[i] = *b;
549            }
550            buf.push_field(
551                &MOBILE_DESCRIPTORS[FD_MOBILE_EMBEDDED_IPV4],
552                FieldValue::Ipv4Addr(addr),
553                range,
554            );
555        } else {
556            let scratch_range = buf.push_scratch(&raw[..raw_len]);
557            buf.push_field(
558                &MOBILE_DESCRIPTORS[FD_MOBILE_EMBEDDED_IPV4],
559                FieldValue::Scratch(scratch_range),
560                range,
561            );
562        }
563    }
564
565    match encoding {
566        MobileSidEncoding::EndMGtp4E {
567            ipv4da_bits,
568            args_mob_session_bits,
569        } => {
570            // RFC 9433, Figure 9 — End.M.GTP4.E SID:
571            // LOC-FUNC | IPv4DA(a) | Args.Mob.Session(b) | 0-padded(c)
572            if *ipv4da_bits > 0 {
573                push_embedded_ipv4(buf, sid, loc_func_bits, *ipv4da_bits, abs_range.clone());
574            }
575            let ams_offset = loc_func_bits + *ipv4da_bits as u16;
576            push_args_mob_session(
577                buf,
578                sid,
579                ams_offset,
580                *args_mob_session_bits,
581                offset,
582                seg_range,
583            );
584        }
585        MobileSidEncoding::EndMGtp6E => {
586            // RFC 9433, Section 6.5 — End.M.GTP6.E:
587            // Args.Mob.Session is in the Argument portion
588            let arg_offset = loc_func_bits;
589            push_args_mob_session(buf, sid, arg_offset, ss.argument_bits, offset, seg_range);
590        }
591        MobileSidEncoding::HmGtp4D {
592            prefix_bits,
593            ipv4da_bits,
594            args_mob_session_bits,
595        } => {
596            // RFC 9433, Figure 11 — H.M.GTP4.D SID:
597            // Destination UPF Prefix | IPv4DA(a) | Args.Mob.Session(b) | 0-padded(c)
598            let ipv4_start = *prefix_bits as u16;
599            if *ipv4da_bits > 0 {
600                push_embedded_ipv4(buf, sid, ipv4_start, *ipv4da_bits, abs_range.clone());
601            }
602            let ams_offset = ipv4_start + *ipv4da_bits as u16;
603            push_args_mob_session(
604                buf,
605                sid,
606                ams_offset,
607                *args_mob_session_bits,
608                offset,
609                seg_range,
610            );
611        }
612        MobileSidEncoding::EndLimit {
613            group_id_bits,
614            limit_rate_bits,
615        } => {
616            // RFC 9433, Figure 12 — End.Limit:
617            // LOC+FUNC(rate-limit) | group-id(i) | limit-rate(j)
618            if *group_id_bits > 0 {
619                let (gid_buf, gid_len) = extract_bits(sid, loc_func_bits, *group_id_bits);
620                let scratch_range = buf.push_scratch(&gid_buf[..gid_len]);
621                buf.push_field(
622                    &MOBILE_DESCRIPTORS[FD_MOBILE_GROUP_ID],
623                    FieldValue::Scratch(scratch_range),
624                    abs_range.clone(),
625                );
626            }
627            if *limit_rate_bits > 0 {
628                let lr_offset = loc_func_bits + *group_id_bits as u16;
629                let (lr_buf, lr_len) = extract_bits(sid, lr_offset, *limit_rate_bits);
630                let scratch_range = buf.push_scratch(&lr_buf[..lr_len]);
631                buf.push_field(
632                    &MOBILE_DESCRIPTORS[FD_MOBILE_LIMIT_RATE],
633                    FieldValue::Scratch(scratch_range),
634                    abs_range,
635                );
636            }
637        }
638    }
639}
640
641// ---------------------------------------------------------------------------
642// Top-level field descriptor indices
643// ---------------------------------------------------------------------------
644
645const FD_NEXT_HEADER: usize = 0;
646const FD_HDR_EXT_LEN: usize = 1;
647const FD_ROUTING_TYPE: usize = 2;
648const FD_SEGMENTS_LEFT: usize = 3;
649const FD_LAST_ENTRY: usize = 4;
650const FD_FLAGS: usize = 5;
651const FD_TAG: usize = 6;
652const FD_SEGMENTS: usize = 7;
653const FD_SEGMENTS_STRUCTURE: usize = 8;
654const FD_CSID_CONTAINERS: usize = 9;
655const FD_TLVS: usize = 10;
656
657/// Top-level field descriptors for the SRv6 dissector.
658static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
659    FieldDescriptor::new("next_header", "Next Header", FieldType::U8),
660    FieldDescriptor::new("hdr_ext_len", "Header Extension Length", FieldType::U8),
661    FieldDescriptor::new("routing_type", "Routing Type", FieldType::U8),
662    FieldDescriptor::new("segments_left", "Segments Left", FieldType::U8),
663    FieldDescriptor::new("last_entry", "Last Entry", FieldType::U8),
664    FieldDescriptor::new("flags", "Flags", FieldType::Object),
665    FieldDescriptor::new("tag", "Tag", FieldType::U16),
666    FieldDescriptor::new("segments", "Segment List", FieldType::Array),
667    FieldDescriptor::new("segments_structure", "Segment Structure", FieldType::Array).optional(),
668    FieldDescriptor::new("csid_containers", "CSID Containers", FieldType::Array).optional(),
669    FieldDescriptor::new("tlvs", "TLVs", FieldType::Array).optional(),
670];
671
672// ---------------------------------------------------------------------------
673// Child field descriptor arrays for sub-fields
674// ---------------------------------------------------------------------------
675
676/// Returns a human-readable name for an SRv6 TLV type.
677///
678/// RFC 8754, Section 2.1 — IANA SRv6 TLV Types registry.
679fn srv6_tlv_type_name(t: u8) -> Option<&'static str> {
680    match t {
681        0 => Some("Pad1"),
682        4 => Some("PadN"),
683        5 => Some("HMAC"),
684        _ => None,
685    }
686}
687
688/// Container descriptor for an SRv6 TLV Object.
689///
690/// The outer label resolves to the TLV name (e.g. `HMAC`) by looking up
691/// the inner `type` field, avoiding collision with the inner "Type" label.
692static FD_TLV: FieldDescriptor = FieldDescriptor {
693    name: "tlv",
694    display_name: "TLV",
695    field_type: FieldType::Object,
696    optional: false,
697    children: None,
698    display_fn: Some(|v, children| match v {
699        FieldValue::Object(_) => children.iter().find_map(|f| match (f.name(), &f.value) {
700            ("type", FieldValue::U8(t)) => srv6_tlv_type_name(*t),
701            _ => None,
702        }),
703        _ => None,
704    }),
705    format_fn: None,
706};
707
708// TLV sub-field descriptors (used in parse_tlvs)
709const FD_TLV_TYPE: usize = 0;
710const FD_TLV_LENGTH: usize = 1;
711const FD_TLV_PADDING: usize = 2;
712const FD_TLV_HMAC_D_FLAG: usize = 3;
713const FD_TLV_HMAC_KEY_ID: usize = 4;
714const FD_TLV_HMAC: usize = 5;
715const FD_TLV_VALUE: usize = 6;
716
717static TLV_DESCRIPTORS: &[FieldDescriptor] = &[
718    FieldDescriptor::new("type", "Type", FieldType::U8),
719    FieldDescriptor::new("length", "Length", FieldType::U8),
720    FieldDescriptor::new("padding", "Padding", FieldType::Bytes).optional(),
721    FieldDescriptor::new("hmac_d_flag", "D Flag", FieldType::U8).optional(),
722    FieldDescriptor::new("hmac_key_id", "HMAC Key ID", FieldType::U32).optional(),
723    FieldDescriptor::new("hmac", "HMAC", FieldType::Bytes).optional(),
724    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
725];
726
727// CSID container sub-field descriptors
728const FD_CONTAINER_INDEX: usize = 0;
729const FD_CSIDS: usize = 1;
730
731static CSID_CONTAINER_DESCRIPTORS: &[FieldDescriptor] = &[
732    FieldDescriptor::new("container_index", "Container Index", FieldType::U8),
733    FieldDescriptor::new("csids", "CSIDs", FieldType::Array),
734];
735
736// Args.Mob.Session sub-field descriptors
737const FD_AMS_QFI: usize = 0;
738const FD_AMS_R_FLAG: usize = 1;
739const FD_AMS_U_FLAG: usize = 2;
740const FD_AMS_PDU_SESSION_ID: usize = 3;
741
742static AMS_DESCRIPTORS: &[FieldDescriptor] = &[
743    FieldDescriptor::new("qfi", "QFI", FieldType::U8),
744    FieldDescriptor::new("r_flag", "R Flag", FieldType::U8),
745    FieldDescriptor::new("u_flag", "U Flag", FieldType::U8),
746    FieldDescriptor::new("pdu_session_id", "PDU Session ID", FieldType::U32),
747];
748
749// Parent descriptor for the args_mob_session Object field
750const FD_ARGS_MOB_SESSION: usize = 0;
751
752static AMS_PARENT_DESCRIPTORS: &[FieldDescriptor] =
753    &[
754        FieldDescriptor::new("args_mob_session", "Args.Mob.Session", FieldType::Object)
755            .optional()
756            .with_children(AMS_DESCRIPTORS),
757    ];
758
759// Mobile SID sub-field descriptors (embedded_ipv4, group_id, limit_rate)
760const FD_MOBILE_EMBEDDED_IPV4: usize = 0;
761const FD_MOBILE_GROUP_ID: usize = 1;
762const FD_MOBILE_LIMIT_RATE: usize = 2;
763
764static MOBILE_DESCRIPTORS: &[FieldDescriptor] = &[
765    FieldDescriptor::new("embedded_ipv4", "Embedded IPv4", FieldType::Ipv4Addr).optional(),
766    FieldDescriptor::new("group_id", "Group ID", FieldType::Bytes).optional(),
767    FieldDescriptor::new("limit_rate", "Limit Rate", FieldType::Bytes).optional(),
768];
769
770/// Writes a SID structure sub-field as a JSON-quoted hex string (e.g., `"20010db8"`).
771///
772/// Handles both [`FieldValue::Scratch`] (bit-extracted sub-byte data stored in the
773/// scratch buffer) and [`FieldValue::Bytes`] (byte-aligned data).
774fn format_sid_hex(
775    value: &FieldValue<'_>,
776    ctx: &FormatContext<'_>,
777    w: &mut dyn std::io::Write,
778) -> std::io::Result<()> {
779    let bytes: &[u8] = match value {
780        FieldValue::Scratch(range) => &ctx.scratch[range.start as usize..range.end as usize],
781        FieldValue::Bytes(b) => b,
782        _ => return w.write_all(b"\"\""),
783    };
784    if bytes.is_empty() {
785        return w.write_all(b"\"\"");
786    }
787    w.write_all(b"\"")?;
788    for &b in bytes {
789        write!(w, "{b:02x}")?;
790    }
791    w.write_all(b"\"")
792}
793
794// SID structure sub-field descriptors
795const FD_SID_LOCATOR_BLOCK: usize = 0;
796const FD_SID_LOCATOR_NODE: usize = 1;
797const FD_SID_FUNCTION: usize = 2;
798const FD_SID_ARGUMENT: usize = 3;
799
800static SID_STRUCTURE_DESCRIPTORS: &[FieldDescriptor] = &[
801    FieldDescriptor::new("locator_block", "Locator Block", FieldType::Bytes)
802        .with_format_fn(format_sid_hex),
803    FieldDescriptor::new("locator_node", "Locator Node", FieldType::Bytes)
804        .with_format_fn(format_sid_hex),
805    FieldDescriptor::new("function", "Function", FieldType::Bytes).with_format_fn(format_sid_hex),
806    FieldDescriptor::new("argument", "Argument", FieldType::Bytes).with_format_fn(format_sid_hex),
807];
808
809// Flags sub-field descriptors
810const FD_FLAGS_RAW: usize = 0;
811const FD_FLAGS_O_FLAG: usize = 1;
812
813static FLAGS_DESCRIPTORS: &[FieldDescriptor] = &[
814    FieldDescriptor::new("raw", "Raw", FieldType::U8),
815    FieldDescriptor::new("o_flag", "O Flag", FieldType::U8),
816];
817
818impl Dissector for Srv6Dissector {
819    fn name(&self) -> &'static str {
820        "IPv6 Segment Routing Header"
821    }
822
823    fn short_name(&self) -> &'static str {
824        "SRv6"
825    }
826
827    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
828        FIELD_DESCRIPTORS
829    }
830
831    fn dissect<'pkt>(
832        &self,
833        data: &'pkt [u8],
834        buf: &mut DissectBuffer<'pkt>,
835        offset: usize,
836    ) -> Result<DissectResult, PacketError> {
837        if data.len() < SRH_FIXED_SIZE {
838            return Err(PacketError::Truncated {
839                expected: SRH_FIXED_SIZE,
840                actual: data.len(),
841            });
842        }
843
844        // RFC 8754, Section 2 — SRH fixed fields
845        let next_header = data[0];
846        let hdr_ext_len = data[1];
847        let routing_type = data[2];
848        let segments_left = data[3];
849        let last_entry = data[4];
850        let flags = data[5];
851        let tag = read_be_u16(data, 6)?;
852
853        // RFC 8200, Section 4.4 — total length = (Hdr Ext Len + 1) * 8
854        let total_len = (hdr_ext_len as usize + 1) * 8;
855
856        if data.len() < total_len {
857            return Err(PacketError::Truncated {
858                expected: total_len,
859                actual: data.len(),
860            });
861        }
862
863        // RFC 8754, Section 4.3.1.1 — validation
864        // max_last_entry = (Hdr Ext Len / 2) - 1
865        // Equivalently: Last Entry must be strictly less than (Hdr Ext Len / 2).
866        // When Hdr Ext Len < 2, (Hdr Ext Len / 2) = 0, so all Last Entry values
867        // are invalid (the RFC formula would give −1 in signed arithmetic).
868        let max_segs = hdr_ext_len as usize / 2;
869        if last_entry as usize >= max_segs {
870            return Err(PacketError::InvalidHeader(
871                "SRH Last Entry exceeds maximum for Hdr Ext Len",
872            ));
873        }
874
875        if segments_left as usize > last_entry as usize + 1 {
876            return Err(PacketError::InvalidHeader(
877                "SRH Segments Left exceeds Last Entry + 1",
878            ));
879        }
880
881        buf.begin_layer(
882            self.short_name(),
883            None,
884            FIELD_DESCRIPTORS,
885            offset..offset + total_len,
886        );
887
888        buf.push_field(
889            &FIELD_DESCRIPTORS[FD_NEXT_HEADER],
890            FieldValue::U8(next_header),
891            offset..offset + 1,
892        );
893        buf.push_field(
894            &FIELD_DESCRIPTORS[FD_HDR_EXT_LEN],
895            FieldValue::U8(hdr_ext_len),
896            offset + 1..offset + 2,
897        );
898        buf.push_field(
899            &FIELD_DESCRIPTORS[FD_ROUTING_TYPE],
900            FieldValue::U8(routing_type),
901            offset + 2..offset + 3,
902        );
903        buf.push_field(
904            &FIELD_DESCRIPTORS[FD_SEGMENTS_LEFT],
905            FieldValue::U8(segments_left),
906            offset + 3..offset + 4,
907        );
908        buf.push_field(
909            &FIELD_DESCRIPTORS[FD_LAST_ENTRY],
910            FieldValue::U8(last_entry),
911            offset + 4..offset + 5,
912        );
913
914        // RFC 8754, Section 2 — Flags (8 bits)
915        // RFC 9259, Section 3 — O-flag (OAM) at bit 2
916        let flags_idx = buf.begin_container(
917            &FIELD_DESCRIPTORS[FD_FLAGS],
918            FieldValue::Object(0..0),
919            offset + 5..offset + 6,
920        );
921        buf.push_field(
922            &FLAGS_DESCRIPTORS[FD_FLAGS_RAW],
923            FieldValue::U8(flags),
924            offset + 5..offset + 6,
925        );
926        buf.push_field(
927            &FLAGS_DESCRIPTORS[FD_FLAGS_O_FLAG],
928            FieldValue::U8((flags >> 5) & 1),
929            offset + 5..offset + 6,
930        );
931        buf.end_container(flags_idx);
932
933        buf.push_field(
934            &FIELD_DESCRIPTORS[FD_TAG],
935            FieldValue::U16(tag),
936            offset + 6..offset + 8,
937        );
938
939        // RFC 8754, Section 2 — Segment List
940        // Encoded starting from the last segment of the SR Policy.
941        // Segment List[0] is the last segment; Segment List[n] is the first.
942        let num_segments = last_entry as usize + 1;
943        let mut actual_segments = 0usize;
944        let seg_arr_idx = buf.begin_container(
945            &FIELD_DESCRIPTORS[FD_SEGMENTS],
946            FieldValue::Array(0..0),
947            offset + SRH_FIXED_SIZE..offset + SRH_FIXED_SIZE + num_segments * SEGMENT_SIZE,
948        );
949        for i in 0..num_segments {
950            let seg_start = SRH_FIXED_SIZE + i * SEGMENT_SIZE;
951            let seg_end = seg_start + SEGMENT_SIZE;
952            if seg_end > total_len {
953                break;
954            }
955            let addr = read_ipv6_addr(data, seg_start)?;
956            buf.push_field(
957                &FIELD_DESCRIPTORS[FD_SEGMENTS],
958                FieldValue::Ipv6Addr(addr),
959                offset + seg_start..offset + seg_end,
960            );
961            actual_segments += 1;
962        }
963        buf.end_container(seg_arr_idx);
964
965        let seg_range_end = SRH_FIXED_SIZE + actual_segments * SEGMENT_SIZE;
966
967        // RFC 8986, Section 3.1 — SID structure decomposition (optional)
968        if let Some(ref ss) = self.sid_structure {
969            let struct_arr_idx = buf.begin_container(
970                &FIELD_DESCRIPTORS[FD_SEGMENTS_STRUCTURE],
971                FieldValue::Array(0..0),
972                offset + SRH_FIXED_SIZE..offset + seg_range_end,
973            );
974            for i in 0..num_segments {
975                let seg_start = SRH_FIXED_SIZE + i * SEGMENT_SIZE;
976                let seg_end = seg_start + SEGMENT_SIZE;
977                if seg_end > total_len {
978                    break;
979                }
980                let sid = &data[seg_start..seg_end];
981                let parts = extract_sid_parts(sid, ss);
982                let seg_range = offset + seg_start..offset + seg_end;
983
984                let obj_idx = buf.begin_container(
985                    &SID_STRUCTURE_DESCRIPTORS[FD_SID_LOCATOR_BLOCK],
986                    FieldValue::Object(0..0),
987                    seg_range.clone(),
988                );
989
990                let lb_scratch = buf.push_scratch(&parts.lb.0[..parts.lb.1]);
991                buf.push_field(
992                    &SID_STRUCTURE_DESCRIPTORS[FD_SID_LOCATOR_BLOCK],
993                    FieldValue::Scratch(lb_scratch),
994                    seg_range.clone(),
995                );
996                let ln_scratch = buf.push_scratch(&parts.ln.0[..parts.ln.1]);
997                buf.push_field(
998                    &SID_STRUCTURE_DESCRIPTORS[FD_SID_LOCATOR_NODE],
999                    FieldValue::Scratch(ln_scratch),
1000                    seg_range.clone(),
1001                );
1002                let func_scratch = buf.push_scratch(&parts.func.0[..parts.func.1]);
1003                buf.push_field(
1004                    &SID_STRUCTURE_DESCRIPTORS[FD_SID_FUNCTION],
1005                    FieldValue::Scratch(func_scratch),
1006                    seg_range.clone(),
1007                );
1008                let arg_scratch = buf.push_scratch(&parts.arg.0[..parts.arg.1]);
1009                buf.push_field(
1010                    &SID_STRUCTURE_DESCRIPTORS[FD_SID_ARGUMENT],
1011                    FieldValue::Scratch(arg_scratch),
1012                    seg_range.clone(),
1013                );
1014
1015                // RFC 9433 — Mobile SID encoding decomposition
1016                if let Some(ref enc) = ss.mobile_encoding {
1017                    push_mobile_sid(buf, sid, ss, enc, offset, seg_start..seg_end);
1018                }
1019
1020                buf.end_container(obj_idx);
1021            }
1022            buf.end_container(struct_arr_idx);
1023
1024            // RFC 9800 — CSID container decomposition
1025            let lnfl = ss.locator_node_bits as usize + ss.function_bits as usize;
1026            // `lnfl > 0` guards every division below; the lint's `checked_div`
1027            // suggestion would only obscure the shared precondition.
1028            #[allow(clippy::manual_checked_ops)]
1029            if lnfl > 0 {
1030                match &ss.csid_flavor {
1031                    CsidFlavor::ReplaceCsid { csid_bits } if *csid_bits > 0 => {
1032                        // REPLACE-CSID (Section 4): K = 128 / LNFL slots per entry
1033                        let k = 128 / lnfl;
1034                        let csid_arr_idx = buf.begin_container(
1035                            &FIELD_DESCRIPTORS[FD_CSID_CONTAINERS],
1036                            FieldValue::Array(0..0),
1037                            offset + SRH_FIXED_SIZE..offset + seg_range_end,
1038                        );
1039                        decompose_csid_containers(
1040                            buf,
1041                            data,
1042                            offset,
1043                            num_segments,
1044                            total_len,
1045                            k,
1046                            0, // slots start at bit 0
1047                            lnfl,
1048                            *csid_bits,
1049                        );
1050                        buf.end_container(csid_arr_idx);
1051                    }
1052                    CsidFlavor::NextCsid { usid_bits } if *usid_bits > 0 => {
1053                        // NEXT-CSID / uSID (Section 3):
1054                        // Locator-Block occupies first LBL bits; remaining
1055                        // (128 - LBL) bits hold K = (128 - LBL) / LNFL uSIDs.
1056                        let lbl = ss.locator_block_bits as usize;
1057                        if lbl < 128 {
1058                            let k = (128 - lbl) / lnfl;
1059                            let csid_arr_idx = buf.begin_container(
1060                                &FIELD_DESCRIPTORS[FD_CSID_CONTAINERS],
1061                                FieldValue::Array(0..0),
1062                                offset + SRH_FIXED_SIZE..offset + seg_range_end,
1063                            );
1064                            decompose_csid_containers(
1065                                buf,
1066                                data,
1067                                offset,
1068                                num_segments,
1069                                total_len,
1070                                k,
1071                                lbl, // slots start after Locator-Block
1072                                lnfl,
1073                                *usid_bits,
1074                            );
1075                            buf.end_container(csid_arr_idx);
1076                        }
1077                    }
1078                    _ => {}
1079                }
1080            }
1081        }
1082
1083        // RFC 8754, Section 2.1 — Optional TLVs after the Segment List
1084        let tlv_start = SRH_FIXED_SIZE + num_segments * SEGMENT_SIZE;
1085        if tlv_start < total_len {
1086            let tlv_arr_idx = buf.begin_container(
1087                &FIELD_DESCRIPTORS[FD_TLVS],
1088                FieldValue::Array(0..0),
1089                offset + tlv_start..offset + total_len,
1090            );
1091            parse_tlvs(buf, data, offset, tlv_start, total_len)?;
1092            buf.end_container(tlv_arr_idx);
1093        }
1094
1095        buf.end_layer();
1096
1097        Ok(DissectResult::new(
1098            total_len,
1099            DispatchHint::ByIpProtocol(next_header),
1100        ))
1101    }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106    use super::*;
1107    use packet_dissector_core::field::{Field, FormatContext};
1108    use packet_dissector_core::packet::Layer;
1109
1110    /// Look up a nested field within an Object/Array container by name.
1111    fn nested_field_by_name<'a, 'pkt>(
1112        buf: &'a DissectBuffer<'pkt>,
1113        parent: &Field<'pkt>,
1114        name: &str,
1115    ) -> Option<&'a Field<'pkt>> {
1116        let range = parent.value.as_container_range()?;
1117        buf.nested_fields(range).iter().find(|f| f.name() == name)
1118    }
1119
1120    /// Resolve a Scratch field value into its byte slice for comparison.
1121    fn resolve_scratch<'a>(buf: &'a DissectBuffer<'_>, field: &Field<'_>) -> &'a [u8] {
1122        let range = field
1123            .value
1124            .as_scratch_range()
1125            .expect("expected Scratch value");
1126        &buf.scratch()[range.start as usize..range.end as usize]
1127    }
1128
1129    /// Collect top-level entries from an Array's nested fields (skipping sub-children of containers).
1130    fn array_entries<'a, 'pkt>(
1131        buf: &'a DissectBuffer<'pkt>,
1132        array_field: &Field<'pkt>,
1133    ) -> Vec<&'a Field<'pkt>> {
1134        let range = array_field.value.as_container_range().unwrap();
1135        let all = buf.nested_fields(range);
1136        let base = range.start;
1137        let mut result = Vec::new();
1138        let mut abs_idx = base;
1139        while abs_idx < range.end {
1140            let rel = (abs_idx - base) as usize;
1141            let field = &all[rel];
1142            result.push(field);
1143            // If this field is a container, skip its children
1144            if let Some(child_range) = field.value.as_container_range() {
1145                // Jump past this field + all its children
1146                abs_idx = child_range.end;
1147            } else {
1148                abs_idx += 1;
1149            }
1150        }
1151        result
1152    }
1153
1154    // # RFC 8754 (SRv6 SRH) Coverage
1155    //
1156    // | RFC Section  | Description              | Test                                  |
1157    // |--------------|--------------------------|---------------------------------------|
1158    // | 2            | SRH format               | parse_srv6_single_segment             |
1159    // | 2            | SRH format               | parse_srv6_multiple_segments          |
1160    // | 2            | Metadata                 | srv6_dissector_metadata               |
1161    // | 2            | Segment List layout      | parse_srv6_multiple_segments          |
1162    // | 2            | Flags/Tag                | parse_srv6_flags_and_tag              |
1163    // | 2            | Optional TLVs            | parse_srv6_with_tlvs                  |
1164    // | 2            | Offset handling          | parse_srv6_with_offset                |
1165    // | 2.1          | Pad1 TLV                 | parse_srv6_tlv_pad1                   |
1166    // | 2.1          | PadN TLV                 | parse_srv6_tlv_padn                   |
1167    // | 2.1          | HMAC TLV                 | parse_srv6_tlv_hmac                   |
1168    // | 2.1          | HMAC D-flag              | parse_srv6_tlv_hmac_d_flag_clear      |
1169    // | 2.1          | Unknown TLV              | parse_srv6_tlv_unknown                |
1170    // | 2.1          | Multiple TLVs            | parse_srv6_tlv_multiple               |
1171    // | 2.1          | Truncated TLV header     | parse_srv6_tlv_truncated_length       |
1172    // | 2.1          | Truncated TLV value      | parse_srv6_tlv_truncated_value        |
1173    // | 2.1          | Undersized HMAC TLV      | parse_srv6_tlv_hmac_truncated         |
1174    // | 4.3.1.1      | Last Entry validation    | parse_srv6_invalid_last_entry         |
1175    // | 4.3.1.1      | Hdr Ext Len too small    | parse_srv6_invalid_hdr_ext_len_small  |
1176    // | 4.3.1.1      | Segments Left validation | parse_srv6_invalid_segments_left      |
1177    // | —            | Truncated fixed header   | parse_srv6_truncated_header           |
1178    // | —            | Truncated total length   | parse_srv6_truncated_total            |
1179    // | —            | Next header chaining     | parse_srv6_next_header_tcp            |
1180    //
1181    // # RFC 9259 (SRv6 OAM) Coverage
1182    //
1183    // | RFC Section  | Description              | Test                                  |
1184    // |--------------|--------------------------|---------------------------------------|
1185    // | 3            | O-flag definition        | parse_srv6_flags_o_flag_set           |
1186    // | 3            | O-flag clear             | parse_srv6_flags_all_zero             |
1187    // | 3            | O-flag with other bits   | parse_srv6_flags_and_tag              |
1188
1189    const SID_A: [u8; 16] = [
1190        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1191        0x01,
1192    ];
1193    const SID_B: [u8; 16] = [
1194        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1195        0x02,
1196    ];
1197    const SID_C: [u8; 16] = [
1198        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1199        0x03,
1200    ];
1201
1202    /// Build an SRH with given parameters.
1203    fn build_srh(
1204        next_header: u8,
1205        segments_left: u8,
1206        segments: &[[u8; 16]],
1207        flags: u8,
1208        tag: u16,
1209        tlvs: &[u8],
1210    ) -> Vec<u8> {
1211        let num_segments = segments.len();
1212        // Pad TLVs to 8-byte alignment
1213        let seg_bytes = num_segments * SEGMENT_SIZE;
1214        let tlv_padded_len = if tlvs.is_empty() {
1215            0
1216        } else {
1217            // Total length must be multiple of 8
1218            let raw_total = SRH_FIXED_SIZE + seg_bytes + tlvs.len();
1219            let padded_total = (raw_total + 7) & !7;
1220            padded_total - SRH_FIXED_SIZE - seg_bytes
1221        };
1222        let total_len = SRH_FIXED_SIZE + seg_bytes + tlv_padded_len;
1223        let hdr_ext_len = (total_len / 8) - 1;
1224        let last_entry = if num_segments == 0 {
1225            0
1226        } else {
1227            (num_segments - 1) as u8
1228        };
1229
1230        let mut data = Vec::with_capacity(total_len);
1231        data.push(next_header);
1232        data.push(hdr_ext_len as u8);
1233        data.push(4); // Routing Type = 4
1234        data.push(segments_left);
1235        data.push(last_entry);
1236        data.push(flags);
1237        data.extend_from_slice(&tag.to_be_bytes());
1238
1239        for seg in segments {
1240            data.extend_from_slice(seg);
1241        }
1242
1243        if !tlvs.is_empty() {
1244            data.extend_from_slice(tlvs);
1245            // Pad to total_len
1246            data.resize(total_len, 0);
1247        }
1248
1249        data
1250    }
1251
1252    #[test]
1253    fn srv6_dissector_metadata() {
1254        let d = Srv6Dissector::new();
1255        assert_eq!(d.name(), "IPv6 Segment Routing Header");
1256        assert_eq!(d.short_name(), "SRv6");
1257    }
1258
1259    #[test]
1260    fn parse_srv6_single_segment() {
1261        // 1 segment: hdr_ext_len = 2, total = 24 bytes
1262        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
1263        let mut buf = DissectBuffer::new();
1264        let result = Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1265
1266        assert_eq!(result.bytes_consumed, 24);
1267        assert_eq!(result.next, DispatchHint::ByIpProtocol(6));
1268
1269        let layer = &buf.layers()[0];
1270        assert_eq!(layer.name, "SRv6");
1271        assert_eq!(
1272            buf.field_by_name(layer, "next_header").unwrap().value,
1273            FieldValue::U8(6)
1274        );
1275        assert_eq!(
1276            buf.field_by_name(layer, "hdr_ext_len").unwrap().value,
1277            FieldValue::U8(2)
1278        );
1279        assert_eq!(
1280            buf.field_by_name(layer, "routing_type").unwrap().value,
1281            FieldValue::U8(4)
1282        );
1283        assert_eq!(
1284            buf.field_by_name(layer, "segments_left").unwrap().value,
1285            FieldValue::U8(1)
1286        );
1287        assert_eq!(
1288            buf.field_by_name(layer, "last_entry").unwrap().value,
1289            FieldValue::U8(0)
1290        );
1291        let segments = {
1292            let r = buf
1293                .field_by_name(layer, "segments")
1294                .unwrap()
1295                .value
1296                .as_container_range()
1297                .unwrap();
1298            buf.nested_fields(r)
1299        };
1300        assert_eq!(segments.len(), 1);
1301        assert_eq!(segments[0].value, FieldValue::Ipv6Addr(SID_A));
1302        assert!(buf.field_by_name(layer, "tlvs").is_none());
1303    }
1304
1305    #[test]
1306    fn parse_srv6_multiple_segments() {
1307        // 3 segments: hdr_ext_len = 6, total = 56 bytes
1308        let data = build_srh(17, 2, &[SID_A, SID_B, SID_C], 0, 0, &[]);
1309        let mut buf = DissectBuffer::new();
1310        let result = Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1311
1312        assert_eq!(result.bytes_consumed, 56);
1313        assert_eq!(result.next, DispatchHint::ByIpProtocol(17));
1314
1315        let layer = &buf.layers()[0];
1316        assert_eq!(
1317            buf.field_by_name(layer, "last_entry").unwrap().value,
1318            FieldValue::U8(2)
1319        );
1320        assert_eq!(
1321            buf.field_by_name(layer, "segments_left").unwrap().value,
1322            FieldValue::U8(2)
1323        );
1324        let segments = {
1325            let r = buf
1326                .field_by_name(layer, "segments")
1327                .unwrap()
1328                .value
1329                .as_container_range()
1330                .unwrap();
1331            buf.nested_fields(r)
1332        };
1333        assert_eq!(segments.len(), 3);
1334        assert_eq!(segments[0].value, FieldValue::Ipv6Addr(SID_A));
1335        assert_eq!(segments[1].value, FieldValue::Ipv6Addr(SID_B));
1336        assert_eq!(segments[2].value, FieldValue::Ipv6Addr(SID_C));
1337    }
1338
1339    /// Look up a sub-field within a flags Object value.
1340    fn flags_sub_field<'a, 'pkt>(
1341        buf: &'a DissectBuffer<'pkt>,
1342        layer: &Layer,
1343        name: &str,
1344    ) -> Option<&'a Field<'pkt>> {
1345        let flags_field = buf.field_by_name(layer, "flags")?;
1346        let range = flags_field.value.as_container_range()?;
1347        buf.nested_fields(range).iter().find(|f| f.name() == name)
1348    }
1349
1350    #[test]
1351    fn parse_srv6_flags_and_tag() {
1352        // 0xAB = 0b1010_1011: bit0=1, bit1=0, bit2(O-flag)=1, bit3=0, bit4=1, bit5=0, bit6=1, bit7=1
1353        let data = build_srh(59, 0, &[SID_A], 0xAB, 0x1234, &[]);
1354        let mut buf = DissectBuffer::new();
1355        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1356
1357        let layer = &buf.layers()[0];
1358        // Raw value preserved
1359        assert_eq!(
1360            flags_sub_field(&buf, layer, "raw").unwrap().value,
1361            FieldValue::U8(0xAB)
1362        );
1363        // RFC 9259 — O-flag is bit 2 (0x20): (0xAB >> 5) & 1 = 1
1364        assert_eq!(
1365            flags_sub_field(&buf, layer, "o_flag").unwrap().value,
1366            FieldValue::U8(1)
1367        );
1368        assert_eq!(
1369            buf.field_by_name(layer, "tag").unwrap().value,
1370            FieldValue::U16(0x1234)
1371        );
1372    }
1373
1374    #[test]
1375    fn parse_srv6_flags_o_flag_set() {
1376        // Only O-flag set: 0x20 = 0b0010_0000
1377        let data = build_srh(6, 1, &[SID_A], 0x20, 0, &[]);
1378        let mut buf = DissectBuffer::new();
1379        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1380
1381        let layer = &buf.layers()[0];
1382        assert_eq!(
1383            flags_sub_field(&buf, layer, "raw").unwrap().value,
1384            FieldValue::U8(0x20)
1385        );
1386        assert_eq!(
1387            flags_sub_field(&buf, layer, "o_flag").unwrap().value,
1388            FieldValue::U8(1)
1389        );
1390    }
1391
1392    #[test]
1393    fn parse_srv6_flags_all_zero() {
1394        let data = build_srh(6, 1, &[SID_A], 0x00, 0, &[]);
1395        let mut buf = DissectBuffer::new();
1396        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1397
1398        let layer = &buf.layers()[0];
1399        assert_eq!(
1400            flags_sub_field(&buf, layer, "raw").unwrap().value,
1401            FieldValue::U8(0)
1402        );
1403        assert_eq!(
1404            flags_sub_field(&buf, layer, "o_flag").unwrap().value,
1405            FieldValue::U8(0)
1406        );
1407    }
1408
1409    #[test]
1410    fn parse_srv6_with_tlvs() {
1411        // PadN TLV (type=4, len=2, data=0x00 0x00) = 4 bytes
1412        // After 1 segment (24 bytes header+seg), need to pad to 32 bytes
1413        // So TLV area = 8 bytes, hdr_ext_len = 3
1414        let tlvs = [4, 2, 0x00, 0x00]; // PadN
1415        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1416        let mut buf = DissectBuffer::new();
1417        let result = Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1418
1419        assert_eq!(result.bytes_consumed, 32);
1420
1421        let layer = &buf.layers()[0];
1422        // TLV[0]: PadN (type=4, length=2)
1423        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1424        let tlvs = array_entries(&buf, tlvs_field);
1425        let tlv0 = tlvs[0];
1426        assert_eq!(
1427            tlv_sub_field(&buf, tlv0, "type").unwrap().value,
1428            FieldValue::U8(4)
1429        );
1430        assert_eq!(
1431            tlv_sub_field(&buf, tlv0, "length").unwrap().value,
1432            FieldValue::U8(2)
1433        );
1434        assert_eq!(
1435            tlv_sub_field(&buf, tlv0, "padding").unwrap().value,
1436            FieldValue::Bytes(&[0, 0])
1437        );
1438    }
1439
1440    #[test]
1441    fn parse_srv6_with_offset() {
1442        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
1443        let mut buf = DissectBuffer::new();
1444        let result = Srv6Dissector::new().dissect(&data, &mut buf, 200).unwrap();
1445
1446        assert_eq!(result.bytes_consumed, 24);
1447        let layer = &buf.layers()[0];
1448        assert_eq!(layer.range, 200..224);
1449        assert_eq!(
1450            buf.field_by_name(layer, "next_header").unwrap().range,
1451            200..201
1452        );
1453        let segments = {
1454            let r = buf
1455                .field_by_name(layer, "segments")
1456                .unwrap()
1457                .value
1458                .as_container_range()
1459                .unwrap();
1460            buf.nested_fields(r)
1461        };
1462        assert_eq!(segments[0].range, 208..224);
1463    }
1464
1465    #[test]
1466    fn parse_srv6_truncated_header() {
1467        let data = [6, 2, 4, 1, 0, 0]; // only 6 bytes, need 8
1468        let mut buf = DissectBuffer::new();
1469        let err = Srv6Dissector::new()
1470            .dissect(&data, &mut buf, 0)
1471            .unwrap_err();
1472        assert!(matches!(
1473            err,
1474            PacketError::Truncated {
1475                expected: 8,
1476                actual: 6
1477            }
1478        ));
1479    }
1480
1481    #[test]
1482    fn parse_srv6_truncated_total() {
1483        // hdr_ext_len=2 means total=24, but only provide 16 bytes
1484        let mut data = vec![6, 2, 4, 1, 0, 0, 0, 0]; // 8 bytes fixed header
1485        data.extend_from_slice(&[0u8; 8]); // only 8 more (total 16), need 24
1486        let mut buf = DissectBuffer::new();
1487        let err = Srv6Dissector::new()
1488            .dissect(&data, &mut buf, 0)
1489            .unwrap_err();
1490        assert!(matches!(
1491            err,
1492            PacketError::Truncated {
1493                expected: 24,
1494                actual: 16
1495            }
1496        ));
1497    }
1498
1499    #[test]
1500    fn parse_srv6_invalid_last_entry() {
1501        // hdr_ext_len=2 → max_last_entry = (2/2)-1 = 0
1502        // Set last_entry=1 → invalid
1503        let mut data = vec![0u8; 24];
1504        data[0] = 6; // next_header
1505        data[1] = 2; // hdr_ext_len
1506        data[2] = 4; // routing_type
1507        data[3] = 0; // segments_left
1508        data[4] = 1; // last_entry = 1 (invalid, max is 0)
1509        let mut buf = DissectBuffer::new();
1510        let err = Srv6Dissector::new()
1511            .dissect(&data, &mut buf, 0)
1512            .unwrap_err();
1513        assert!(matches!(err, PacketError::InvalidHeader(_)));
1514    }
1515
1516    #[test]
1517    fn parse_srv6_invalid_hdr_ext_len_small() {
1518        // RFC 8754, Section 4.3.1.1: max_last_entry = (Hdr Ext Len / 2) - 1.
1519        // For Hdr Ext Len = 0, max_last_entry = -1 in signed arithmetic,
1520        // meaning all Last Entry values are invalid (Last Entry 0 > -1).
1521        // Verify that any last_entry value is rejected when hdr_ext_len = 0.
1522        let mut data = vec![0u8; 8];
1523        data[0] = 6; // next_header
1524        data[1] = 0; // hdr_ext_len = 0 → max_segs = 0 → all last_entry invalid
1525        data[2] = 4; // routing_type
1526        data[3] = 0; // segments_left
1527        data[4] = 0; // last_entry = 0 (invalid: 0 >= max_segs 0)
1528        let mut buf = DissectBuffer::new();
1529        let err = Srv6Dissector::new()
1530            .dissect(&data, &mut buf, 0)
1531            .unwrap_err();
1532        assert!(matches!(err, PacketError::InvalidHeader(_)));
1533
1534        // Same for hdr_ext_len = 1 (total = 16 bytes, still no room for a segment)
1535        let mut data2 = vec![0u8; 16];
1536        data2[0] = 6;
1537        data2[1] = 1; // hdr_ext_len = 1 → max_segs = 0 → all last_entry invalid
1538        data2[2] = 4;
1539        data2[3] = 0;
1540        data2[4] = 0; // last_entry = 0 (invalid)
1541        let mut buf2 = DissectBuffer::new();
1542        let err2 = Srv6Dissector::new()
1543            .dissect(&data2, &mut buf2, 0)
1544            .unwrap_err();
1545        assert!(matches!(err2, PacketError::InvalidHeader(_)));
1546    }
1547
1548    #[test]
1549    fn parse_srv6_invalid_segments_left() {
1550        // 1 segment (last_entry=0), but segments_left=2 → invalid
1551        let mut data = vec![0u8; 24];
1552        data[0] = 6;
1553        data[1] = 2;
1554        data[2] = 4;
1555        data[3] = 2; // segments_left = 2 (invalid, max is last_entry+1 = 1)
1556        data[4] = 0; // last_entry = 0
1557        let mut buf = DissectBuffer::new();
1558        let err = Srv6Dissector::new()
1559            .dissect(&data, &mut buf, 0)
1560            .unwrap_err();
1561        assert!(matches!(err, PacketError::InvalidHeader(_)));
1562    }
1563
1564    #[test]
1565    fn parse_srv6_next_header_tcp() {
1566        // Verify chaining: next_header=6 (TCP) propagates
1567        let data = build_srh(6, 0, &[SID_A], 0, 0, &[]);
1568        let mut buf = DissectBuffer::new();
1569        let result = Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1570        assert_eq!(result.next, DispatchHint::ByIpProtocol(6));
1571    }
1572
1573    #[test]
1574    fn parse_srv6_next_header_no_next() {
1575        // next_header=59 (No Next Header)
1576        let data = build_srh(59, 0, &[SID_A], 0, 0, &[]);
1577        let mut buf = DissectBuffer::new();
1578        let result = Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1579        assert_eq!(result.next, DispatchHint::ByIpProtocol(59));
1580    }
1581
1582    // --- TLV parsing tests (RFC 8754, Section 2.1) ---
1583
1584    /// Look up a sub-field within a TLV entry's Object value.
1585    fn tlv_sub_field<'a, 'pkt>(
1586        buf: &'a DissectBuffer<'pkt>,
1587        tlv: &Field<'pkt>,
1588        name: &str,
1589    ) -> Option<&'a Field<'pkt>> {
1590        let range = tlv.value.as_container_range()?;
1591        buf.nested_fields(range).iter().find(|f| f.name() == name)
1592    }
1593
1594    #[test]
1595    fn parse_srv6_tlv_pad1() {
1596        // Pad1 (type=0) is a single byte with no Length/Value.
1597        // We need 8 bytes of TLV area for alignment: Pad1 + PadN(5) to fill.
1598        // Pad1=0x00, PadN: type=4, len=5, 5 zero bytes => total 8 bytes TLV
1599        let tlvs = [0x00, 4, 5, 0, 0, 0, 0, 0];
1600        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1601        let mut buf = DissectBuffer::new();
1602        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1603
1604        let layer = &buf.layers()[0];
1605        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1606        let tlvs = array_entries(&buf, tlvs_field);
1607        assert!(tlvs.len() >= 2);
1608        // First TLV: Pad1
1609        assert_eq!(
1610            tlv_sub_field(&buf, tlvs[0], "type").unwrap().value,
1611            FieldValue::U8(0)
1612        );
1613        // Pad1 has no length field
1614        assert!(tlv_sub_field(&buf, tlvs[0], "length").is_none());
1615        // Second TLV: PadN
1616        assert_eq!(
1617            tlv_sub_field(&buf, tlvs[1], "type").unwrap().value,
1618            FieldValue::U8(4)
1619        );
1620        assert_eq!(
1621            tlv_sub_field(&buf, tlvs[1], "length").unwrap().value,
1622            FieldValue::U8(5)
1623        );
1624    }
1625
1626    #[test]
1627    fn parse_srv6_tlv_padn() {
1628        // PadN TLV: type=4, length=4, 4 zero bytes => 6 bytes
1629        // build_srh pads to 8-byte alignment, so TLV area = 8 bytes
1630        let tlvs = [4, 4, 0, 0, 0, 0];
1631        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1632        let mut buf = DissectBuffer::new();
1633        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1634
1635        let layer = &buf.layers()[0];
1636        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1637        let tlvs = array_entries(&buf, tlvs_field);
1638        let tlv0 = tlvs[0];
1639        assert_eq!(
1640            tlv_sub_field(&buf, tlv0, "type").unwrap().value,
1641            FieldValue::U8(4)
1642        );
1643        assert_eq!(
1644            tlv_sub_field(&buf, tlv0, "length").unwrap().value,
1645            FieldValue::U8(4)
1646        );
1647        assert_eq!(
1648            tlv_sub_field(&buf, tlv0, "padding").unwrap().value,
1649            FieldValue::Bytes(&[0, 0, 0, 0])
1650        );
1651    }
1652
1653    #[test]
1654    fn parse_srv6_tlv_hmac() {
1655        // HMAC TLV: type=5, length=38 (2 D+Reserved + 4 KeyID + 32 HMAC)
1656        // D-flag=1 (MSB of first value byte), Reserved=0, Key ID=0x00000001
1657        // Total TLV = 1+1+38 = 40 bytes; after 1 segment (24 bytes), total = 64
1658        let mut tlv_bytes = vec![5u8, 38]; // type, length
1659        // D-flag(1) + Reserved(15 bits) = 0x8000
1660        tlv_bytes.extend_from_slice(&[0x80, 0x00]);
1661        // HMAC Key ID = 1
1662        tlv_bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
1663        // 32-byte HMAC
1664        let hmac_value: Vec<u8> = (1..=32).collect();
1665        tlv_bytes.extend_from_slice(&hmac_value);
1666
1667        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlv_bytes);
1668        let mut buf = DissectBuffer::new();
1669        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1670
1671        let layer = &buf.layers()[0];
1672        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1673        let tlvs = array_entries(&buf, tlvs_field);
1674        let tlv0 = tlvs[0];
1675        assert_eq!(
1676            tlv_sub_field(&buf, tlv0, "type").unwrap().value,
1677            FieldValue::U8(5)
1678        );
1679        assert_eq!(
1680            tlv_sub_field(&buf, tlv0, "length").unwrap().value,
1681            FieldValue::U8(38)
1682        );
1683        assert_eq!(
1684            tlv_sub_field(&buf, tlv0, "hmac_d_flag").unwrap().value,
1685            FieldValue::U8(1)
1686        );
1687        assert_eq!(
1688            tlv_sub_field(&buf, tlv0, "hmac_key_id").unwrap().value,
1689            FieldValue::U32(1)
1690        );
1691        assert_eq!(
1692            tlv_sub_field(&buf, tlv0, "hmac").unwrap().value,
1693            FieldValue::Bytes(&hmac_value)
1694        );
1695    }
1696
1697    #[test]
1698    fn parse_srv6_tlv_hmac_d_flag_clear() {
1699        // Same as above but D-flag=0
1700        let mut tlv_bytes = vec![5u8, 38];
1701        // D-flag(0) + Reserved(15 bits) = 0x0000
1702        tlv_bytes.extend_from_slice(&[0x00, 0x00]);
1703        // HMAC Key ID = 0x12345678
1704        tlv_bytes.extend_from_slice(&[0x12, 0x34, 0x56, 0x78]);
1705        // 32-byte HMAC (all 0xFF)
1706        tlv_bytes.extend_from_slice(&[0xFF; 32]);
1707
1708        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlv_bytes);
1709        let mut buf = DissectBuffer::new();
1710        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1711
1712        let layer = &buf.layers()[0];
1713        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1714        let tlvs = array_entries(&buf, tlvs_field);
1715        let tlv0 = tlvs[0];
1716        assert_eq!(
1717            tlv_sub_field(&buf, tlv0, "hmac_d_flag").unwrap().value,
1718            FieldValue::U8(0)
1719        );
1720        assert_eq!(
1721            tlv_sub_field(&buf, tlv0, "hmac_key_id").unwrap().value,
1722            FieldValue::U32(0x12345678)
1723        );
1724    }
1725
1726    #[test]
1727    fn parse_srv6_tlv_unknown() {
1728        // Unknown TLV type=99, length=3, value=[0xAA, 0xBB, 0xCC]
1729        // Total TLV = 1+1+3 = 5 bytes; padded to 8
1730        let tlvs = [99, 3, 0xAA, 0xBB, 0xCC];
1731        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1732        let mut buf = DissectBuffer::new();
1733        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1734
1735        let layer = &buf.layers()[0];
1736        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1737        let tlvs = array_entries(&buf, tlvs_field);
1738        let tlv0 = tlvs[0];
1739        assert_eq!(
1740            tlv_sub_field(&buf, tlv0, "type").unwrap().value,
1741            FieldValue::U8(99)
1742        );
1743        assert_eq!(
1744            tlv_sub_field(&buf, tlv0, "length").unwrap().value,
1745            FieldValue::U8(3)
1746        );
1747        assert_eq!(
1748            tlv_sub_field(&buf, tlv0, "value").unwrap().value,
1749            FieldValue::Bytes(&[0xAA, 0xBB, 0xCC])
1750        );
1751    }
1752
1753    #[test]
1754    fn parse_srv6_tlv_multiple() {
1755        // Pad1(1 byte) + PadN(type=4, len=1, 1 zero byte = 3 bytes)
1756        // + Pad1(1 byte) + trailing zeros parsed as Pad1s
1757        // Total TLV area: 8 bytes
1758        let tlvs = [0x00, 4, 1, 0x00, 0x00];
1759        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1760        let mut buf = DissectBuffer::new();
1761        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1762
1763        let layer = &buf.layers()[0];
1764        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1765        let tlvs = array_entries(&buf, tlvs_field);
1766        assert!(tlvs.len() >= 2);
1767        // TLV[0]: Pad1
1768        assert_eq!(
1769            tlv_sub_field(&buf, tlvs[0], "type").unwrap().value,
1770            FieldValue::U8(0)
1771        );
1772        // TLV[1]: PadN
1773        assert_eq!(
1774            tlv_sub_field(&buf, tlvs[1], "type").unwrap().value,
1775            FieldValue::U8(4)
1776        );
1777        assert_eq!(
1778            tlv_sub_field(&buf, tlvs[1], "length").unwrap().value,
1779            FieldValue::U8(1)
1780        );
1781    }
1782
1783    #[test]
1784    fn parse_srv6_tlv_truncated_length() {
1785        // TLV area has only 1 byte (non-Pad1 type with no length byte).
1786        // We need to build this manually: 1 segment + 1 byte TLV area.
1787        // hdr_ext_len must produce total_len that leaves exactly 1 TLV byte.
1788        // 1 segment = 24 bytes header+seg. We need total=32 (hdr_ext_len=3)
1789        // with TLV area = 8 bytes, but place a non-Pad1 type at the very end.
1790        // Build: 7 Pad1 bytes (0x00) + 1 non-Pad1 type byte (e.g., 99)
1791        let tlvs = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99];
1792        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1793        let mut buf = DissectBuffer::new();
1794        let err = Srv6Dissector::new()
1795            .dissect(&data, &mut buf, 0)
1796            .unwrap_err();
1797        assert!(matches!(err, PacketError::Truncated { .. }));
1798    }
1799
1800    #[test]
1801    fn parse_srv6_tlv_truncated_value() {
1802        // TLV with type=99, length=20 but only 6 bytes remaining
1803        let tlvs = [99, 20, 0xAA, 0xBB, 0xCC, 0xDD];
1804        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1805        let mut buf = DissectBuffer::new();
1806        let err = Srv6Dissector::new()
1807            .dissect(&data, &mut buf, 0)
1808            .unwrap_err();
1809        assert!(matches!(err, PacketError::Truncated { .. }));
1810    }
1811
1812    #[test]
1813    fn parse_srv6_tlv_hmac_truncated() {
1814        // HMAC TLV with length < 6 (minimum: 2 D+Reserved + 4 Key ID)
1815        // Falls back to raw bytes via unknown path (Postel's law)
1816        // type=5, length=4, value=4 bytes
1817        let tlvs = [5, 4, 0x80, 0x00, 0x00, 0x01];
1818        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
1819        let mut buf = DissectBuffer::new();
1820        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1821
1822        let layer = &buf.layers()[0];
1823        let tlvs_field = buf.field_by_name(layer, "tlvs").unwrap();
1824        let tlvs = array_entries(&buf, tlvs_field);
1825        let tlv0 = tlvs[0];
1826        assert_eq!(
1827            tlv_sub_field(&buf, tlv0, "type").unwrap().value,
1828            FieldValue::U8(5)
1829        );
1830        assert_eq!(
1831            tlv_sub_field(&buf, tlv0, "length").unwrap().value,
1832            FieldValue::U8(4)
1833        );
1834        // Falls back to raw value since length < 6
1835        assert_eq!(
1836            tlv_sub_field(&buf, tlv0, "value").unwrap().value,
1837            FieldValue::Bytes(&[0x80, 0x00, 0x00, 0x01])
1838        );
1839        // HMAC-specific fields should not be present
1840        assert!(tlv_sub_field(&buf, tlv0, "hmac_d_flag").is_none());
1841    }
1842
1843    // --- SID structure analysis tests (RFC 8986, Section 3.1) ---
1844
1845    // # RFC 8986 (SRv6 Network Programming) Coverage
1846    //
1847    // | RFC Section  | Description              | Test                                  |
1848    // |--------------|--------------------------|---------------------------------------|
1849    // | 3.1          | SID structure            | parse_srv6_sid_structure_48_16_16_48   |
1850    // | 3.1          | No SID structure config  | parse_srv6_sid_structure_none          |
1851    //
1852    // # RFC 9800 (CSID/REPLACE-CSID) Coverage
1853    //
1854    // | RFC Section  | Description              | Test                                  |
1855    // |--------------|--------------------------|---------------------------------------|
1856    // | 3            | NEXT-CSID (uSID) decomp  | parse_srv6_usid_next_csid             |
1857    // | 4            | REPLACE-CSID decomp      | parse_srv6_csid_replace_csid          |
1858    // | —            | CSID disabled            | parse_srv6_csid_disabled              |
1859
1860    #[test]
1861    fn parse_srv6_sid_structure_none() {
1862        // Without SidStructure config, no segments_structure field should appear
1863        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
1864        let mut buf = DissectBuffer::new();
1865        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
1866
1867        let layer = &buf.layers()[0];
1868        assert!(buf.field_by_name(layer, "segments_structure").is_none());
1869    }
1870
1871    #[test]
1872    fn parse_srv6_sid_structure_48_16_16_48() {
1873        // SID_A = 2001:0db8:0001:0000:0000:0000:0000:0001
1874        // With B=48, N=16, F=16, A=48:
1875        //   Locator-Block (48 bits) = 2001:0db8:0001 → [0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]
1876        //   Locator-Node  (16 bits) = 0000 → [0x00, 0x00]
1877        //   Function      (16 bits) = 0000 → [0x00, 0x00]
1878        //   Argument      (48 bits) = 0000:0000:0001 → [0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
1879        let ss = SidStructure {
1880            locator_block_bits: 48,
1881            locator_node_bits: 16,
1882            function_bits: 16,
1883            argument_bits: 48,
1884            csid_flavor: CsidFlavor::Classic,
1885            mobile_encoding: None,
1886        };
1887        let dissector = Srv6Dissector::with_sid_structure(ss);
1888        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
1889        let mut buf = DissectBuffer::new();
1890        dissector.dissect(&data, &mut buf, 0).unwrap();
1891
1892        let layer = &buf.layers()[0];
1893        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
1894        let structure = array_entries(&buf, structure_field);
1895        assert_eq!(structure.len(), 1);
1896
1897        let seg0 = structure[0];
1898        assert_eq!(
1899            resolve_scratch(
1900                &buf,
1901                nested_field_by_name(&buf, seg0, "locator_block").unwrap()
1902            ),
1903            &[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]
1904        );
1905        assert_eq!(
1906            resolve_scratch(
1907                &buf,
1908                nested_field_by_name(&buf, seg0, "locator_node").unwrap()
1909            ),
1910            &[0x00, 0x00]
1911        );
1912        assert_eq!(
1913            resolve_scratch(&buf, nested_field_by_name(&buf, seg0, "function").unwrap()),
1914            &[0x00, 0x00]
1915        );
1916        assert_eq!(
1917            resolve_scratch(&buf, nested_field_by_name(&buf, seg0, "argument").unwrap()),
1918            &[0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
1919        );
1920    }
1921
1922    // --- CSID tests (RFC 9800) ---
1923
1924    #[test]
1925    fn parse_srv6_csid_disabled() {
1926        // csid_bits=0 means no CSID decomposition
1927        let ss = SidStructure {
1928            locator_block_bits: 48,
1929            locator_node_bits: 16,
1930            function_bits: 16,
1931            argument_bits: 48,
1932            csid_flavor: CsidFlavor::Classic,
1933            mobile_encoding: None,
1934        };
1935        let dissector = Srv6Dissector::with_sid_structure(ss);
1936        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
1937        let mut buf = DissectBuffer::new();
1938        dissector.dissect(&data, &mut buf, 0).unwrap();
1939
1940        let layer = &buf.layers()[0];
1941        // segments_structure should exist (SID structure enabled)
1942        assert!(buf.field_by_name(layer, "segments_structure").is_some());
1943        // csid_containers should NOT exist
1944        assert!(buf.field_by_name(layer, "csid_containers").is_none());
1945    }
1946
1947    #[test]
1948    fn parse_srv6_csid_replace_csid() {
1949        // REPLACE-CSID with B=32, N=16, F=16, A=64, CSID=32 (N+F=32)
1950        // K = 128 / 32 = 4 slots per container
1951        //
1952        // Container: 0xAAAA BBBB CCCC DDDD 0000 0000 0000 0000
1953        // (16 bytes: AA AA BB BB CC CC DD DD 00 00 00 00 00 00 00 00)
1954        // Slot layout (32 bits each):
1955        //   Slot 0 (bits 0..31)  = 0xAAAABBBB
1956        //   Slot 1 (bits 32..63) = 0xCCCCDDDD
1957        //   Slot 2 (bits 64..95) = 0x00000000
1958        //   Slot 3 (bits 96..127)= 0x00000000
1959        let container: [u8; 16] = [
1960            0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, 0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1961            0x00, 0x00,
1962        ];
1963        let ss = SidStructure {
1964            locator_block_bits: 32,
1965            locator_node_bits: 16,
1966            function_bits: 16,
1967            argument_bits: 64,
1968            csid_flavor: CsidFlavor::ReplaceCsid { csid_bits: 32 },
1969            mobile_encoding: None,
1970        };
1971        let dissector = Srv6Dissector::with_sid_structure(ss);
1972        let data = build_srh(6, 1, &[container], 0, 0, &[]);
1973        let mut buf = DissectBuffer::new();
1974        dissector.dissect(&data, &mut buf, 0).unwrap();
1975
1976        let layer = &buf.layers()[0];
1977        let containers_field = buf.field_by_name(layer, "csid_containers").unwrap();
1978        let containers = array_entries(&buf, containers_field);
1979        assert_eq!(containers.len(), 1);
1980
1981        let c0 = containers[0];
1982        assert_eq!(
1983            nested_field_by_name(&buf, c0, "container_index")
1984                .unwrap()
1985                .value,
1986            FieldValue::U8(0)
1987        );
1988        let csids_field = nested_field_by_name(&buf, c0, "csids").unwrap();
1989        let csids = buf.nested_fields(csids_field.value.as_container_range().unwrap());
1990        assert_eq!(csids.len(), 4);
1991        // Slot 0: bits 0..31 = 0xAAAABBBB
1992        assert_eq!(resolve_scratch(&buf, &csids[0]), &[0xAA, 0xAA, 0xBB, 0xBB]);
1993        // Slot 1: bits 32..63 = 0xCCCCDDDD
1994        assert_eq!(resolve_scratch(&buf, &csids[1]), &[0xCC, 0xCC, 0xDD, 0xDD]);
1995        // Slot 2: bits 64..95 = 0x00000000
1996        assert_eq!(resolve_scratch(&buf, &csids[2]), &[0x00, 0x00, 0x00, 0x00]);
1997        // Slot 3: bits 96..127 = 0x00000000
1998        assert_eq!(resolve_scratch(&buf, &csids[3]), &[0x00, 0x00, 0x00, 0x00]);
1999    }
2000
2001    #[test]
2002    fn parse_srv6_usid_next_csid() {
2003        // NEXT-CSID (uSID) with LBL=32, N=16, F=16, A=64
2004        // LNFL = 16 + 16 = 32, K = (128 - 32) / 32 = 3 uSID slots
2005        //
2006        // Container: 0xAAAAAAAA BBBB1111 CCCC2222 DDDD3333
2007        // (Locator-Block: 0xAAAAAAAA = first 32 bits, not a uSID slot)
2008        // uSID slots (each 32 bits, starting after LBL at bit 32):
2009        //   Slot 0 (bits 32..63)  = 0xBBBB1111
2010        //   Slot 1 (bits 64..95)  = 0xCCCC2222
2011        //   Slot 2 (bits 96..127) = 0xDDDD3333
2012        let container: [u8; 16] = [
2013            0xAA, 0xAA, 0xAA, 0xAA, // Locator-Block
2014            0xBB, 0xBB, 0x11, 0x11, // uSID slot 0
2015            0xCC, 0xCC, 0x22, 0x22, // uSID slot 1
2016            0xDD, 0xDD, 0x33, 0x33, // uSID slot 2
2017        ];
2018        let ss = SidStructure {
2019            locator_block_bits: 32,
2020            locator_node_bits: 16,
2021            function_bits: 16,
2022            argument_bits: 64,
2023            csid_flavor: CsidFlavor::NextCsid { usid_bits: 32 },
2024            mobile_encoding: None,
2025        };
2026        let dissector = Srv6Dissector::with_sid_structure(ss);
2027        let data = build_srh(6, 1, &[container], 0, 0, &[]);
2028        let mut buf = DissectBuffer::new();
2029        dissector.dissect(&data, &mut buf, 0).unwrap();
2030
2031        let layer = &buf.layers()[0];
2032        let containers_field = buf.field_by_name(layer, "csid_containers").unwrap();
2033        let containers = array_entries(&buf, containers_field);
2034        assert_eq!(containers.len(), 1);
2035
2036        let c0 = containers[0];
2037        let csids_field = nested_field_by_name(&buf, c0, "csids").unwrap();
2038        let csids = buf.nested_fields(csids_field.value.as_container_range().unwrap());
2039        // 3 uSID slots (after Locator-Block)
2040        assert_eq!(csids.len(), 3);
2041        // Slot 0: bits 32..63 = 0xBBBB1111
2042        assert_eq!(resolve_scratch(&buf, &csids[0]), &[0xBB, 0xBB, 0x11, 0x11]);
2043        // Slot 1: bits 64..95 = 0xCCCC2222
2044        assert_eq!(resolve_scratch(&buf, &csids[1]), &[0xCC, 0xCC, 0x22, 0x22]);
2045        // Slot 2: bits 96..127 = 0xDDDD3333
2046        assert_eq!(resolve_scratch(&buf, &csids[2]), &[0xDD, 0xDD, 0x33, 0x33]);
2047    }
2048
2049    // # RFC 9433 (SRv6 Mobile User Plane) Coverage
2050    //
2051    // | RFC Section  | Description                    | Test                                         |
2052    // |--------------|--------------------------------|----------------------------------------------|
2053    // | 6.1 Fig.8    | Args.Mob.Session basic         | parse_srv6_args_mob_session_basic             |
2054    // | 6.1 Fig.8    | Args.Mob.Session max QFI       | parse_srv6_args_mob_session_max_qfi           |
2055    // | 6.1 Fig.8    | Args.Mob.Session R flag set    | parse_srv6_args_mob_session_r_flag            |
2056    // | 6.1 Fig.8    | Args.Mob.Session too short     | parse_srv6_args_mob_session_too_short         |
2057    // | Fig.9        | End.M.GTP4.E SID encoding      | parse_srv6_mobile_end_m_gtp4_e                |
2058    // | 6.5          | End.M.GTP6.E SID encoding      | parse_srv6_mobile_end_m_gtp6_e                |
2059    // | Fig.11       | H.M.GTP4.D SID encoding        | parse_srv6_mobile_h_m_gtp4_d                  |
2060    // | Fig.12       | End.Limit SID encoding         | parse_srv6_mobile_end_limit                   |
2061    // | 11           | Endpoint behavior names        | endpoint_behavior_names                       |
2062    // | —            | No mobile encoding (regression)| parse_srv6_mobile_encoding_none                |
2063
2064    #[test]
2065    fn endpoint_behavior_names() {
2066        assert_eq!(endpoint_behavior_name(BEHAVIOR_END_MAP), Some("End.MAP"));
2067        assert_eq!(
2068            endpoint_behavior_name(BEHAVIOR_END_LIMIT),
2069            Some("End.Limit")
2070        );
2071        assert_eq!(
2072            endpoint_behavior_name(BEHAVIOR_END_M_GTP6_D),
2073            Some("End.M.GTP6.D")
2074        );
2075        assert_eq!(
2076            endpoint_behavior_name(BEHAVIOR_END_M_GTP6_DI),
2077            Some("End.M.GTP6.Di")
2078        );
2079        assert_eq!(
2080            endpoint_behavior_name(BEHAVIOR_END_M_GTP6_E),
2081            Some("End.M.GTP6.E")
2082        );
2083        assert_eq!(
2084            endpoint_behavior_name(BEHAVIOR_END_M_GTP4_E),
2085            Some("End.M.GTP4.E")
2086        );
2087        assert_eq!(endpoint_behavior_name(0), None);
2088        assert_eq!(endpoint_behavior_name(9999), None);
2089    }
2090
2091    #[test]
2092    fn parse_srv6_args_mob_session_basic() {
2093        // End.M.GTP6.E: LOC(48) + Node(16) + Func(16) + Arg(48)
2094        // Argument carries Args.Mob.Session (40 bits) + 8 bits padding.
2095        //
2096        // Args.Mob.Session: QFI=9 (0b001001), R=0, U=0, PDU Session ID=0x12345678
2097        // Binary: 001001_0_0 = 0x24  then 0x12 0x34 0x56 0x78  then 0x00 padding
2098        //
2099        // SID layout (16 bytes):
2100        //   Bytes 0-5:  LOC  = 2001:0db8:0001
2101        //   Bytes 6-7:  Node = 0002
2102        //   Bytes 8-9:  Func = 0047 (End.M.GTP6.E IANA value, but opaque here)
2103        //   Bytes 10-14: Arg  = 0x24 0x12 0x34 0x56 0x78  (Args.Mob.Session)
2104        //   Byte 15:     Arg  = 0x00 (padding)
2105        let sid: [u8; 16] = [
2106            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, // LOC (48 bits)
2107            0x00, 0x02, // Node (16 bits)
2108            0x00, 0x47, // Func (16 bits)
2109            0x24, 0x12, 0x34, 0x56, 0x78, // Args.Mob.Session (40 bits)
2110            0x00, // padding (8 bits)
2111        ];
2112        let ss = SidStructure {
2113            locator_block_bits: 48,
2114            locator_node_bits: 16,
2115            function_bits: 16,
2116            argument_bits: 48,
2117            csid_flavor: CsidFlavor::Classic,
2118            mobile_encoding: Some(MobileSidEncoding::EndMGtp6E),
2119        };
2120        let dissector = Srv6Dissector::with_sid_structure(ss);
2121        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2122        let mut buf = DissectBuffer::new();
2123        dissector.dissect(&data, &mut buf, 0).unwrap();
2124
2125        let layer = &buf.layers()[0];
2126        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2127        let structure = array_entries(&buf, structure_field);
2128        assert_eq!(structure.len(), 1);
2129
2130        let seg0 = structure[0];
2131        // Verify standard fields exist
2132        assert!(nested_field_by_name(&buf, seg0, "locator_block").is_some());
2133        assert!(nested_field_by_name(&buf, seg0, "function").is_some());
2134
2135        // Verify Args.Mob.Session
2136        let ams = nested_field_by_name(&buf, seg0, "args_mob_session").unwrap();
2137        let ams_obj = buf.nested_fields(ams.value.as_container_range().unwrap());
2138        let qfi = ams_obj.iter().find(|f| f.name() == "qfi").unwrap();
2139        assert_eq!(qfi.value, FieldValue::U8(9));
2140        let r_flag = ams_obj.iter().find(|f| f.name() == "r_flag").unwrap();
2141        assert_eq!(r_flag.value, FieldValue::U8(0));
2142        let u_flag = ams_obj.iter().find(|f| f.name() == "u_flag").unwrap();
2143        assert_eq!(u_flag.value, FieldValue::U8(0));
2144        let pdu_id = ams_obj
2145            .iter()
2146            .find(|f| f.name() == "pdu_session_id")
2147            .unwrap();
2148        assert_eq!(pdu_id.value, FieldValue::U32(0x12345678));
2149    }
2150
2151    #[test]
2152    fn parse_srv6_args_mob_session_max_qfi() {
2153        // QFI max value = 63 (0b111111), R=0, U=0, PDU Session ID=0x00000001
2154        // Binary: 111111_0_0 = 0xFC  then 0x00 0x00 0x00 0x01
2155        let sid: [u8; 16] = [
2156            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, // LOC
2157            0x00, 0x02, // Node
2158            0x00, 0x47, // Func
2159            0xFC, 0x00, 0x00, 0x00, 0x01, // Args.Mob.Session
2160            0x00, // padding
2161        ];
2162        let ss = SidStructure {
2163            locator_block_bits: 48,
2164            locator_node_bits: 16,
2165            function_bits: 16,
2166            argument_bits: 48,
2167            csid_flavor: CsidFlavor::Classic,
2168            mobile_encoding: Some(MobileSidEncoding::EndMGtp6E),
2169        };
2170        let dissector = Srv6Dissector::with_sid_structure(ss);
2171        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2172        let mut buf = DissectBuffer::new();
2173        dissector.dissect(&data, &mut buf, 0).unwrap();
2174
2175        let layer = &buf.layers()[0];
2176        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2177        let structure = array_entries(&buf, structure_field);
2178        let seg0 = structure[0];
2179        let ams = nested_field_by_name(&buf, seg0, "args_mob_session").unwrap();
2180        let ams_obj = buf.nested_fields(ams.value.as_container_range().unwrap());
2181        let qfi = ams_obj.iter().find(|f| f.name() == "qfi").unwrap();
2182        assert_eq!(qfi.value, FieldValue::U8(63));
2183    }
2184
2185    #[test]
2186    fn parse_srv6_args_mob_session_r_flag() {
2187        // QFI=0, R=1, U=0, PDU Session ID=0xAABBCCDD
2188        // Binary: 000000_1_0 = 0x02  then 0xAA 0xBB 0xCC 0xDD
2189        let sid: [u8; 16] = [
2190            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, // LOC
2191            0x00, 0x02, // Node
2192            0x00, 0x47, // Func
2193            0x02, 0xAA, 0xBB, 0xCC, 0xDD, // Args.Mob.Session
2194            0x00, // padding
2195        ];
2196        let ss = SidStructure {
2197            locator_block_bits: 48,
2198            locator_node_bits: 16,
2199            function_bits: 16,
2200            argument_bits: 48,
2201            csid_flavor: CsidFlavor::Classic,
2202            mobile_encoding: Some(MobileSidEncoding::EndMGtp6E),
2203        };
2204        let dissector = Srv6Dissector::with_sid_structure(ss);
2205        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2206        let mut buf = DissectBuffer::new();
2207        dissector.dissect(&data, &mut buf, 0).unwrap();
2208
2209        let layer = &buf.layers()[0];
2210        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2211        let structure = array_entries(&buf, structure_field);
2212        let seg0 = structure[0];
2213        let ams = nested_field_by_name(&buf, seg0, "args_mob_session").unwrap();
2214        let ams_obj = buf.nested_fields(ams.value.as_container_range().unwrap());
2215        let qfi = ams_obj.iter().find(|f| f.name() == "qfi").unwrap();
2216        assert_eq!(qfi.value, FieldValue::U8(0));
2217        let r_flag = ams_obj.iter().find(|f| f.name() == "r_flag").unwrap();
2218        assert_eq!(r_flag.value, FieldValue::U8(1));
2219        let u_flag = ams_obj.iter().find(|f| f.name() == "u_flag").unwrap();
2220        assert_eq!(u_flag.value, FieldValue::U8(0));
2221        let pdu_id = ams_obj
2222            .iter()
2223            .find(|f| f.name() == "pdu_session_id")
2224            .unwrap();
2225        assert_eq!(pdu_id.value, FieldValue::U32(0xAABBCCDD));
2226    }
2227
2228    #[test]
2229    fn parse_srv6_args_mob_session_too_short() {
2230        // Argument is only 32 bits — too short for Args.Mob.Session (40 bits).
2231        // Mobile fields should be absent (graceful degradation per Postel's law).
2232        // LOC(48) + Node(16) + Func(32) + Arg(32) = 128
2233        let sid: [u8; 16] = [
2234            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, // LOC (48)
2235            0x00, 0x02, // Node (16)
2236            0x00, 0x47, 0x00, 0x01, // Func (32)
2237            0x24, 0x12, 0x34, 0x56, // Arg (32) — too short for AMS
2238        ];
2239        let ss = SidStructure {
2240            locator_block_bits: 48,
2241            locator_node_bits: 16,
2242            function_bits: 32,
2243            argument_bits: 32,
2244            csid_flavor: CsidFlavor::Classic,
2245            mobile_encoding: Some(MobileSidEncoding::EndMGtp6E),
2246        };
2247        let dissector = Srv6Dissector::with_sid_structure(ss);
2248        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2249        let mut buf = DissectBuffer::new();
2250        dissector.dissect(&data, &mut buf, 0).unwrap();
2251
2252        let layer = &buf.layers()[0];
2253        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2254        let structure = array_entries(&buf, structure_field);
2255        let seg0 = structure[0];
2256        // args_mob_session should NOT be present (too short)
2257        assert!(nested_field_by_name(&buf, seg0, "args_mob_session").is_none());
2258    }
2259
2260    #[test]
2261    fn parse_srv6_mobile_end_m_gtp4_e() {
2262        // End.M.GTP4.E (RFC 9433, Figure 9):
2263        // LOC-FUNC(48) | IPv4DA(32) | Args.Mob.Session(40) | 0-padded(8)
2264        //
2265        // IPv4DA = 10.0.0.1 (0x0A000001)
2266        // Args.Mob.Session: QFI=5 (0b000101), R=0, U=0, PDU Session ID=0x00000042
2267        // AMS binary: 000101_0_0 = 0x14  then 0x00 0x00 0x00 0x42
2268        let sid: [u8; 16] = [
2269            0x20, 0x01, 0x0D, 0xB8, // LOC-FUNC bytes 0-3
2270            0x00, 0x01, // LOC-FUNC bytes 4-5  (48 bits total)
2271            0x0A, 0x00, 0x00, 0x01, // IPv4DA = 10.0.0.1 (32 bits)
2272            0x14, 0x00, 0x00, 0x00, 0x42, // Args.Mob.Session (40 bits)
2273            0x00, // padding (8 bits)
2274        ];
2275        let ss = SidStructure {
2276            locator_block_bits: 32,
2277            locator_node_bits: 0,
2278            function_bits: 16,
2279            argument_bits: 80,
2280            csid_flavor: CsidFlavor::Classic,
2281            mobile_encoding: Some(MobileSidEncoding::EndMGtp4E {
2282                ipv4da_bits: 32,
2283                args_mob_session_bits: 40,
2284            }),
2285        };
2286        let dissector = Srv6Dissector::with_sid_structure(ss);
2287        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2288        let mut buf = DissectBuffer::new();
2289        dissector.dissect(&data, &mut buf, 0).unwrap();
2290
2291        let layer = &buf.layers()[0];
2292        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2293        let structure = array_entries(&buf, structure_field);
2294        let seg0 = structure[0];
2295
2296        // Verify embedded IPv4
2297        let ipv4 = nested_field_by_name(&buf, seg0, "embedded_ipv4").unwrap();
2298        assert_eq!(ipv4.value, FieldValue::Ipv4Addr([10, 0, 0, 1]));
2299
2300        // Verify Args.Mob.Session
2301        let ams = nested_field_by_name(&buf, seg0, "args_mob_session").unwrap();
2302        let ams_obj = buf.nested_fields(ams.value.as_container_range().unwrap());
2303        let qfi = ams_obj.iter().find(|f| f.name() == "qfi").unwrap();
2304        assert_eq!(qfi.value, FieldValue::U8(5));
2305        let pdu_id = ams_obj
2306            .iter()
2307            .find(|f| f.name() == "pdu_session_id")
2308            .unwrap();
2309        assert_eq!(pdu_id.value, FieldValue::U32(0x00000042));
2310    }
2311
2312    #[test]
2313    fn parse_srv6_mobile_end_m_gtp6_e() {
2314        // End.M.GTP6.E: Same as parse_srv6_args_mob_session_basic (already tested)
2315        // Here we verify that the standard locator/function/argument fields coexist
2316        // with mobile fields in the same structure entry.
2317        let sid: [u8; 16] = [
2318            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, // LOC (48)
2319            0x00, 0x02, // Node (16)
2320            0x00, 0x47, // Func (16)
2321            0x24, 0x12, 0x34, 0x56, 0x78, // AMS: QFI=9, R=0, U=0, PDU=0x12345678
2322            0x00, // padding
2323        ];
2324        let ss = SidStructure {
2325            locator_block_bits: 48,
2326            locator_node_bits: 16,
2327            function_bits: 16,
2328            argument_bits: 48,
2329            csid_flavor: CsidFlavor::Classic,
2330            mobile_encoding: Some(MobileSidEncoding::EndMGtp6E),
2331        };
2332        let dissector = Srv6Dissector::with_sid_structure(ss);
2333        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2334        let mut buf = DissectBuffer::new();
2335        dissector.dissect(&data, &mut buf, 0).unwrap();
2336
2337        let layer = &buf.layers()[0];
2338        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2339        let structure = array_entries(&buf, structure_field);
2340        let seg0 = structure[0];
2341
2342        // All standard fields present
2343        assert!(nested_field_by_name(&buf, seg0, "locator_block").is_some());
2344        assert!(nested_field_by_name(&buf, seg0, "locator_node").is_some());
2345        assert!(nested_field_by_name(&buf, seg0, "function").is_some());
2346        assert!(nested_field_by_name(&buf, seg0, "argument").is_some());
2347
2348        // Mobile field also present
2349        assert!(nested_field_by_name(&buf, seg0, "args_mob_session").is_some());
2350
2351        // No embedded_ipv4 for GTP6.E
2352        assert!(nested_field_by_name(&buf, seg0, "embedded_ipv4").is_none());
2353    }
2354
2355    #[test]
2356    fn parse_srv6_mobile_h_m_gtp4_d() {
2357        // H.M.GTP4.D (RFC 9433, Figure 11):
2358        // Destination UPF Prefix(48) | IPv4DA(32) | Args.Mob.Session(40) | 0-padded(8)
2359        //
2360        // IPv4DA = 192.168.1.1 (0xC0A80101)
2361        // AMS: QFI=15 (0b001111), R=1, U=0, PDU Session ID=0xDEADBEEF
2362        // AMS binary: 001111_1_0 = 0x3E  then 0xDE 0xAD 0xBE 0xEF
2363        let sid: [u8; 16] = [
2364            0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, // UPF Prefix (48 bits)
2365            0xC0, 0xA8, 0x01, 0x01, // IPv4DA = 192.168.1.1 (32 bits)
2366            0x3E, 0xDE, 0xAD, 0xBE, 0xEF, // Args.Mob.Session (40 bits)
2367            0x00, // padding (8 bits)
2368        ];
2369        let ss = SidStructure {
2370            locator_block_bits: 48,
2371            locator_node_bits: 0,
2372            function_bits: 0,
2373            argument_bits: 80,
2374            csid_flavor: CsidFlavor::Classic,
2375            mobile_encoding: Some(MobileSidEncoding::HmGtp4D {
2376                prefix_bits: 48,
2377                ipv4da_bits: 32,
2378                args_mob_session_bits: 40,
2379            }),
2380        };
2381        let dissector = Srv6Dissector::with_sid_structure(ss);
2382        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2383        let mut buf = DissectBuffer::new();
2384        dissector.dissect(&data, &mut buf, 0).unwrap();
2385
2386        let layer = &buf.layers()[0];
2387        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2388        let structure = array_entries(&buf, structure_field);
2389        let seg0 = structure[0];
2390
2391        // Verify embedded IPv4
2392        let ipv4 = nested_field_by_name(&buf, seg0, "embedded_ipv4").unwrap();
2393        assert_eq!(ipv4.value, FieldValue::Ipv4Addr([192, 168, 1, 1]));
2394
2395        // Verify Args.Mob.Session
2396        let ams = nested_field_by_name(&buf, seg0, "args_mob_session").unwrap();
2397        let ams_obj = buf.nested_fields(ams.value.as_container_range().unwrap());
2398        let qfi = ams_obj.iter().find(|f| f.name() == "qfi").unwrap();
2399        assert_eq!(qfi.value, FieldValue::U8(15));
2400        let r_flag = ams_obj.iter().find(|f| f.name() == "r_flag").unwrap();
2401        assert_eq!(r_flag.value, FieldValue::U8(1));
2402        let pdu_id = ams_obj
2403            .iter()
2404            .find(|f| f.name() == "pdu_session_id")
2405            .unwrap();
2406        assert_eq!(pdu_id.value, FieldValue::U32(0xDEADBEEF));
2407    }
2408
2409    #[test]
2410    fn parse_srv6_mobile_end_limit() {
2411        // End.Limit (RFC 9433, Figure 12):
2412        // LOC+FUNC(rate-limit)(64) | group-id(32) | limit-rate(32)
2413        let sid: [u8; 16] = [
2414            0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x29, // LOC+FUNC (64 bits)
2415            0x00, 0x00, 0x00, 0x05, // group-id = 5 (32 bits)
2416            0x00, 0x01, 0x00, 0x00, // limit-rate = 65536 (32 bits)
2417        ];
2418        let ss = SidStructure {
2419            locator_block_bits: 48,
2420            locator_node_bits: 0,
2421            function_bits: 16,
2422            argument_bits: 64,
2423            csid_flavor: CsidFlavor::Classic,
2424            mobile_encoding: Some(MobileSidEncoding::EndLimit {
2425                group_id_bits: 32,
2426                limit_rate_bits: 32,
2427            }),
2428        };
2429        let dissector = Srv6Dissector::with_sid_structure(ss);
2430        let data = build_srh(6, 1, &[sid], 0, 0, &[]);
2431        let mut buf = DissectBuffer::new();
2432        dissector.dissect(&data, &mut buf, 0).unwrap();
2433
2434        let layer = &buf.layers()[0];
2435        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2436        let structure = array_entries(&buf, structure_field);
2437        let seg0 = structure[0];
2438
2439        // Verify group_id
2440        let gid = nested_field_by_name(&buf, seg0, "group_id").unwrap();
2441        assert_eq!(resolve_scratch(&buf, gid), &[0x00, 0x00, 0x00, 0x05]);
2442
2443        // Verify limit_rate
2444        let lr = nested_field_by_name(&buf, seg0, "limit_rate").unwrap();
2445        assert_eq!(resolve_scratch(&buf, lr), &[0x00, 0x01, 0x00, 0x00]);
2446
2447        // No args_mob_session or embedded_ipv4
2448        assert!(nested_field_by_name(&buf, seg0, "args_mob_session").is_none());
2449        assert!(nested_field_by_name(&buf, seg0, "embedded_ipv4").is_none());
2450    }
2451
2452    #[test]
2453    fn parse_srv6_mobile_encoding_none() {
2454        // Regression: SidStructure with mobile_encoding: None should produce
2455        // segments_structure with only the standard 4 fields (no mobile extras).
2456        let ss = SidStructure {
2457            locator_block_bits: 48,
2458            locator_node_bits: 16,
2459            function_bits: 16,
2460            argument_bits: 48,
2461            csid_flavor: CsidFlavor::Classic,
2462            mobile_encoding: None,
2463        };
2464        let dissector = Srv6Dissector::with_sid_structure(ss);
2465        let data = build_srh(6, 1, &[SID_A], 0, 0, &[]);
2466        let mut buf = DissectBuffer::new();
2467        dissector.dissect(&data, &mut buf, 0).unwrap();
2468
2469        let layer = &buf.layers()[0];
2470        let structure_field = buf.field_by_name(layer, "segments_structure").unwrap();
2471        let structure = array_entries(&buf, structure_field);
2472        let seg0 = structure[0];
2473        let obj = buf.nested_fields(seg0.value.as_container_range().unwrap());
2474        // Exactly 4 fields: locator_block, locator_node, function, argument
2475        assert_eq!(obj.len(), 4);
2476        assert!(nested_field_by_name(&buf, seg0, "args_mob_session").is_none());
2477        assert!(nested_field_by_name(&buf, seg0, "embedded_ipv4").is_none());
2478        assert!(nested_field_by_name(&buf, seg0, "group_id").is_none());
2479        assert!(nested_field_by_name(&buf, seg0, "limit_rate").is_none());
2480    }
2481
2482    fn call_format_fn(
2483        f: fn(&FieldValue<'_>, &FormatContext<'_>, &mut dyn std::io::Write) -> std::io::Result<()>,
2484        value: &FieldValue<'_>,
2485        scratch: &[u8],
2486    ) -> String {
2487        let ctx = FormatContext {
2488            packet_data: &[],
2489            scratch,
2490            layer_range: 0..0,
2491            field_range: 0..0,
2492        };
2493        let mut out = Vec::new();
2494        f(value, &ctx, &mut out).unwrap();
2495        String::from_utf8(out).unwrap()
2496    }
2497
2498    #[test]
2499    fn format_sid_hex_scratch() {
2500        let scratch = [0x20, 0x01, 0x0d, 0xb8];
2501        let val = FieldValue::Scratch(0..4);
2502        assert_eq!(
2503            call_format_fn(format_sid_hex, &val, &scratch),
2504            "\"20010db8\""
2505        );
2506    }
2507
2508    #[test]
2509    fn format_sid_hex_bytes() {
2510        let data = [0xab, 0xcd, 0xef];
2511        let val = FieldValue::Bytes(&data);
2512        assert_eq!(call_format_fn(format_sid_hex, &val, &[]), "\"abcdef\"");
2513    }
2514
2515    #[test]
2516    fn format_sid_hex_empty_scratch() {
2517        let val = FieldValue::Scratch(0..0);
2518        assert_eq!(call_format_fn(format_sid_hex, &val, &[]), "\"\"");
2519    }
2520
2521    #[test]
2522    fn format_sid_hex_empty_bytes() {
2523        let val = FieldValue::Bytes(&[]);
2524        assert_eq!(call_format_fn(format_sid_hex, &val, &[]), "\"\"");
2525    }
2526
2527    #[test]
2528    fn format_sid_hex_other_variant() {
2529        let val = FieldValue::U8(42);
2530        assert_eq!(call_format_fn(format_sid_hex, &val, &[]), "\"\"");
2531    }
2532
2533    #[test]
2534    fn tlv_container_resolves_to_tlv_name() {
2535        // PadN TLV so the container label resolves to "PadN" instead of
2536        // duplicating the inner "Type" label.
2537        let tlvs = [4, 4, 0, 0, 0, 0];
2538        let data = build_srh(6, 1, &[SID_A], 0, 0, &tlvs);
2539        let mut buf = DissectBuffer::new();
2540        Srv6Dissector::new().dissect(&data, &mut buf, 0).unwrap();
2541
2542        let (idx, field) = buf
2543            .fields()
2544            .iter()
2545            .enumerate()
2546            .find(|(_, f)| f.name() == "tlv")
2547            .expect("tlv container not found");
2548        assert!(matches!(field.value, FieldValue::Object(_)));
2549        assert_eq!(field.display_name(), "TLV");
2550        assert_eq!(buf.resolve_container_display_name(idx as u32), Some("PadN"));
2551    }
2552}