Skip to main content

packet_dissector_icmp/
lib.rs

1//! ICMP (Internet Control Message Protocol) dissector.
2//!
3//! ## References
4//! - RFC 792: <https://www.rfc-editor.org/rfc/rfc792>
5//! - RFC 950 (updates RFC 792 — Address Mask): <https://www.rfc-editor.org/rfc/rfc950>
6//! - RFC 1191 (Path MTU Discovery — updates Type 3 Code 4): <https://www.rfc-editor.org/rfc/rfc1191>
7//! - RFC 1256 (Router Discovery): <https://www.rfc-editor.org/rfc/rfc1256>
8//! - RFC 2521 (ICMP Security Failures / Photuris): <https://www.rfc-editor.org/rfc/rfc2521>
9//! - RFC 4065 (Seamoby Experimental Mobility): <https://www.rfc-editor.org/rfc/rfc4065>
10//! - RFC 4884 (Extended ICMP — adds Length at offset 5 for Types 3/11/12): <https://www.rfc-editor.org/rfc/rfc4884>
11//! - RFC 4950 (MPLS Label Stack — Extension Object Class 1): <https://www.rfc-editor.org/rfc/rfc4950>
12//! - RFC 5837 (Interface Information Object — Extension Object Class 2): <https://www.rfc-editor.org/rfc/rfc5837>
13//! - RFC 6633 (Deprecation of Source Quench): <https://www.rfc-editor.org/rfc/rfc6633>
14//! - RFC 6918 (Formally Deprecating Types 6, 15–18, 30–39): <https://www.rfc-editor.org/rfc/rfc6918>
15//! - RFC 8335 (PROBE — Extended Echo Request/Reply, Types 42/43; Interface Identification Class 3): <https://www.rfc-editor.org/rfc/rfc8335>
16
17#![deny(missing_docs)]
18
19use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
20use packet_dissector_core::error::PacketError;
21use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue};
22use packet_dissector_core::packet::DissectBuffer;
23use packet_dissector_core::util::{read_be_u16, read_be_u32};
24
25/// Returns a human-readable name for well-known ICMP type values.
26fn icmp_type_name(v: u8) -> Option<&'static str> {
27    match v {
28        0 => Some("Echo Reply"),
29        3 => Some("Destination Unreachable"),
30        5 => Some("Redirect"),
31        8 => Some("Echo Request"),
32        11 => Some("Time Exceeded"),
33        12 => Some("Parameter Problem"),
34        _ => None,
35    }
36}
37
38/// Minimum ICMP header size (Type + Code + Checksum + 4 bytes type-specific).
39const HEADER_SIZE: usize = 8;
40/// Minimum size for Timestamp/Timestamp Reply (Type 13/14): 20 bytes.
41const TIMESTAMP_SIZE: usize = 20;
42/// Minimum size for Address Mask Request/Reply (Type 17/18): 12 bytes.
43const ADDRESS_MASK_SIZE: usize = 12;
44/// Minimum IPv4 header size for invoking_packet sub-parsing.
45const IPV4_MIN_HEADER: usize = 20;
46
47const FD_TYPE: usize = 0;
48const FD_CODE: usize = 1;
49const FD_CHECKSUM: usize = 2;
50const FD_IDENTIFIER: usize = 3;
51const FD_SEQUENCE_NUMBER: usize = 4;
52const FD_DATA: usize = 5;
53const FD_GATEWAY: usize = 6;
54const FD_POINTER: usize = 7;
55const FD_LENGTH: usize = 8;
56const FD_NEXT_HOP_MTU: usize = 9;
57const FD_INVOKING_PACKET: usize = 10;
58const FD_NUM_ADDRS: usize = 11;
59const FD_ADDR_ENTRY_SIZE: usize = 12;
60const FD_LIFETIME: usize = 13;
61const FD_ENTRIES: usize = 14;
62const FD_ORIGINATE_TIMESTAMP: usize = 15;
63const FD_RECEIVE_TIMESTAMP: usize = 16;
64const FD_TRANSMIT_TIMESTAMP: usize = 17;
65const FD_ADDRESS_MASK: usize = 18;
66const FD_SUBTYPE: usize = 19;
67const FD_LOCAL: usize = 20;
68const FD_STATE: usize = 21;
69const FD_ACTIVE: usize = 22;
70const FD_IPV4: usize = 23;
71const FD_IPV6: usize = 24;
72const FD_PHOTURIS_RESERVED: usize = 25;
73const FD_PHOTURIS_POINTER: usize = 26;
74const FD_EXTENSIONS: usize = 27;
75
76// Minimum ICMP Extension Header size per RFC 4884, Section 7.
77// <https://www.rfc-editor.org/rfc/rfc4884#section-7>
78const EXT_HEADER_SIZE: usize = 4;
79// Minimum ICMP Extension Object header size per RFC 4884, Section 7.
80const EXT_OBJECT_HEADER_SIZE: usize = 4;
81// Minimum padded original datagram length when extensions are present, per
82// RFC 4884, Section 5.5. <https://www.rfc-editor.org/rfc/rfc4884#section-5.5>
83const EXT_COMPAT_MIN_ORIG_DATAGRAM: usize = 128;
84
85// EXTENSION_CHILDREN indices
86const EXT_VERSION: usize = 0;
87const EXT_RESERVED: usize = 1;
88const EXT_CHECKSUM: usize = 2;
89const EXT_OBJECTS: usize = 3;
90
91// EXTENSION_OBJECT_CHILDREN indices
92const EOBJ_LENGTH: usize = 0;
93const EOBJ_CLASS_NUM: usize = 1;
94const EOBJ_C_TYPE: usize = 2;
95const EOBJ_PAYLOAD: usize = 3;
96const EOBJ_MPLS_LABELS: usize = 4;
97const EOBJ_INTERFACE_ROLE: usize = 5;
98const EOBJ_IF_INDEX: usize = 6;
99const EOBJ_AFI: usize = 7;
100const EOBJ_ADDRESS_LENGTH: usize = 8;
101const EOBJ_IPV4_ADDRESS: usize = 9;
102const EOBJ_IPV6_ADDRESS: usize = 10;
103const EOBJ_INTERFACE_NAME: usize = 11;
104const EOBJ_MTU: usize = 12;
105
106// MPLS_LABEL_CHILDREN indices
107const MPLS_LABEL: usize = 0;
108const MPLS_TC: usize = 1;
109const MPLS_S: usize = 2;
110const MPLS_TTL: usize = 3;
111
112const IPC_VERSION: usize = 0;
113const IPC_IHL: usize = 1;
114const IPC_TOTAL_LENGTH: usize = 2;
115const IPC_PROTOCOL: usize = 3;
116const IPC_SRC: usize = 4;
117const IPC_DST: usize = 5;
118const IPC_SRC_PORT: usize = 6;
119const IPC_DST_PORT: usize = 7;
120const IPC_TRANSPORT_DATA: usize = 8;
121
122static INVOKING_PACKET_CHILDREN: &[FieldDescriptor] = &[
123    FieldDescriptor::new("version", "Version", FieldType::U8),
124    FieldDescriptor::new("ihl", "Internet Header Length", FieldType::U8),
125    FieldDescriptor::new("total_length", "Total Length", FieldType::U16),
126    FieldDescriptor::new("protocol", "Protocol", FieldType::U8),
127    FieldDescriptor::new("src", "Source Address", FieldType::Ipv4Addr),
128    FieldDescriptor::new("dst", "Destination Address", FieldType::Ipv4Addr),
129    FieldDescriptor::new("src_port", "Source Port", FieldType::U16).optional(),
130    FieldDescriptor::new("dst_port", "Destination Port", FieldType::U16).optional(),
131    FieldDescriptor::new("transport_data", "Transport Data", FieldType::Bytes).optional(),
132];
133
134const REC_ROUTER_ADDRESS: usize = 0;
135const REC_PREFERENCE_LEVEL: usize = 1;
136
137static ROUTER_ENTRY_CHILDREN: &[FieldDescriptor] = &[
138    FieldDescriptor::new("router_address", "Router Address", FieldType::Ipv4Addr),
139    // RFC 1256, Section 3.1 — Preference Level is "A signed, twos-complement value;
140    // higher values mean more preferable." The minimum value (0x80000000 = i32::MIN)
141    // signals that the address MUST NOT be used as a default router.
142    // <https://www.rfc-editor.org/rfc/rfc1256#section-3.1>
143    FieldDescriptor::new("preference_level", "Preference Level", FieldType::I32),
144];
145
146// RFC 4950, Section 3 — MPLS Label Stack Entry (4 octets).
147// <https://www.rfc-editor.org/rfc/rfc4950#section-3>
148static MPLS_LABEL_CHILDREN: &[FieldDescriptor] = &[
149    FieldDescriptor::new("label", "Label", FieldType::U32),
150    FieldDescriptor::new("tc", "Traffic Class", FieldType::U8),
151    FieldDescriptor::new("s", "Bottom of Stack", FieldType::U8),
152    FieldDescriptor::new("ttl", "Time to Live", FieldType::U8),
153];
154
155// RFC 4884, Section 7.1 — ICMP Extension Object Header, plus per-class payload
156// fields parsed out by this dissector (RFC 4950 Class 1, RFC 5837 Class 2,
157// RFC 8335 Section 2.1 Class 3). Only the first three entries are always present;
158// the remainder are conditional on class/c_type.
159// <https://www.rfc-editor.org/rfc/rfc4884#section-7.1>
160static EXTENSION_OBJECT_CHILDREN: &[FieldDescriptor] = &[
161    FieldDescriptor::new("length", "Length", FieldType::U16),
162    FieldDescriptor::new("class_num", "Class-Num", FieldType::U8),
163    FieldDescriptor::new("c_type", "C-Type", FieldType::U8),
164    FieldDescriptor::new("payload", "Payload", FieldType::Bytes).optional(),
165    FieldDescriptor::new("mpls_labels", "MPLS Label Stack", FieldType::Array)
166        .optional()
167        .with_children(MPLS_LABEL_CHILDREN),
168    FieldDescriptor::new("interface_role", "Interface Role", FieldType::U8).optional(),
169    FieldDescriptor::new("if_index", "ifIndex", FieldType::U32).optional(),
170    FieldDescriptor::new("afi", "Address Family Identifier", FieldType::U16).optional(),
171    FieldDescriptor::new("address_length", "Address Length", FieldType::U8).optional(),
172    FieldDescriptor::new("ipv4_address", "IPv4 Address", FieldType::Ipv4Addr).optional(),
173    FieldDescriptor::new("ipv6_address", "IPv6 Address", FieldType::Ipv6Addr).optional(),
174    FieldDescriptor::new("interface_name", "Interface Name", FieldType::Bytes).optional(),
175    FieldDescriptor::new("mtu", "MTU", FieldType::U32).optional(),
176];
177
178// RFC 4884, Section 7 — ICMP Extension Header fields.
179// <https://www.rfc-editor.org/rfc/rfc4884#section-7>
180static EXTENSION_CHILDREN: &[FieldDescriptor] = &[
181    FieldDescriptor::new("version", "Version", FieldType::U8),
182    FieldDescriptor::new("reserved", "Reserved", FieldType::U16),
183    FieldDescriptor::new("checksum", "Checksum", FieldType::U16),
184    FieldDescriptor::new("objects", "Objects", FieldType::Array)
185        .with_children(EXTENSION_OBJECT_CHILDREN),
186];
187
188static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
189    FieldDescriptor {
190        name: "type",
191        display_name: "Type",
192        field_type: FieldType::U8,
193        optional: false,
194        children: None,
195        display_fn: Some(|v, _siblings| match v {
196            FieldValue::U8(t) => icmp_type_name(*t),
197            _ => None,
198        }),
199        format_fn: None,
200    },
201    FieldDescriptor::new("code", "Code", FieldType::U8),
202    FieldDescriptor::new("checksum", "Checksum", FieldType::U16),
203    FieldDescriptor::new("identifier", "Identifier", FieldType::U16).optional(),
204    FieldDescriptor::new("sequence_number", "Sequence Number", FieldType::U16).optional(),
205    FieldDescriptor::new("data", "Data", FieldType::Bytes).optional(),
206    FieldDescriptor::new("gateway", "Gateway Internet Address", FieldType::Ipv4Addr).optional(),
207    FieldDescriptor::new("pointer", "Pointer", FieldType::U8).optional(),
208    FieldDescriptor::new("length", "Length", FieldType::U8).optional(),
209    FieldDescriptor::new("next_hop_mtu", "Next-Hop MTU", FieldType::U16).optional(),
210    FieldDescriptor::new("invoking_packet", "Invoking Packet", FieldType::Object)
211        .optional()
212        .with_children(INVOKING_PACKET_CHILDREN),
213    FieldDescriptor::new("num_addrs", "Number of Addresses", FieldType::U8).optional(),
214    FieldDescriptor::new("addr_entry_size", "Address Entry Size", FieldType::U8).optional(),
215    FieldDescriptor::new("lifetime", "Lifetime", FieldType::U16).optional(),
216    FieldDescriptor::new("entries", "Entries", FieldType::Array)
217        .optional()
218        .with_children(ROUTER_ENTRY_CHILDREN),
219    FieldDescriptor::new("originate_timestamp", "Originate Timestamp", FieldType::U32).optional(),
220    FieldDescriptor::new("receive_timestamp", "Receive Timestamp", FieldType::U32).optional(),
221    FieldDescriptor::new("transmit_timestamp", "Transmit Timestamp", FieldType::U32).optional(),
222    FieldDescriptor::new("address_mask", "Address Mask", FieldType::Ipv4Addr).optional(),
223    FieldDescriptor::new("subtype", "Subtype", FieldType::U8).optional(),
224    FieldDescriptor::new("local", "Local", FieldType::U8).optional(),
225    FieldDescriptor::new("state", "State", FieldType::U8).optional(),
226    FieldDescriptor::new("active", "Active", FieldType::U8).optional(),
227    FieldDescriptor::new("ipv4", "IPv4", FieldType::U8).optional(),
228    FieldDescriptor::new("ipv6", "IPv6", FieldType::U8).optional(),
229    // RFC 2521, Section 2 — Photuris (Type 40) Reserved is 16 bits, Pointer is 16 bits.
230    // These distinct descriptors coexist with the 8-bit `pointer` used by Parameter
231    // Problem (Type 12, RFC 792) so each field carries the correct declared width.
232    // <https://www.rfc-editor.org/rfc/rfc2521#section-2>
233    FieldDescriptor::new("photuris_reserved", "Reserved", FieldType::U16).optional(),
234    FieldDescriptor::new("photuris_pointer", "Pointer", FieldType::U16).optional(),
235    // RFC 4884, Section 7 — ICMP Extension Structure (Types 3/11/12 after padded
236    // original datagram; Type 42 after the fixed 8-byte header).
237    // <https://www.rfc-editor.org/rfc/rfc4884#section-7>
238    FieldDescriptor::new("extensions", "ICMP Extension Structure", FieldType::Object)
239        .optional()
240        .with_children(EXTENSION_CHILDREN),
241];
242
243/// ICMP dissector.
244pub struct IcmpDissector;
245
246/// Push invoking packet fields as an Object container into buf.
247fn push_invoking_packet<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
248    if data.len() >= IPV4_MIN_HEADER {
249        let version = data[0] >> 4;
250        let ihl = data[0] & 0x0f;
251        let total_length = read_be_u16(data, 2).unwrap_or_default();
252        let protocol = data[9];
253        let src = [data[12], data[13], data[14], data[15]];
254        let dst = [data[16], data[17], data[18], data[19]];
255
256        let obj_idx = buf.begin_container(
257            &FIELD_DESCRIPTORS[FD_INVOKING_PACKET],
258            FieldValue::Object(0..0),
259            offset..offset + data.len(),
260        );
261        buf.push_field(
262            &INVOKING_PACKET_CHILDREN[IPC_VERSION],
263            FieldValue::U8(version),
264            offset..offset + 1,
265        );
266        buf.push_field(
267            &INVOKING_PACKET_CHILDREN[IPC_IHL],
268            FieldValue::U8(ihl),
269            offset..offset + 1,
270        );
271        buf.push_field(
272            &INVOKING_PACKET_CHILDREN[IPC_TOTAL_LENGTH],
273            FieldValue::U16(total_length),
274            offset + 2..offset + 4,
275        );
276        buf.push_field(
277            &INVOKING_PACKET_CHILDREN[IPC_PROTOCOL],
278            FieldValue::U8(protocol),
279            offset + 9..offset + 10,
280        );
281        buf.push_field(
282            &INVOKING_PACKET_CHILDREN[IPC_SRC],
283            FieldValue::Ipv4Addr(src),
284            offset + 12..offset + 16,
285        );
286        buf.push_field(
287            &INVOKING_PACKET_CHILDREN[IPC_DST],
288            FieldValue::Ipv4Addr(dst),
289            offset + 16..offset + 20,
290        );
291
292        // RFC 792 — parse transport layer from the invoking packet.
293        // ICMP error messages include the original IP header + first 8 bytes of
294        // the original datagram, which covers src_port/dst_port for TCP and UDP.
295        let ip_header_len = (ihl as usize) * 4;
296        if ip_header_len >= IPV4_MIN_HEADER && data.len() > ip_header_len {
297            let transport = &data[ip_header_len..];
298            let transport_offset = offset + ip_header_len;
299            match protocol {
300                6 | 17 => {
301                    // TCP/UDP: first 4 bytes are src_port (2) + dst_port (2)
302                    if transport.len() >= 4 {
303                        let src_port = u16::from_be_bytes([transport[0], transport[1]]);
304                        let dst_port = u16::from_be_bytes([transport[2], transport[3]]);
305                        buf.push_field(
306                            &INVOKING_PACKET_CHILDREN[IPC_SRC_PORT],
307                            FieldValue::U16(src_port),
308                            transport_offset..transport_offset + 2,
309                        );
310                        buf.push_field(
311                            &INVOKING_PACKET_CHILDREN[IPC_DST_PORT],
312                            FieldValue::U16(dst_port),
313                            transport_offset + 2..transport_offset + 4,
314                        );
315                    }
316                }
317                _ => {
318                    buf.push_field(
319                        &INVOKING_PACKET_CHILDREN[IPC_TRANSPORT_DATA],
320                        FieldValue::Bytes(transport),
321                        transport_offset..transport_offset + transport.len(),
322                    );
323                }
324            }
325        }
326
327        buf.end_container(obj_idx);
328    } else {
329        buf.push_field(
330            &FIELD_DESCRIPTORS[FD_INVOKING_PACKET],
331            FieldValue::Bytes(data),
332            offset..offset + data.len(),
333        );
334    }
335}
336
337/// Parse an ICMP Extension Structure (RFC 4884, Section 7) starting at `data[0..]`.
338///
339/// The caller is responsible for locating the start of the Extension Structure
340/// (after the padded original datagram for Types 3/11/12, or immediately after
341/// the 8-byte header for Type 42 per RFC 8335, Section 2).
342///
343/// Silently stops on malformed input (length fields out of range, truncated
344/// objects) per Postel's Law — the ICMP message itself remains valid.
345///
346/// RFC 4884, Section 7 — <https://www.rfc-editor.org/rfc/rfc4884#section-7>
347fn push_extensions<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
348    if data.len() < EXT_HEADER_SIZE {
349        return;
350    }
351    // RFC 4884, Section 7 — Version (4 bits) + Reserved (12 bits) + Checksum (16 bits).
352    let version = data[0] >> 4;
353    let reserved = (u16::from(data[0] & 0x0F) << 8) | u16::from(data[1]);
354    let checksum = read_be_u16(data, 2).unwrap_or_default();
355
356    let ext_idx = buf.begin_container(
357        &FIELD_DESCRIPTORS[FD_EXTENSIONS],
358        FieldValue::Object(0..0),
359        offset..offset + data.len(),
360    );
361    buf.push_field(
362        &EXTENSION_CHILDREN[EXT_VERSION],
363        FieldValue::U8(version),
364        offset..offset + 1,
365    );
366    buf.push_field(
367        &EXTENSION_CHILDREN[EXT_RESERVED],
368        FieldValue::U16(reserved),
369        offset..offset + 2,
370    );
371    buf.push_field(
372        &EXTENSION_CHILDREN[EXT_CHECKSUM],
373        FieldValue::U16(checksum),
374        offset + 2..offset + 4,
375    );
376
377    let objects_idx = buf.begin_container(
378        &EXTENSION_CHILDREN[EXT_OBJECTS],
379        FieldValue::Array(0..0),
380        offset + EXT_HEADER_SIZE..offset + data.len(),
381    );
382    let mut pos = EXT_HEADER_SIZE;
383    while pos + EXT_OBJECT_HEADER_SIZE <= data.len() {
384        // RFC 4884, Section 7.1 — Object header: Length(u16) + Class-Num(u8) + C-Type(u8).
385        let obj_len = read_be_u16(data, pos).unwrap_or_default() as usize;
386        let class_num = data[pos + 2];
387        let c_type = data[pos + 3];
388        // Length covers the whole Object header plus payload (minimum 4 octets).
389        if obj_len < EXT_OBJECT_HEADER_SIZE || pos + obj_len > data.len() {
390            break;
391        }
392        let body = &data[pos + EXT_OBJECT_HEADER_SIZE..pos + obj_len];
393        let body_offset = offset + pos + EXT_OBJECT_HEADER_SIZE;
394        let c_type_offset = offset + pos + 3;
395
396        let obj_idx = buf.begin_container(
397            &EXTENSION_CHILDREN[EXT_OBJECTS],
398            FieldValue::Object(0..0),
399            offset + pos..offset + pos + obj_len,
400        );
401        buf.push_field(
402            &EXTENSION_OBJECT_CHILDREN[EOBJ_LENGTH],
403            FieldValue::U16(obj_len as u16),
404            offset + pos..offset + pos + 2,
405        );
406        buf.push_field(
407            &EXTENSION_OBJECT_CHILDREN[EOBJ_CLASS_NUM],
408            FieldValue::U8(class_num),
409            offset + pos + 2..offset + pos + 3,
410        );
411        buf.push_field(
412            &EXTENSION_OBJECT_CHILDREN[EOBJ_C_TYPE],
413            FieldValue::U8(c_type),
414            offset + pos + 3..offset + pos + 4,
415        );
416
417        match (class_num, c_type) {
418            // RFC 4950, Section 3 — MPLS Label Stack (Class-Num 1, C-Type 1).
419            // <https://www.rfc-editor.org/rfc/rfc4950#section-3>
420            (1, 1) => push_mpls_labels(buf, body, body_offset),
421            // RFC 5837, Section 4 — Interface Information (Class-Num 2).
422            // C-Type itself encodes Role + sub-object presence flags.
423            // <https://www.rfc-editor.org/rfc/rfc5837#section-4>
424            (2, _) => push_interface_info(buf, body, body_offset, c_type, c_type_offset),
425            // RFC 8335, Section 2.1 — Interface Identification (Class-Num 3).
426            // <https://www.rfc-editor.org/rfc/rfc8335#section-2.1>
427            (3, _) => push_interface_id(buf, body, body_offset, c_type),
428            _ => {
429                if !body.is_empty() {
430                    buf.push_field(
431                        &EXTENSION_OBJECT_CHILDREN[EOBJ_PAYLOAD],
432                        FieldValue::Bytes(body),
433                        body_offset..body_offset + body.len(),
434                    );
435                }
436            }
437        }
438        buf.end_container(obj_idx);
439        pos += obj_len;
440    }
441    buf.end_container(objects_idx);
442    buf.end_container(ext_idx);
443}
444
445/// Parse an RFC 4950 MPLS Label Stack Object body as an array of 4-octet LSEs.
446///
447/// Each entry: Label (20 bits) | TC (3 bits) | S (1 bit) | TTL (8 bits).
448/// <https://www.rfc-editor.org/rfc/rfc4950#section-3>
449fn push_mpls_labels<'pkt>(buf: &mut DissectBuffer<'pkt>, body: &'pkt [u8], offset: usize) {
450    let arr_idx = buf.begin_container(
451        &EXTENSION_OBJECT_CHILDREN[EOBJ_MPLS_LABELS],
452        FieldValue::Array(0..0),
453        offset..offset + body.len(),
454    );
455    let mut p = 0usize;
456    while p + 4 <= body.len() {
457        let b0 = u32::from(body[p]);
458        let b1 = u32::from(body[p + 1]);
459        let b2 = u32::from(body[p + 2]);
460        // Label occupies the high 20 bits of octets 0..3.
461        let label = (b0 << 12) | (b1 << 4) | (b2 >> 4);
462        let tc = (body[p + 2] >> 1) & 0x07;
463        let s = body[p + 2] & 0x01;
464        let ttl = body[p + 3];
465
466        let entry_idx = buf.begin_container(
467            &EXTENSION_OBJECT_CHILDREN[EOBJ_MPLS_LABELS],
468            FieldValue::Object(0..0),
469            offset + p..offset + p + 4,
470        );
471        buf.push_field(
472            &MPLS_LABEL_CHILDREN[MPLS_LABEL],
473            FieldValue::U32(label),
474            offset + p..offset + p + 3,
475        );
476        buf.push_field(
477            &MPLS_LABEL_CHILDREN[MPLS_TC],
478            FieldValue::U8(tc),
479            offset + p + 2..offset + p + 3,
480        );
481        buf.push_field(
482            &MPLS_LABEL_CHILDREN[MPLS_S],
483            FieldValue::U8(s),
484            offset + p + 2..offset + p + 3,
485        );
486        buf.push_field(
487            &MPLS_LABEL_CHILDREN[MPLS_TTL],
488            FieldValue::U8(ttl),
489            offset + p + 3..offset + p + 4,
490        );
491        buf.end_container(entry_idx);
492        p += 4;
493    }
494    buf.end_container(arr_idx);
495}
496
497/// Parse an RFC 5837 Interface Information Object body.
498///
499/// The C-Type byte itself encodes the Interface Role (bits 0-1) and four
500/// presence flags: ifIndex (bit 4), IP Address (bit 5), Interface Name (bit 6),
501/// MTU (bit 7). Sub-objects appear in that fixed order.
502///
503/// RFC 5837, Section 4 — <https://www.rfc-editor.org/rfc/rfc5837#section-4>
504fn push_interface_info<'pkt>(
505    buf: &mut DissectBuffer<'pkt>,
506    body: &'pkt [u8],
507    body_offset: usize,
508    c_type: u8,
509    c_type_offset: usize,
510) {
511    // RFC 5837, Section 4.1 — Interface Role: bits 0-1 of the C-Type byte
512    // (bit 0 = MSB; i.e. (c_type >> 6) & 0x03).
513    let role = (c_type >> 6) & 0x03;
514    let has_ifindex = (c_type & 0x08) != 0;
515    let has_addr = (c_type & 0x04) != 0;
516    let has_name = (c_type & 0x02) != 0;
517    let has_mtu = (c_type & 0x01) != 0;
518
519    buf.push_field(
520        &EXTENSION_OBJECT_CHILDREN[EOBJ_INTERFACE_ROLE],
521        FieldValue::U8(role),
522        c_type_offset..c_type_offset + 1,
523    );
524
525    let mut p = 0usize;
526    if has_ifindex {
527        if p + 4 > body.len() {
528            return;
529        }
530        let ifindex = read_be_u32(body, p).unwrap_or_default();
531        buf.push_field(
532            &EXTENSION_OBJECT_CHILDREN[EOBJ_IF_INDEX],
533            FieldValue::U32(ifindex),
534            body_offset + p..body_offset + p + 4,
535        );
536        p += 4;
537    }
538    if has_addr {
539        // RFC 5837, Section 4.2 — IP Address Sub-Object: AFI(u16) + Reserved(u16) + Address.
540        if p + 4 > body.len() {
541            return;
542        }
543        let afi = read_be_u16(body, p).unwrap_or_default();
544        buf.push_field(
545            &EXTENSION_OBJECT_CHILDREN[EOBJ_AFI],
546            FieldValue::U16(afi),
547            body_offset + p..body_offset + p + 2,
548        );
549        p += 4; // skip AFI + Reserved
550        match afi {
551            // IANA Address Family Numbers: 1 = IPv4, 2 = IPv6.
552            1 => {
553                if p + 4 > body.len() {
554                    return;
555                }
556                let addr = [body[p], body[p + 1], body[p + 2], body[p + 3]];
557                buf.push_field(
558                    &EXTENSION_OBJECT_CHILDREN[EOBJ_IPV4_ADDRESS],
559                    FieldValue::Ipv4Addr(addr),
560                    body_offset + p..body_offset + p + 4,
561                );
562                p += 4;
563            }
564            2 => {
565                if p + 16 > body.len() {
566                    return;
567                }
568                let mut addr = [0u8; 16];
569                addr.copy_from_slice(&body[p..p + 16]);
570                buf.push_field(
571                    &EXTENSION_OBJECT_CHILDREN[EOBJ_IPV6_ADDRESS],
572                    FieldValue::Ipv6Addr(addr),
573                    body_offset + p..body_offset + p + 16,
574                );
575                p += 16;
576            }
577            _ => return,
578        }
579    }
580    if has_name {
581        // RFC 5837, Section 4.5 — Interface Name Sub-Object: 1-octet Length
582        // (including itself, multiple of 4, max 64), then name bytes padded with NULs.
583        if p >= body.len() {
584            return;
585        }
586        let name_total = body[p] as usize;
587        if name_total < 2 || p + name_total > body.len() {
588            return;
589        }
590        let name_bytes = &body[p + 1..p + name_total];
591        buf.push_field(
592            &EXTENSION_OBJECT_CHILDREN[EOBJ_INTERFACE_NAME],
593            FieldValue::Bytes(name_bytes),
594            body_offset + p + 1..body_offset + p + name_total,
595        );
596        p += name_total;
597    }
598    if has_mtu {
599        // RFC 5837, Section 4.6 — MTU Sub-Object: 32-bit unsigned MTU.
600        if p + 4 > body.len() {
601            return;
602        }
603        let mtu = read_be_u32(body, p).unwrap_or_default();
604        buf.push_field(
605            &EXTENSION_OBJECT_CHILDREN[EOBJ_MTU],
606            FieldValue::U32(mtu),
607            body_offset + p..body_offset + p + 4,
608        );
609    }
610}
611
612/// Parse an RFC 8335 Interface Identification Object body (Class-Num 3).
613///
614/// - C-Type 1: interface name (raw bytes, NUL-padded to 32-bit boundary).
615/// - C-Type 2: 32-bit ifIndex.
616/// - C-Type 3: AFI(u16) + AddrLen(u8) + Reserved(u8) + Address (NUL-padded).
617///
618/// <https://www.rfc-editor.org/rfc/rfc8335#section-2.1>
619fn push_interface_id<'pkt>(
620    buf: &mut DissectBuffer<'pkt>,
621    body: &'pkt [u8],
622    body_offset: usize,
623    c_type: u8,
624) {
625    match c_type {
626        1 if !body.is_empty() => {
627            buf.push_field(
628                &EXTENSION_OBJECT_CHILDREN[EOBJ_INTERFACE_NAME],
629                FieldValue::Bytes(body),
630                body_offset..body_offset + body.len(),
631            );
632        }
633        2 if body.len() >= 4 => {
634            let ifindex = read_be_u32(body, 0).unwrap_or_default();
635            buf.push_field(
636                &EXTENSION_OBJECT_CHILDREN[EOBJ_IF_INDEX],
637                FieldValue::U32(ifindex),
638                body_offset..body_offset + 4,
639            );
640        }
641        3 => {
642            if body.len() < 4 {
643                return;
644            }
645            let afi = read_be_u16(body, 0).unwrap_or_default();
646            let addr_len = body[2];
647            buf.push_field(
648                &EXTENSION_OBJECT_CHILDREN[EOBJ_AFI],
649                FieldValue::U16(afi),
650                body_offset..body_offset + 2,
651            );
652            buf.push_field(
653                &EXTENSION_OBJECT_CHILDREN[EOBJ_ADDRESS_LENGTH],
654                FieldValue::U8(addr_len),
655                body_offset + 2..body_offset + 3,
656            );
657            match afi {
658                // IANA AFI: 1 = IPv4 (4 octets), 2 = IPv6 (16 octets).
659                1 if addr_len as usize >= 4 && body.len() >= 8 => {
660                    let a = [body[4], body[5], body[6], body[7]];
661                    buf.push_field(
662                        &EXTENSION_OBJECT_CHILDREN[EOBJ_IPV4_ADDRESS],
663                        FieldValue::Ipv4Addr(a),
664                        body_offset + 4..body_offset + 8,
665                    );
666                }
667                2 if addr_len as usize >= 16 && body.len() >= 20 => {
668                    let mut a = [0u8; 16];
669                    a.copy_from_slice(&body[4..20]);
670                    buf.push_field(
671                        &EXTENSION_OBJECT_CHILDREN[EOBJ_IPV6_ADDRESS],
672                        FieldValue::Ipv6Addr(a),
673                        body_offset + 4..body_offset + 20,
674                    );
675                }
676                _ => {}
677            }
678        }
679        _ => {}
680    }
681}
682
683/// Compute where the RFC 4884 Extension Structure starts for an error message
684/// (Types 3, 11, 12). Returns `Some(offset)` when the Length field indicates
685/// extensions follow, or `None` when there are no extensions to parse.
686///
687/// Per RFC 4884, Section 5.5, when the Length field is non-zero the original
688/// datagram MUST be padded to at least 128 octets before the extensions.
689/// <https://www.rfc-editor.org/rfc/rfc4884#section-5.5>
690fn rfc4884_extension_offset(data_len: usize, length: u8) -> Option<usize> {
691    if length == 0 {
692        return None;
693    }
694    let orig_len = (length as usize) * 4;
695    let padded = orig_len.max(EXT_COMPAT_MIN_ORIG_DATAGRAM);
696    let ext_start = HEADER_SIZE + padded;
697    if ext_start + EXT_HEADER_SIZE <= data_len {
698        Some(ext_start)
699    } else {
700        None
701    }
702}
703
704impl Dissector for IcmpDissector {
705    fn name(&self) -> &'static str {
706        "Internet Control Message Protocol"
707    }
708    fn short_name(&self) -> &'static str {
709        "ICMP"
710    }
711    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
712        FIELD_DESCRIPTORS
713    }
714
715    fn dissect<'pkt>(
716        &self,
717        data: &'pkt [u8],
718        buf: &mut DissectBuffer<'pkt>,
719        offset: usize,
720    ) -> Result<DissectResult, PacketError> {
721        if data.len() < HEADER_SIZE {
722            return Err(PacketError::Truncated {
723                expected: HEADER_SIZE,
724                actual: data.len(),
725            });
726        }
727
728        let icmp_type = data[0];
729        let code = data[1];
730        let checksum = read_be_u16(data, 2)?;
731
732        buf.begin_layer(
733            self.short_name(),
734            None,
735            FIELD_DESCRIPTORS,
736            offset..offset + data.len(),
737        );
738        buf.push_field(
739            &FIELD_DESCRIPTORS[FD_TYPE],
740            FieldValue::U8(icmp_type),
741            offset..offset + 1,
742        );
743        buf.push_field(
744            &FIELD_DESCRIPTORS[FD_CODE],
745            FieldValue::U8(code),
746            offset + 1..offset + 2,
747        );
748        buf.push_field(
749            &FIELD_DESCRIPTORS[FD_CHECKSUM],
750            FieldValue::U16(checksum),
751            offset + 2..offset + 4,
752        );
753
754        match icmp_type {
755            // RFC 792 — Echo Reply (0) / Echo Request (8)
756            // <https://www.rfc-editor.org/rfc/rfc792#page-14>
757            0 | 8 => {
758                let identifier = read_be_u16(data, 4)?;
759                let sequence_number = read_be_u16(data, 6)?;
760                buf.push_field(
761                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
762                    FieldValue::U16(identifier),
763                    offset + 4..offset + 6,
764                );
765                buf.push_field(
766                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
767                    FieldValue::U16(sequence_number),
768                    offset + 6..offset + 8,
769                );
770                if data.len() > HEADER_SIZE {
771                    buf.push_field(
772                        &FIELD_DESCRIPTORS[FD_DATA],
773                        FieldValue::Bytes(&data[HEADER_SIZE..]),
774                        offset + HEADER_SIZE..offset + data.len(),
775                    );
776                }
777            }
778            // RFC 792 — Destination Unreachable (3)
779            // <https://www.rfc-editor.org/rfc/rfc792#page-4>
780            // Safe: HEADER_SIZE check (8 bytes) guarantees indices 0..7
781            3 => {
782                // RFC 4884, Section 4.5 — Length field (offset 5) in 32-bit words.
783                // <https://www.rfc-editor.org/rfc/rfc4884#section-4.5>
784                let length = data[5];
785                if length > 0 {
786                    buf.push_field(
787                        &FIELD_DESCRIPTORS[FD_LENGTH],
788                        FieldValue::U8(length),
789                        offset + 5..offset + 6,
790                    );
791                }
792                if code == 4 {
793                    // RFC 1191, Section 4 — Next-Hop MTU (u16 at offset 6).
794                    // <https://www.rfc-editor.org/rfc/rfc1191#section-4>
795                    let next_hop_mtu = read_be_u16(data, 6)?;
796                    buf.push_field(
797                        &FIELD_DESCRIPTORS[FD_NEXT_HOP_MTU],
798                        FieldValue::U16(next_hop_mtu),
799                        offset + 6..offset + 8,
800                    );
801                }
802                if data.len() > HEADER_SIZE {
803                    push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
804                }
805                if let Some(ext_start) = rfc4884_extension_offset(data.len(), length) {
806                    push_extensions(buf, &data[ext_start..], offset + ext_start);
807                }
808            }
809            // RFC 792 / RFC 6633 — Source Quench (4), deprecated.
810            // <https://www.rfc-editor.org/rfc/rfc6633#section-1>
811            4 if data.len() > HEADER_SIZE => {
812                push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
813            }
814            // RFC 792 — Time Exceeded (11)
815            // <https://www.rfc-editor.org/rfc/rfc792#page-6>
816            // Safe: HEADER_SIZE check (8 bytes) guarantees indices 0..7
817            11 => {
818                // RFC 4884, Section 4.5 — Length field (offset 5) in 32-bit words.
819                // <https://www.rfc-editor.org/rfc/rfc4884#section-4.5>
820                let length = data[5];
821                if length > 0 {
822                    buf.push_field(
823                        &FIELD_DESCRIPTORS[FD_LENGTH],
824                        FieldValue::U8(length),
825                        offset + 5..offset + 6,
826                    );
827                }
828                if data.len() > HEADER_SIZE {
829                    push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
830                }
831                if let Some(ext_start) = rfc4884_extension_offset(data.len(), length) {
832                    push_extensions(buf, &data[ext_start..], offset + ext_start);
833                }
834            }
835            // RFC 792 — Redirect (5)
836            // <https://www.rfc-editor.org/rfc/rfc792#page-12>
837            5 => {
838                let gateway = [data[4], data[5], data[6], data[7]];
839                buf.push_field(
840                    &FIELD_DESCRIPTORS[FD_GATEWAY],
841                    FieldValue::Ipv4Addr(gateway),
842                    offset + 4..offset + 8,
843                );
844                if data.len() > HEADER_SIZE {
845                    push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
846                }
847            }
848            // RFC 792 — Parameter Problem (12)
849            // <https://www.rfc-editor.org/rfc/rfc792#page-8>
850            12 => {
851                // RFC 792 — Pointer (u8 at offset 4), identifies the octet in error.
852                let pointer = data[4];
853                buf.push_field(
854                    &FIELD_DESCRIPTORS[FD_POINTER],
855                    FieldValue::U8(pointer),
856                    offset + 4..offset + 5,
857                );
858                // RFC 4884, Section 4.5 — Length field (offset 5) in 32-bit words.
859                // <https://www.rfc-editor.org/rfc/rfc4884#section-4.5>
860                let length = data[5];
861                if length > 0 {
862                    buf.push_field(
863                        &FIELD_DESCRIPTORS[FD_LENGTH],
864                        FieldValue::U8(length),
865                        offset + 5..offset + 6,
866                    );
867                }
868                if data.len() > HEADER_SIZE {
869                    push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
870                }
871                if let Some(ext_start) = rfc4884_extension_offset(data.len(), length) {
872                    push_extensions(buf, &data[ext_start..], offset + ext_start);
873                }
874            }
875            // RFC 1256 — Router Advertisement (9)
876            // <https://www.rfc-editor.org/rfc/rfc1256>
877            9 => {
878                let num_addrs = data[4];
879                let addr_entry_size = data[5];
880                let lifetime = read_be_u16(data, 6)?;
881                buf.push_field(
882                    &FIELD_DESCRIPTORS[FD_NUM_ADDRS],
883                    FieldValue::U8(num_addrs),
884                    offset + 4..offset + 5,
885                );
886                buf.push_field(
887                    &FIELD_DESCRIPTORS[FD_ADDR_ENTRY_SIZE],
888                    FieldValue::U8(addr_entry_size),
889                    offset + 5..offset + 6,
890                );
891                buf.push_field(
892                    &FIELD_DESCRIPTORS[FD_LIFETIME],
893                    FieldValue::U16(lifetime),
894                    offset + 6..offset + 8,
895                );
896
897                let entry_bytes = addr_entry_size as usize * 4;
898                let array_idx = buf.begin_container(
899                    &FIELD_DESCRIPTORS[FD_ENTRIES],
900                    FieldValue::Array(0..0),
901                    offset + HEADER_SIZE..offset + data.len(),
902                );
903                let mut pos = HEADER_SIZE;
904                for _ in 0..num_addrs {
905                    if pos + entry_bytes > data.len() || entry_bytes < 8 {
906                        break;
907                    }
908                    let router_addr = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
909                    // RFC 1256, Section 3.1 — Preference Level is a signed 32-bit
910                    // twos-complement value. <https://www.rfc-editor.org/rfc/rfc1256#section-3.1>
911                    let pref = read_be_u32(data, pos + 4)? as i32;
912                    let obj_idx = buf.begin_container(
913                        &ROUTER_ENTRY_CHILDREN[REC_ROUTER_ADDRESS], // reuse descriptor for object marker
914                        FieldValue::Object(0..0),
915                        offset + pos..offset + pos + entry_bytes,
916                    );
917                    buf.push_field(
918                        &ROUTER_ENTRY_CHILDREN[REC_ROUTER_ADDRESS],
919                        FieldValue::Ipv4Addr(router_addr),
920                        offset + pos..offset + pos + 4,
921                    );
922                    buf.push_field(
923                        &ROUTER_ENTRY_CHILDREN[REC_PREFERENCE_LEVEL],
924                        FieldValue::I32(pref),
925                        offset + pos + 4..offset + pos + 8,
926                    );
927                    buf.end_container(obj_idx);
928                    pos += entry_bytes;
929                }
930                buf.end_container(array_idx);
931            }
932            // RFC 1256 — Router Solicitation (10), no type-specific fields
933            // <https://www.rfc-editor.org/rfc/rfc1256>
934            10 => {}
935            // RFC 792 — Timestamp (13) / Timestamp Reply (14)
936            // <https://www.rfc-editor.org/rfc/rfc792#page-16>
937            13 | 14 => {
938                if data.len() < TIMESTAMP_SIZE {
939                    return Err(PacketError::Truncated {
940                        expected: TIMESTAMP_SIZE,
941                        actual: data.len(),
942                    });
943                }
944                let identifier = read_be_u16(data, 4)?;
945                let sequence_number = read_be_u16(data, 6)?;
946                let originate = read_be_u32(data, 8)?;
947                let receive = read_be_u32(data, 12)?;
948                let transmit = read_be_u32(data, 16)?;
949                buf.push_field(
950                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
951                    FieldValue::U16(identifier),
952                    offset + 4..offset + 6,
953                );
954                buf.push_field(
955                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
956                    FieldValue::U16(sequence_number),
957                    offset + 6..offset + 8,
958                );
959                buf.push_field(
960                    &FIELD_DESCRIPTORS[FD_ORIGINATE_TIMESTAMP],
961                    FieldValue::U32(originate),
962                    offset + 8..offset + 12,
963                );
964                buf.push_field(
965                    &FIELD_DESCRIPTORS[FD_RECEIVE_TIMESTAMP],
966                    FieldValue::U32(receive),
967                    offset + 12..offset + 16,
968                );
969                buf.push_field(
970                    &FIELD_DESCRIPTORS[FD_TRANSMIT_TIMESTAMP],
971                    FieldValue::U32(transmit),
972                    offset + 16..offset + 20,
973                );
974            }
975            // RFC 792 / RFC 6918 — Information Request (15) / Reply (16), deprecated.
976            // <https://www.rfc-editor.org/rfc/rfc6918#section-3>
977            15 | 16 => {
978                let identifier = read_be_u16(data, 4)?;
979                let sequence_number = read_be_u16(data, 6)?;
980                buf.push_field(
981                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
982                    FieldValue::U16(identifier),
983                    offset + 4..offset + 6,
984                );
985                buf.push_field(
986                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
987                    FieldValue::U16(sequence_number),
988                    offset + 6..offset + 8,
989                );
990            }
991            // RFC 950 / RFC 6918 — Address Mask Request (17) / Reply (18), deprecated.
992            // <https://www.rfc-editor.org/rfc/rfc950>
993            17 | 18 => {
994                if data.len() < ADDRESS_MASK_SIZE {
995                    return Err(PacketError::Truncated {
996                        expected: ADDRESS_MASK_SIZE,
997                        actual: data.len(),
998                    });
999                }
1000                let identifier = read_be_u16(data, 4)?;
1001                let sequence_number = read_be_u16(data, 6)?;
1002                let mask = [data[8], data[9], data[10], data[11]];
1003                buf.push_field(
1004                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
1005                    FieldValue::U16(identifier),
1006                    offset + 4..offset + 6,
1007                );
1008                buf.push_field(
1009                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
1010                    FieldValue::U16(sequence_number),
1011                    offset + 6..offset + 8,
1012                );
1013                buf.push_field(
1014                    &FIELD_DESCRIPTORS[FD_ADDRESS_MASK],
1015                    FieldValue::Ipv4Addr(mask),
1016                    offset + 8..offset + 12,
1017                );
1018            }
1019            // ICMP Security Failures / Photuris (Type 40) — RFC 2521, Section 2.
1020            // Layout: Reserved (u16 at offset 4), Pointer (u16 at offset 6), then
1021            // the Original Internet Headers + 64 bits of the offending payload.
1022            // <https://www.rfc-editor.org/rfc/rfc2521#section-2>
1023            40 => {
1024                let reserved = read_be_u16(data, 4)?;
1025                let pointer = read_be_u16(data, 6)?;
1026                buf.push_field(
1027                    &FIELD_DESCRIPTORS[FD_PHOTURIS_RESERVED],
1028                    FieldValue::U16(reserved),
1029                    offset + 4..offset + 6,
1030                );
1031                buf.push_field(
1032                    &FIELD_DESCRIPTORS[FD_PHOTURIS_POINTER],
1033                    FieldValue::U16(pointer),
1034                    offset + 6..offset + 8,
1035                );
1036                if data.len() > HEADER_SIZE {
1037                    push_invoking_packet(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
1038                }
1039            }
1040            // RFC 4065, Section 8 — Experimental Mobility (41)
1041            // <https://www.rfc-editor.org/rfc/rfc4065#section-8>
1042            41 => {
1043                let subtype = data[4];
1044                buf.push_field(
1045                    &FIELD_DESCRIPTORS[FD_SUBTYPE],
1046                    FieldValue::U8(subtype),
1047                    offset + 4..offset + 5,
1048                );
1049                if data.len() > HEADER_SIZE {
1050                    buf.push_field(
1051                        &FIELD_DESCRIPTORS[FD_DATA],
1052                        FieldValue::Bytes(&data[HEADER_SIZE..]),
1053                        offset + HEADER_SIZE..offset + data.len(),
1054                    );
1055                }
1056            }
1057            // RFC 8335, Section 2 — Extended Echo Request (42).
1058            // Body after the 8-byte header is an RFC 4884 Extension Structure
1059            // carrying the Interface Identification Object (Class-Num 3).
1060            // <https://www.rfc-editor.org/rfc/rfc8335#section-2>
1061            42 => {
1062                let identifier = read_be_u16(data, 4)?;
1063                // RFC 8335: Sequence Number is 1 byte, promoted to U16
1064                // to match the shared field descriptor.
1065                let sequence_number = u16::from(data[6]);
1066                let local = data[7] & 0x01;
1067                buf.push_field(
1068                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
1069                    FieldValue::U16(identifier),
1070                    offset + 4..offset + 6,
1071                );
1072                buf.push_field(
1073                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
1074                    FieldValue::U16(sequence_number),
1075                    offset + 6..offset + 7,
1076                );
1077                buf.push_field(
1078                    &FIELD_DESCRIPTORS[FD_LOCAL],
1079                    FieldValue::U8(local),
1080                    offset + 7..offset + 8,
1081                );
1082                if data.len() > HEADER_SIZE {
1083                    push_extensions(buf, &data[HEADER_SIZE..], offset + HEADER_SIZE);
1084                }
1085            }
1086            // RFC 8335, Section 3 — Extended Echo Reply (43)
1087            // <https://www.rfc-editor.org/rfc/rfc8335#section-3>
1088            43 => {
1089                let identifier = read_be_u16(data, 4)?;
1090                // RFC 8335: Sequence Number is 1 byte, promoted to U16
1091                // to match the shared field descriptor.
1092                let sequence_number = u16::from(data[6]);
1093                let flags_byte = data[7];
1094                let state = (flags_byte >> 5) & 0x07;
1095                let active = (flags_byte >> 2) & 0x01;
1096                let ipv4_flag = (flags_byte >> 1) & 0x01;
1097                let ipv6_flag = flags_byte & 0x01;
1098                buf.push_field(
1099                    &FIELD_DESCRIPTORS[FD_IDENTIFIER],
1100                    FieldValue::U16(identifier),
1101                    offset + 4..offset + 6,
1102                );
1103                buf.push_field(
1104                    &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
1105                    FieldValue::U16(sequence_number),
1106                    offset + 6..offset + 7,
1107                );
1108                buf.push_field(
1109                    &FIELD_DESCRIPTORS[FD_STATE],
1110                    FieldValue::U8(state),
1111                    offset + 7..offset + 8,
1112                );
1113                buf.push_field(
1114                    &FIELD_DESCRIPTORS[FD_ACTIVE],
1115                    FieldValue::U8(active),
1116                    offset + 7..offset + 8,
1117                );
1118                buf.push_field(
1119                    &FIELD_DESCRIPTORS[FD_IPV4],
1120                    FieldValue::U8(ipv4_flag),
1121                    offset + 7..offset + 8,
1122                );
1123                buf.push_field(
1124                    &FIELD_DESCRIPTORS[FD_IPV6],
1125                    FieldValue::U8(ipv6_flag),
1126                    offset + 7..offset + 8,
1127                );
1128            }
1129            _ => {}
1130        }
1131
1132        buf.end_layer();
1133
1134        Ok(DissectResult::new(data.len(), DispatchHint::End))
1135    }
1136}
1137
1138#[cfg(test)]
1139mod tests {
1140    //! # RFC Coverage
1141    //!
1142    //! | RFC Section                        | Description                            | Test                                           |
1143    //! |------------------------------------|----------------------------------------|------------------------------------------------|
1144    //! | RFC 792 Echo Request/Reply         | Echo Request basic fields              | parse_echo_request                             |
1145    //! | RFC 792 Destination Unreachable    | Code 4 Next-Hop MTU (RFC 1191)         | parse_dest_unreachable_fragmentation_needed     |
1146    //! | RFC 4884 §4.5                      | Length field (offset 5)                | parse_dest_unreachable_with_rfc4884_length      |
1147    //! | RFC 792 Destination Unreachable    | IPv4 header only (regression)          | parse_invoking_packet_ipv4_only                |
1148    //! | RFC 792 Destination Unreachable    | UDP src_port/dst_port extraction       | parse_dest_unreachable_udp_ports               |
1149    //! | RFC 792 Destination Unreachable    | TCP src_port/dst_port extraction       | parse_dest_unreachable_tcp_ports               |
1150    //! | RFC 792 Destination Unreachable    | Other protocol → raw bytes             | parse_dest_unreachable_other_protocol          |
1151    //! | RFC 792 Destination Unreachable    | Truncated transport (no ports)         | parse_dest_unreachable_truncated_transport      |
1152    //! | RFC 792 Redirect                   | Gateway address                        | parse_redirect                                 |
1153    //! | RFC 792 Parameter Problem          | Pointer + RFC 4884 Length              | parse_parameter_problem                        |
1154    //! | RFC 792 Timestamp                  | 20-byte timestamp fields               | parse_timestamp_request                        |
1155    //! | RFC 792 Timestamp                  | Truncated timestamp → error            | parse_timestamp_truncated                      |
1156    //! | RFC 950 Address Mask               | Identifier + Seq + Mask                | parse_address_mask_reply                       |
1157    //! | RFC 1256 Router Advertisement      | Signed preference level                | parse_router_advertisement_signed_preference   |
1158    //! | RFC 1256 Router Advertisement      | Do-not-use preference (0x80000000)     | parse_router_advertisement_do_not_use          |
1159    //! | RFC 2521 Photuris (Type 40)        | Reserved(u16) + Pointer(u16)           | parse_photuris                                 |
1160    //! | RFC 8335 §2 Extended Echo Request  | Identifier, Seq(u8), L-bit             | parse_extended_echo_request                    |
1161    //! | RFC 8335 §3 Extended Echo Reply    | State, Active, IPv4, IPv6 flags        | parse_extended_echo_reply                      |
1162    //! | RFC 792                            | Header < 8 bytes → Truncated error     | parse_truncated_header                         |
1163    //! | RFC 4884 §7 Extension Header       | Version / Reserved / Checksum          | parse_extension_header_fields                  |
1164    //! | RFC 4884 §5.5                      | Length=0 → no extensions               | parse_extensions_not_parsed_when_length_zero   |
1165    //! | RFC 4884 §5.5                      | 128-octet min padded datagram rule     | rfc4884_extension_offset_padding_semantics     |
1166    //! | RFC 4884 §7.1                      | Malformed Object Length → stop         | parse_extension_malformed_object_length_stops_parsing |
1167    //! | RFC 4884 §7.1                      | Unknown class → raw payload            | parse_extension_unknown_class_preserves_payload |
1168    //! | RFC 4950 §3 MPLS Label Stack       | Class 1 LSE: Label/TC/S/TTL            | parse_extension_mpls_label_stack_class1        |
1169    //! | RFC 5837 §4 Interface Information  | Class 2 all sub-objects (IPv4)         | parse_extension_interface_info_class2_all_sub_objects |
1170    //! | RFC 5837 §4.2                      | Class 2 IPv6 address sub-object        | parse_extension_interface_info_class2_ipv6_address |
1171    //! | RFC 8335 §2.1 Interface ID         | Class 3 C-Type 1 (by Name)             | parse_extension_interface_id_class3_by_name    |
1172    //! | RFC 8335 §2.1 Interface ID         | Class 3 C-Type 2 (by Index)            | parse_extension_interface_id_class3_by_index   |
1173    //! | RFC 8335 §2.1 Interface ID         | Class 3 C-Type 3 (by IPv4 Address)     | parse_extension_interface_id_class3_by_address_ipv4 |
1174    //! | RFC 8335 §2.1 Interface ID         | Class 3 C-Type 3 (by IPv6 Address)     | parse_extension_interface_id_class3_by_address_ipv6 |
1175    //! | RFC 8335 §2 + RFC 4884             | Type 42 body IS Extension Structure    | parse_extended_echo_request_with_interface_id_extension |
1176
1177    use super::*;
1178
1179    /// Build an ICMP Destination Unreachable (type 3) packet with an invoking packet.
1180    fn build_dest_unreachable(code: u8, invoking: &[u8]) -> Vec<u8> {
1181        let mut pkt = Vec::with_capacity(HEADER_SIZE + invoking.len());
1182        pkt.push(3); // type = Destination Unreachable
1183        pkt.push(code);
1184        pkt.extend_from_slice(&[0x00, 0x00]); // checksum
1185        pkt.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // unused
1186        pkt.extend_from_slice(invoking);
1187        pkt
1188    }
1189
1190    /// Build a minimal IPv4 header (20 bytes, IHL=5) with the given protocol.
1191    fn build_ipv4_header(protocol: u8, src: [u8; 4], dst: [u8; 4]) -> Vec<u8> {
1192        let mut hdr = vec![0u8; 20];
1193        hdr[0] = 0x45; // version=4, ihl=5
1194        hdr[2] = 0x00; // total_length high byte
1195        hdr[3] = 0x28; // total_length = 40
1196        hdr[9] = protocol;
1197        hdr[12..16].copy_from_slice(&src);
1198        hdr[16..20].copy_from_slice(&dst);
1199        hdr
1200    }
1201
1202    #[test]
1203    fn parse_invoking_packet_ipv4_only() {
1204        let invoking = build_ipv4_header(17, [10, 0, 0, 1], [10, 0, 0, 2]);
1205        let data = build_dest_unreachable(0, &invoking);
1206
1207        let mut buf = DissectBuffer::new();
1208        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1209
1210        let layer = &buf.layers()[0];
1211        let fields = buf.layer_fields(layer);
1212        let ip_obj = fields
1213            .iter()
1214            .find(|f| f.descriptor.name == "invoking_packet")
1215            .unwrap();
1216        let range = match &ip_obj.value {
1217            FieldValue::Object(r) => r.clone(),
1218            other => panic!("expected Object, got {other:?}"),
1219        };
1220        let children = buf.nested_fields(&range);
1221        assert_eq!(children[IPC_VERSION].value, FieldValue::U8(4));
1222        assert_eq!(children[IPC_IHL].value, FieldValue::U8(5));
1223        assert_eq!(children[IPC_PROTOCOL].value, FieldValue::U8(17));
1224        assert_eq!(children[IPC_SRC].value, FieldValue::Ipv4Addr([10, 0, 0, 1]));
1225        assert_eq!(children[IPC_DST].value, FieldValue::Ipv4Addr([10, 0, 0, 2]));
1226    }
1227
1228    #[test]
1229    fn parse_dest_unreachable_udp_ports() {
1230        let src = [192, 168, 1, 1];
1231        let dst = [192, 168, 1, 2];
1232        let mut invoking = build_ipv4_header(17, src, dst); // UDP
1233        // Append UDP header: src_port(2) + dst_port(2) + length(2) + checksum(2)
1234        invoking.extend_from_slice(&[0x1F, 0x90]); // src_port = 8080
1235        invoking.extend_from_slice(&[0x00, 0x35]); // dst_port = 53
1236        invoking.extend_from_slice(&[0x00, 0x08, 0x00, 0x00]); // length + checksum
1237        let data = build_dest_unreachable(3, &invoking); // code 3 = Port Unreachable
1238
1239        let mut buf = DissectBuffer::new();
1240        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1241
1242        let layer = &buf.layers()[0];
1243        let fields = buf.layer_fields(layer);
1244        let ip_obj = fields
1245            .iter()
1246            .find(|f| f.descriptor.name == "invoking_packet")
1247            .unwrap();
1248        let range = match &ip_obj.value {
1249            FieldValue::Object(r) => r.clone(),
1250            other => panic!("expected Object, got {other:?}"),
1251        };
1252        let children = buf.nested_fields(&range);
1253        let src_port = children
1254            .iter()
1255            .find(|f| f.descriptor.name == "src_port")
1256            .unwrap();
1257        assert_eq!(src_port.value, FieldValue::U16(8080));
1258        let dst_port = children
1259            .iter()
1260            .find(|f| f.descriptor.name == "dst_port")
1261            .unwrap();
1262        assert_eq!(dst_port.value, FieldValue::U16(53));
1263    }
1264
1265    #[test]
1266    fn parse_dest_unreachable_tcp_ports() {
1267        let src = [10, 0, 0, 1];
1268        let dst = [10, 0, 0, 2];
1269        let mut invoking = build_ipv4_header(6, src, dst); // TCP
1270        // Append TCP header first 8 bytes: src_port(2) + dst_port(2) + seq(4)
1271        invoking.extend_from_slice(&[0x00, 0x50]); // src_port = 80
1272        invoking.extend_from_slice(&[0xC0, 0x00]); // dst_port = 49152
1273        invoking.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); // seq
1274        let data = build_dest_unreachable(3, &invoking);
1275
1276        let mut buf = DissectBuffer::new();
1277        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1278
1279        let layer = &buf.layers()[0];
1280        let fields = buf.layer_fields(layer);
1281        let ip_obj = fields
1282            .iter()
1283            .find(|f| f.descriptor.name == "invoking_packet")
1284            .unwrap();
1285        let range = match &ip_obj.value {
1286            FieldValue::Object(r) => r.clone(),
1287            other => panic!("expected Object, got {other:?}"),
1288        };
1289        let children = buf.nested_fields(&range);
1290        let src_port = children
1291            .iter()
1292            .find(|f| f.descriptor.name == "src_port")
1293            .unwrap();
1294        assert_eq!(src_port.value, FieldValue::U16(80));
1295        let dst_port = children
1296            .iter()
1297            .find(|f| f.descriptor.name == "dst_port")
1298            .unwrap();
1299        assert_eq!(dst_port.value, FieldValue::U16(49152));
1300    }
1301
1302    #[test]
1303    fn parse_dest_unreachable_other_protocol() {
1304        let mut invoking = build_ipv4_header(1, [10, 0, 0, 1], [10, 0, 0, 2]); // ICMP
1305        invoking.extend_from_slice(&[0x08, 0x00, 0xAB, 0xCD, 0x00, 0x01, 0x00, 0x01]);
1306        let data = build_dest_unreachable(0, &invoking);
1307
1308        let mut buf = DissectBuffer::new();
1309        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1310
1311        let layer = &buf.layers()[0];
1312        let fields = buf.layer_fields(layer);
1313        let ip_obj = fields
1314            .iter()
1315            .find(|f| f.descriptor.name == "invoking_packet")
1316            .unwrap();
1317        let range = match &ip_obj.value {
1318            FieldValue::Object(r) => r.clone(),
1319            other => panic!("expected Object, got {other:?}"),
1320        };
1321        let children = buf.nested_fields(&range);
1322        let transport = children
1323            .iter()
1324            .find(|f| f.descriptor.name == "transport_data")
1325            .unwrap();
1326        assert!(matches!(transport.value, FieldValue::Bytes(_)));
1327    }
1328
1329    #[test]
1330    fn parse_dest_unreachable_truncated_transport() {
1331        // IPv4 header only, no transport data
1332        let invoking = build_ipv4_header(17, [10, 0, 0, 1], [10, 0, 0, 2]);
1333        let data = build_dest_unreachable(3, &invoking);
1334
1335        let mut buf = DissectBuffer::new();
1336        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1337
1338        let layer = &buf.layers()[0];
1339        let fields = buf.layer_fields(layer);
1340        let ip_obj = fields
1341            .iter()
1342            .find(|f| f.descriptor.name == "invoking_packet")
1343            .unwrap();
1344        let range = match &ip_obj.value {
1345            FieldValue::Object(r) => r.clone(),
1346            other => panic!("expected Object, got {other:?}"),
1347        };
1348        let children = buf.nested_fields(&range);
1349        // Should only have the 6 IPv4 header fields, no port fields
1350        assert!(children.iter().all(|f| f.descriptor.name != "src_port"));
1351        assert!(children.iter().all(|f| f.descriptor.name != "dst_port"));
1352        assert!(
1353            children
1354                .iter()
1355                .all(|f| f.descriptor.name != "transport_data")
1356        );
1357    }
1358
1359    // ---- Basic type coverage tests ----
1360
1361    #[test]
1362    fn parse_truncated_header() {
1363        let data = [0x08, 0x00, 0x00, 0x00]; // only 4 bytes, need 8
1364        let mut buf = DissectBuffer::new();
1365        let err = IcmpDissector.dissect(&data, &mut buf, 0).unwrap_err();
1366        assert!(
1367            matches!(
1368                err,
1369                PacketError::Truncated {
1370                    expected: 8,
1371                    actual: 4
1372                }
1373            ),
1374            "expected Truncated(8,4), got {err:?}"
1375        );
1376    }
1377
1378    #[test]
1379    fn parse_echo_request() {
1380        // RFC 792 Echo Request: type=8, code=0, checksum, id=0x1234, seq=0x0001, data
1381        let data: &[u8] = &[
1382            0x08, 0x00, 0xAB, 0xCD, // type=8, code=0, checksum=0xABCD
1383            0x12, 0x34, 0x00, 0x01, // id=0x1234, seq=1
1384            0xDE, 0xAD, // 2 bytes payload
1385        ];
1386        let mut buf = DissectBuffer::new();
1387        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1388
1389        let layer = &buf.layers()[0];
1390        let fields = buf.layer_fields(layer);
1391        assert_eq!(fields[FD_TYPE].value, FieldValue::U8(8));
1392        assert_eq!(fields[FD_CODE].value, FieldValue::U8(0));
1393        assert_eq!(fields[FD_CHECKSUM].value, FieldValue::U16(0xABCD));
1394        let id = fields
1395            .iter()
1396            .find(|f| f.descriptor.name == "identifier")
1397            .unwrap();
1398        assert_eq!(id.value, FieldValue::U16(0x1234));
1399        let seq = fields
1400            .iter()
1401            .find(|f| f.descriptor.name == "sequence_number")
1402            .unwrap();
1403        assert_eq!(seq.value, FieldValue::U16(1));
1404        let payload = fields.iter().find(|f| f.descriptor.name == "data").unwrap();
1405        assert_eq!(payload.value, FieldValue::Bytes(&[0xDE, 0xAD]));
1406    }
1407
1408    #[test]
1409    fn parse_dest_unreachable_fragmentation_needed() {
1410        // RFC 1191 — Type 3, Code 4: Next-Hop MTU at offset 6.
1411        let data: &[u8] = &[
1412            0x03, 0x04, 0x00, 0x00, // type=3, code=4, checksum
1413            0x00, 0x00, 0x05, 0xDC, // unused=0, next_hop_mtu=1500
1414        ];
1415        let mut buf = DissectBuffer::new();
1416        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1417
1418        let layer = &buf.layers()[0];
1419        let fields = buf.layer_fields(layer);
1420        let mtu = fields
1421            .iter()
1422            .find(|f| f.descriptor.name == "next_hop_mtu")
1423            .unwrap();
1424        assert_eq!(mtu.value, FieldValue::U16(1500));
1425    }
1426
1427    #[test]
1428    fn parse_dest_unreachable_with_rfc4884_length() {
1429        // RFC 4884 — Length field at offset 5 (non-zero → exposed).
1430        let mut data = vec![
1431            0x03, 0x01, 0x00, 0x00, // type=3, code=1, checksum
1432            0x00, 0x07, 0x00, 0x00, // unused, length=7 (28 bytes), unused
1433        ];
1434        // Append 28 bytes of invoking data (7 * 4)
1435        data.extend_from_slice(&[0u8; 28]);
1436        let mut buf = DissectBuffer::new();
1437        IcmpDissector.dissect(&data, &mut buf, 0).unwrap();
1438
1439        let layer = &buf.layers()[0];
1440        let fields = buf.layer_fields(layer);
1441        let len = fields
1442            .iter()
1443            .find(|f| f.descriptor.name == "length")
1444            .unwrap();
1445        assert_eq!(len.value, FieldValue::U8(7));
1446    }
1447
1448    #[test]
1449    fn parse_redirect() {
1450        // RFC 792 Redirect — Type 5, Code 1, Gateway at offset 4–7.
1451        let data: &[u8] = &[
1452            0x05, 0x01, 0x00, 0x00, // type=5, code=1, checksum
1453            0xC0, 0xA8, 0x01, 0x01, // gateway=192.168.1.1
1454        ];
1455        let mut buf = DissectBuffer::new();
1456        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1457
1458        let layer = &buf.layers()[0];
1459        let fields = buf.layer_fields(layer);
1460        let gw = fields
1461            .iter()
1462            .find(|f| f.descriptor.name == "gateway")
1463            .unwrap();
1464        assert_eq!(gw.value, FieldValue::Ipv4Addr([192, 168, 1, 1]));
1465    }
1466
1467    #[test]
1468    fn parse_parameter_problem() {
1469        // RFC 792 — Type 12: Pointer at offset 4 (u8), RFC 4884 Length at offset 5.
1470        let data: &[u8] = &[
1471            0x0C, 0x00, 0x00, 0x00, // type=12, code=0, checksum
1472            0x08, 0x03, 0x00, 0x00, // pointer=8, length=3, unused
1473        ];
1474        let mut buf = DissectBuffer::new();
1475        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1476
1477        let layer = &buf.layers()[0];
1478        let fields = buf.layer_fields(layer);
1479        let ptr = fields
1480            .iter()
1481            .find(|f| f.descriptor.name == "pointer")
1482            .unwrap();
1483        assert_eq!(ptr.value, FieldValue::U8(8));
1484        assert_eq!(ptr.descriptor.field_type, FieldType::U8);
1485        let len = fields
1486            .iter()
1487            .find(|f| f.descriptor.name == "length")
1488            .unwrap();
1489        assert_eq!(len.value, FieldValue::U8(3));
1490    }
1491
1492    #[test]
1493    fn parse_timestamp_request() {
1494        // RFC 792 Timestamp Request — 20 bytes total.
1495        let data: &[u8] = &[
1496            0x0D, 0x00, 0x00, 0x00, // type=13, code=0, checksum
1497            0x00, 0x01, 0x00, 0x02, // id=1, seq=2
1498            0x00, 0x01, 0x51, 0x80, // originate=86400 (ms)
1499            0x00, 0x02, 0xA3, 0x00, // receive=172800
1500            0x00, 0x03, 0xF4, 0x80, // transmit=259200
1501        ];
1502        let mut buf = DissectBuffer::new();
1503        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1504
1505        let layer = &buf.layers()[0];
1506        let fields = buf.layer_fields(layer);
1507        let orig = fields
1508            .iter()
1509            .find(|f| f.descriptor.name == "originate_timestamp")
1510            .unwrap();
1511        assert_eq!(orig.value, FieldValue::U32(86400));
1512        let recv = fields
1513            .iter()
1514            .find(|f| f.descriptor.name == "receive_timestamp")
1515            .unwrap();
1516        assert_eq!(recv.value, FieldValue::U32(172800));
1517        let xmit = fields
1518            .iter()
1519            .find(|f| f.descriptor.name == "transmit_timestamp")
1520            .unwrap();
1521        assert_eq!(xmit.value, FieldValue::U32(259200));
1522    }
1523
1524    #[test]
1525    fn parse_timestamp_truncated() {
1526        // Timestamp requires 20 bytes; supply only 16.
1527        let data: &[u8] = &[
1528            0x0D, 0x00, 0x00, 0x00, // type=13
1529            0x00, 0x01, 0x00, 0x02, // id, seq
1530            0x00, 0x00, 0x00, 0x00, // originate
1531            0x00, 0x00, 0x00, 0x00, // receive (no transmit)
1532        ];
1533        let mut buf = DissectBuffer::new();
1534        let err = IcmpDissector.dissect(data, &mut buf, 0).unwrap_err();
1535        assert!(
1536            matches!(
1537                err,
1538                PacketError::Truncated {
1539                    expected: 20,
1540                    actual: 16
1541                }
1542            ),
1543            "expected Truncated(20,16), got {err:?}"
1544        );
1545    }
1546
1547    #[test]
1548    fn parse_address_mask_reply() {
1549        // RFC 950 — Type 18, Address Mask Reply.
1550        let data: &[u8] = &[
1551            0x12, 0x00, 0x00, 0x00, // type=18, code=0, checksum
1552            0x00, 0x0A, 0x00, 0x05, // id=10, seq=5
1553            0xFF, 0xFF, 0xFF, 0x00, // mask=255.255.255.0
1554        ];
1555        let mut buf = DissectBuffer::new();
1556        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1557
1558        let layer = &buf.layers()[0];
1559        let fields = buf.layer_fields(layer);
1560        let mask = fields
1561            .iter()
1562            .find(|f| f.descriptor.name == "address_mask")
1563            .unwrap();
1564        assert_eq!(mask.value, FieldValue::Ipv4Addr([255, 255, 255, 0]));
1565    }
1566
1567    // ---- Bug-regression tests (previously incorrect behavior) ----
1568
1569    #[test]
1570    fn parse_router_advertisement_signed_preference() {
1571        // RFC 1256, Section 3.1 — Preference Level is signed twos-complement.
1572        // Positive preference 0x00000064 = 100.
1573        let data: &[u8] = &[
1574            0x09, 0x00, 0x00, 0x00, // type=9, code=0, checksum
1575            0x01, 0x02, 0x00, 0x1E, // num_addrs=1, addr_entry_size=2, lifetime=30
1576            0xC0, 0xA8, 0x01, 0x01, // router_address=192.168.1.1
1577            0x00, 0x00, 0x00, 0x64, // preference_level=+100
1578        ];
1579        let mut buf = DissectBuffer::new();
1580        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1581
1582        let layer = &buf.layers()[0];
1583        let fields = buf.layer_fields(layer);
1584        let entries = fields
1585            .iter()
1586            .find(|f| f.descriptor.name == "entries")
1587            .unwrap();
1588        let range = match &entries.value {
1589            FieldValue::Array(r) => r.clone(),
1590            other => panic!("expected Array, got {other:?}"),
1591        };
1592        let items = buf.nested_fields(&range);
1593        let obj = items.iter().find(|f| f.value.is_object()).unwrap();
1594        let obj_range = match &obj.value {
1595            FieldValue::Object(r) => r.clone(),
1596            other => panic!("expected Object, got {other:?}"),
1597        };
1598        let children = buf.nested_fields(&obj_range);
1599        let pref = children
1600            .iter()
1601            .find(|f| f.descriptor.name == "preference_level")
1602            .unwrap();
1603        // Must be I32, not U32
1604        assert_eq!(pref.descriptor.field_type, FieldType::I32);
1605        assert_eq!(pref.value, FieldValue::I32(100));
1606    }
1607
1608    #[test]
1609    fn parse_router_advertisement_do_not_use() {
1610        // RFC 1256 — 0x80000000 (i32::MIN) signals "do not use as default router".
1611        let data: &[u8] = &[
1612            0x09, 0x00, 0x00, 0x00, // type=9, code=0, checksum
1613            0x01, 0x02, 0x00, 0x1E, // num_addrs=1, addr_entry_size=2, lifetime=30
1614            0x0A, 0x00, 0x00, 0x01, // router_address=10.0.0.1
1615            0x80, 0x00, 0x00, 0x00, // preference_level=0x80000000 = i32::MIN
1616        ];
1617        let mut buf = DissectBuffer::new();
1618        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1619
1620        let layer = &buf.layers()[0];
1621        let fields = buf.layer_fields(layer);
1622        let entries = fields
1623            .iter()
1624            .find(|f| f.descriptor.name == "entries")
1625            .unwrap();
1626        let range = match &entries.value {
1627            FieldValue::Array(r) => r.clone(),
1628            other => panic!("expected Array, got {other:?}"),
1629        };
1630        let items = buf.nested_fields(&range);
1631        let obj = items.iter().find(|f| f.value.is_object()).unwrap();
1632        let obj_range = match &obj.value {
1633            FieldValue::Object(r) => r.clone(),
1634            other => panic!("expected Object, got {other:?}"),
1635        };
1636        let children = buf.nested_fields(&obj_range);
1637        let pref = children
1638            .iter()
1639            .find(|f| f.descriptor.name == "preference_level")
1640            .unwrap();
1641        assert_eq!(pref.value, FieldValue::I32(i32::MIN));
1642    }
1643
1644    #[test]
1645    fn parse_photuris() {
1646        // RFC 2521, Section 2 — Type 40: Reserved (u16 at 4–5), Pointer (u16 at 6–7).
1647        let data: &[u8] = &[
1648            0x28, 0x01, 0x00, 0x00, // type=40, code=1 (Auth Failed), checksum
1649            0x00, 0x00, 0x00, 0x14, // reserved=0, pointer=20
1650        ];
1651        let mut buf = DissectBuffer::new();
1652        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1653
1654        let layer = &buf.layers()[0];
1655        let fields = buf.layer_fields(layer);
1656
1657        // Verify Reserved field is U16
1658        let reserved = fields
1659            .iter()
1660            .find(|f| f.descriptor.name == "photuris_reserved")
1661            .unwrap();
1662        assert_eq!(reserved.value, FieldValue::U16(0));
1663        assert_eq!(reserved.descriptor.field_type, FieldType::U16);
1664
1665        // Verify Pointer field is U16 (not U8 as it was before the fix)
1666        let ptr = fields
1667            .iter()
1668            .find(|f| f.descriptor.name == "photuris_pointer")
1669            .unwrap();
1670        assert_eq!(ptr.value, FieldValue::U16(20));
1671        assert_eq!(ptr.descriptor.field_type, FieldType::U16);
1672    }
1673
1674    // ---- RFC 8335 Extended Echo ----
1675
1676    #[test]
1677    fn parse_extended_echo_request() {
1678        // RFC 8335, Section 2 — Type 42, Seq=u8 at offset 6, L-bit at offset 7 bit 0.
1679        let data: &[u8] = &[
1680            0x2A, 0x00, 0x00, 0x00, // type=42, code=0, checksum
1681            0xAB, 0xCD, 0x07, 0x01, // id=0xABCD, seq=7, Reserved|L=1 (L-bit set)
1682        ];
1683        let mut buf = DissectBuffer::new();
1684        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1685
1686        let layer = &buf.layers()[0];
1687        let fields = buf.layer_fields(layer);
1688        let id = fields
1689            .iter()
1690            .find(|f| f.descriptor.name == "identifier")
1691            .unwrap();
1692        assert_eq!(id.value, FieldValue::U16(0xABCD));
1693        let seq = fields
1694            .iter()
1695            .find(|f| f.descriptor.name == "sequence_number")
1696            .unwrap();
1697        // Sequence Number is 1 byte (value=7) promoted to U16.
1698        assert_eq!(seq.value, FieldValue::U16(7));
1699        let local = fields
1700            .iter()
1701            .find(|f| f.descriptor.name == "local")
1702            .unwrap();
1703        assert_eq!(local.value, FieldValue::U8(1)); // L-bit set
1704    }
1705
1706    #[test]
1707    fn parse_extended_echo_reply() {
1708        // RFC 8335, Section 3 — Type 43, flags in offset 7:
1709        // State(3 bits)=2 (Reachable), Res(2)=0, A(1)=1, 4(1)=1, 6(1)=0
1710        // Byte 7 = 0b010_00_1_1_0 = 0x46
1711        let data: &[u8] = &[
1712            0x2B, 0x00, 0x00, 0x00, // type=43, code=0, checksum
1713            0x00, 0x01, 0x03, 0x46, // id=1, seq=3, flags=0x46
1714        ];
1715        let mut buf = DissectBuffer::new();
1716        IcmpDissector.dissect(data, &mut buf, 0).unwrap();
1717
1718        let layer = &buf.layers()[0];
1719        let fields = buf.layer_fields(layer);
1720        let state = fields
1721            .iter()
1722            .find(|f| f.descriptor.name == "state")
1723            .unwrap();
1724        assert_eq!(state.value, FieldValue::U8(2)); // Reachable
1725        let active = fields
1726            .iter()
1727            .find(|f| f.descriptor.name == "active")
1728            .unwrap();
1729        assert_eq!(active.value, FieldValue::U8(1));
1730        let ipv4 = fields.iter().find(|f| f.descriptor.name == "ipv4").unwrap();
1731        assert_eq!(ipv4.value, FieldValue::U8(1));
1732        let ipv6 = fields.iter().find(|f| f.descriptor.name == "ipv6").unwrap();
1733        assert_eq!(ipv6.value, FieldValue::U8(0));
1734    }
1735
1736    // ---- RFC 4884 Extension Structure ----
1737
1738    /// Build a Time Exceeded (type 11) packet with a padded-to-128 original
1739    /// datagram followed by the supplied extension structure bytes. The Length
1740    /// field (offset 5) is set to 32 (= 128 / 4 octets) to indicate extensions.
1741    fn build_time_exceeded_with_extensions(ext: &[u8]) -> Vec<u8> {
1742        let mut pkt = Vec::with_capacity(HEADER_SIZE + 128 + ext.len());
1743        pkt.push(11); // type = Time Exceeded
1744        pkt.push(0); // code = TTL exceeded
1745        pkt.extend_from_slice(&[0x00, 0x00]); // checksum
1746        pkt.push(0); // unused
1747        pkt.push(32); // RFC 4884 Length = 32 (32 * 4 = 128 bytes)
1748        pkt.extend_from_slice(&[0x00, 0x00]); // unused
1749        // 128 bytes of padded original datagram (zeros here)
1750        pkt.extend_from_slice(&[0u8; 128]);
1751        pkt.extend_from_slice(ext);
1752        pkt
1753    }
1754
1755    /// Walk `layer.fields -> extensions -> objects` and return the list of
1756    /// direct Object container ranges (skipping past each object's descendants
1757    /// so that nested Objects inside Arrays — e.g. MPLS LSEs — are not
1758    /// mistaken for top-level extension objects).
1759    fn extension_object_ranges(
1760        buf: &DissectBuffer<'_>,
1761        layer_idx: usize,
1762    ) -> Vec<core::ops::Range<u32>> {
1763        let layer = &buf.layers()[layer_idx];
1764        let fields = buf.layer_fields(layer);
1765        let ext = fields
1766            .iter()
1767            .find(|f| f.descriptor.name == "extensions")
1768            .expect("extensions field present");
1769        let ext_range = match &ext.value {
1770            FieldValue::Object(r) => r.clone(),
1771            other => panic!("expected Object, got {other:?}"),
1772        };
1773        let ext_children = buf.nested_fields(&ext_range);
1774        let objects = ext_children
1775            .iter()
1776            .find(|f| f.descriptor.name == "objects")
1777            .expect("objects array");
1778        let objects_range = match &objects.value {
1779            FieldValue::Array(r) => r.clone(),
1780            other => panic!("expected Array, got {other:?}"),
1781        };
1782        let items = buf.nested_fields(&objects_range);
1783        let mut result = Vec::new();
1784        let mut i = 0;
1785        while i < items.len() {
1786            let f = &items[i];
1787            match &f.value {
1788                FieldValue::Object(r) => {
1789                    let descendants = (r.end - r.start) as usize;
1790                    result.push(r.clone());
1791                    i += 1 + descendants;
1792                }
1793                FieldValue::Array(r) => {
1794                    let descendants = (r.end - r.start) as usize;
1795                    i += 1 + descendants;
1796                }
1797                _ => i += 1,
1798            }
1799        }
1800        result
1801    }
1802
1803    #[test]
1804    fn parse_extension_header_fields() {
1805        // RFC 4884 §7 — Extension Header: Version (high nibble) + Reserved (12 bits) + Checksum.
1806        // Build one object (class=99/unknown/1 byte payload) to verify the header is parsed.
1807        let ext = [
1808            0x20, 0x00, 0x12, 0x34, // version=2, reserved=0, checksum=0x1234
1809            0x00, 0x05, 0x63, 0x01, // obj_len=5, class_num=99, c_type=1
1810            0xAA, 0x00, 0x00,
1811            0x00, // 1 payload byte + 3 bytes don't care (length=5 stops here)
1812        ];
1813        let pkt = build_time_exceeded_with_extensions(&ext[..9]); // exact 5-byte object
1814        let mut buf = DissectBuffer::new();
1815        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
1816
1817        let layer = &buf.layers()[0];
1818        let fields = buf.layer_fields(layer);
1819        let ext_field = fields
1820            .iter()
1821            .find(|f| f.descriptor.name == "extensions")
1822            .unwrap();
1823        let ext_range = match &ext_field.value {
1824            FieldValue::Object(r) => r.clone(),
1825            other => panic!("expected Object, got {other:?}"),
1826        };
1827        let children = buf.nested_fields(&ext_range);
1828        let version = children
1829            .iter()
1830            .find(|f| f.descriptor.name == "version")
1831            .unwrap();
1832        assert_eq!(version.value, FieldValue::U8(2));
1833        let reserved = children
1834            .iter()
1835            .find(|f| f.descriptor.name == "reserved")
1836            .unwrap();
1837        assert_eq!(reserved.value, FieldValue::U16(0));
1838        let checksum = children
1839            .iter()
1840            .find(|f| f.descriptor.name == "checksum")
1841            .unwrap();
1842        assert_eq!(checksum.value, FieldValue::U16(0x1234));
1843    }
1844
1845    #[test]
1846    fn parse_extensions_not_parsed_when_length_zero() {
1847        // RFC 4884 §4.5 — With Length=0, the trailing bytes are NOT an Extension
1848        // Structure. push_extensions must not run.
1849        let mut pkt = vec![
1850            11, 0, 0, 0, // type=11, code=0, checksum
1851            0, 0, 0, 0, // unused + length=0 + unused
1852        ];
1853        // 128 bytes that COULD be mistaken for a bogus Extension Structure.
1854        pkt.extend_from_slice(&[0u8; 128]);
1855        let mut buf = DissectBuffer::new();
1856        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
1857
1858        let layer = &buf.layers()[0];
1859        let fields = buf.layer_fields(layer);
1860        assert!(
1861            fields.iter().all(|f| f.descriptor.name != "extensions"),
1862            "extensions must not be present when RFC 4884 Length is 0"
1863        );
1864    }
1865
1866    #[test]
1867    fn parse_extension_mpls_label_stack_class1() {
1868        // RFC 4950 — Class-Num=1, C-Type=1, two label stack entries.
1869        // LSE1: Label=0x12345, TC=5, S=0, TTL=64
1870        //   Bytes: 0x12, 0x34, 0x5A, 0x40 (label[19:0]=0x12345, TC=0b101=5, S=0, TTL=64)
1871        // LSE2: Label=0x00010, TC=0, S=1, TTL=32
1872        //   Bytes: 0x00, 0x01, 0x01, 0x20 (label=0x10, TC=0, S=1, TTL=32)
1873        let ext = [
1874            0x20, 0x00, 0x00, 0x00, // version=2, reserved=0, checksum=0
1875            0x00, 0x0C, 0x01, 0x01, // obj_len=12, class=1 (MPLS), c_type=1
1876            0x12, 0x34, 0x5A, 0x40, // LSE1
1877            0x00, 0x01, 0x01, 0x20, // LSE2
1878        ];
1879        let pkt = build_time_exceeded_with_extensions(&ext);
1880        let mut buf = DissectBuffer::new();
1881        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
1882
1883        let objs = extension_object_ranges(&buf, 0);
1884        assert_eq!(objs.len(), 1, "one MPLS object");
1885        let obj_children = buf.nested_fields(&objs[0]);
1886        let class_num = obj_children
1887            .iter()
1888            .find(|f| f.descriptor.name == "class_num")
1889            .unwrap();
1890        assert_eq!(class_num.value, FieldValue::U8(1));
1891        let mpls_labels = obj_children
1892            .iter()
1893            .find(|f| f.descriptor.name == "mpls_labels")
1894            .unwrap();
1895        let mpls_range = match &mpls_labels.value {
1896            FieldValue::Array(r) => r.clone(),
1897            other => panic!("expected Array, got {other:?}"),
1898        };
1899        let entries: Vec<_> = buf
1900            .nested_fields(&mpls_range)
1901            .iter()
1902            .filter_map(|f| match &f.value {
1903                FieldValue::Object(r) => Some(r.clone()),
1904                _ => None,
1905            })
1906            .collect();
1907        assert_eq!(entries.len(), 2);
1908
1909        // LSE1
1910        let lse1 = buf.nested_fields(&entries[0]);
1911        let l1 = lse1.iter().find(|f| f.descriptor.name == "label").unwrap();
1912        assert_eq!(l1.value, FieldValue::U32(0x12345));
1913        let tc1 = lse1.iter().find(|f| f.descriptor.name == "tc").unwrap();
1914        assert_eq!(tc1.value, FieldValue::U8(5));
1915        let s1 = lse1.iter().find(|f| f.descriptor.name == "s").unwrap();
1916        assert_eq!(s1.value, FieldValue::U8(0));
1917        let ttl1 = lse1.iter().find(|f| f.descriptor.name == "ttl").unwrap();
1918        assert_eq!(ttl1.value, FieldValue::U8(64));
1919
1920        // LSE2 (bottom of stack)
1921        let lse2 = buf.nested_fields(&entries[1]);
1922        let l2 = lse2.iter().find(|f| f.descriptor.name == "label").unwrap();
1923        assert_eq!(l2.value, FieldValue::U32(0x00010));
1924        let s2 = lse2.iter().find(|f| f.descriptor.name == "s").unwrap();
1925        assert_eq!(s2.value, FieldValue::U8(1));
1926        let ttl2 = lse2.iter().find(|f| f.descriptor.name == "ttl").unwrap();
1927        assert_eq!(ttl2.value, FieldValue::U8(32));
1928    }
1929
1930    #[test]
1931    fn parse_extension_interface_info_class2_all_sub_objects() {
1932        // RFC 5837 — Class-Num=2, C-Type encodes:
1933        //   Role=2 (Outgoing IP Interface) -> bits 0-1 = 10 -> 0x80
1934        //   ifIndex=1, IPAddr=1, Name=1, MTU=1 -> 0x0F
1935        //   C-Type = 0x8F = 0b10_00_1_1_1_1
1936        // Sub-objects in order: ifIndex(4) + IP Address(8, IPv4) + Name(8) + MTU(4) = 24 bytes
1937        // Object length = 4 (header) + 24 = 28
1938        let ext = [
1939            0x20, 0x00, 0x00, 0x00, // ext header
1940            0x00, 0x1C, 0x02, 0x8F, // obj_len=28, class=2, c_type=0x8F (Role=2, all bits set)
1941            0x00, 0x00, 0x00, 0x07, // ifIndex=7
1942            0x00, 0x01, 0x00, 0x00, // AFI=1 (IPv4), Reserved=0
1943            0xC0, 0xA8, 0x00, 0x01, // Address=192.168.0.1
1944            0x08, b'e', b't', b'h', // Name Length=8, "eth"
1945            b'0', 0x00, 0x00, 0x00, // "0" + NUL padding (total 8 bytes)
1946            0x00, 0x00, 0x05, 0xDC, // MTU=1500
1947        ];
1948        let pkt = build_time_exceeded_with_extensions(&ext);
1949        let mut buf = DissectBuffer::new();
1950        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
1951
1952        let objs = extension_object_ranges(&buf, 0);
1953        assert_eq!(objs.len(), 1);
1954        let obj = buf.nested_fields(&objs[0]);
1955
1956        let role = obj
1957            .iter()
1958            .find(|f| f.descriptor.name == "interface_role")
1959            .unwrap();
1960        assert_eq!(role.value, FieldValue::U8(2));
1961
1962        let ifi = obj
1963            .iter()
1964            .find(|f| f.descriptor.name == "if_index")
1965            .unwrap();
1966        assert_eq!(ifi.value, FieldValue::U32(7));
1967
1968        let afi = obj.iter().find(|f| f.descriptor.name == "afi").unwrap();
1969        assert_eq!(afi.value, FieldValue::U16(1));
1970
1971        let ip = obj
1972            .iter()
1973            .find(|f| f.descriptor.name == "ipv4_address")
1974            .unwrap();
1975        assert_eq!(ip.value, FieldValue::Ipv4Addr([192, 168, 0, 1]));
1976
1977        let name = obj
1978            .iter()
1979            .find(|f| f.descriptor.name == "interface_name")
1980            .unwrap();
1981        // The name bytes exclude the leading Length octet; trailing NULs remain.
1982        assert_eq!(name.value, FieldValue::Bytes(b"eth0\x00\x00\x00"));
1983
1984        let mtu = obj.iter().find(|f| f.descriptor.name == "mtu").unwrap();
1985        assert_eq!(mtu.value, FieldValue::U32(1500));
1986    }
1987
1988    #[test]
1989    fn parse_extension_interface_info_class2_ipv6_address() {
1990        // RFC 5837 — Class 2, Role=0 (Incoming IP), IPAddr bit only (C-Type=0x04).
1991        // IP Address Sub-Object: AFI=2 (IPv6) + Reserved + 16-byte address = 20 bytes.
1992        // Object length = 4 + 20 = 24.
1993        let ext = [
1994            0x20, 0x00, 0x00, 0x00, // ext header
1995            0x00, 0x18, 0x02, 0x04, // obj_len=24, class=2, c_type=0x04 (Role=0, IPAddr only)
1996            0x00, 0x02, 0x00, 0x00, // AFI=2 (IPv6), Reserved=0
1997            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // 2001:db8::...
1998            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // ...::1
1999        ];
2000        let pkt = build_time_exceeded_with_extensions(&ext);
2001        let mut buf = DissectBuffer::new();
2002        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2003
2004        let objs = extension_object_ranges(&buf, 0);
2005        let obj = buf.nested_fields(&objs[0]);
2006        let role = obj
2007            .iter()
2008            .find(|f| f.descriptor.name == "interface_role")
2009            .unwrap();
2010        assert_eq!(role.value, FieldValue::U8(0));
2011        let ipv6 = obj
2012            .iter()
2013            .find(|f| f.descriptor.name == "ipv6_address")
2014            .unwrap();
2015        let mut expected = [0u8; 16];
2016        expected[0] = 0x20;
2017        expected[1] = 0x01;
2018        expected[2] = 0x0d;
2019        expected[3] = 0xb8;
2020        expected[15] = 0x01;
2021        assert_eq!(ipv6.value, FieldValue::Ipv6Addr(expected));
2022    }
2023
2024    #[test]
2025    fn parse_extension_interface_id_class3_by_index() {
2026        // RFC 8335 §2.1 — Class-Num=3, C-Type=2 (by index): 4-byte ifIndex.
2027        let ext = [
2028            0x20, 0x00, 0x00, 0x00, // ext header
2029            0x00, 0x08, 0x03, 0x02, // obj_len=8, class=3, c_type=2
2030            0x00, 0x00, 0x00, 0x2A, // ifIndex=42
2031        ];
2032        let pkt = build_time_exceeded_with_extensions(&ext);
2033        let mut buf = DissectBuffer::new();
2034        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2035
2036        let objs = extension_object_ranges(&buf, 0);
2037        let obj = buf.nested_fields(&objs[0]);
2038        let ifi = obj
2039            .iter()
2040            .find(|f| f.descriptor.name == "if_index")
2041            .unwrap();
2042        assert_eq!(ifi.value, FieldValue::U32(42));
2043    }
2044
2045    #[test]
2046    fn parse_extension_interface_id_class3_by_name() {
2047        // RFC 8335 §2.1 — Class-Num=3, C-Type=1: body is raw name, NUL-padded.
2048        let ext = [
2049            0x20, 0x00, 0x00, 0x00, // ext header
2050            0x00, 0x0C, 0x03, 0x01, // obj_len=12, class=3, c_type=1
2051            b'e', b'n', b'0', 0x00, // "en0" + NUL padding
2052            0x00, 0x00, 0x00, 0x00, // more NUL padding (to 8-byte payload)
2053        ];
2054        let pkt = build_time_exceeded_with_extensions(&ext);
2055        let mut buf = DissectBuffer::new();
2056        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2057
2058        let objs = extension_object_ranges(&buf, 0);
2059        let obj = buf.nested_fields(&objs[0]);
2060        let name = obj
2061            .iter()
2062            .find(|f| f.descriptor.name == "interface_name")
2063            .unwrap();
2064        assert_eq!(name.value, FieldValue::Bytes(b"en0\x00\x00\x00\x00\x00"));
2065    }
2066
2067    #[test]
2068    fn parse_extension_interface_id_class3_by_address_ipv4() {
2069        // RFC 8335 §2.1 — Class-Num=3, C-Type=3: AFI(u16) + AddrLen(u8) + Reserved(u8) + Address.
2070        let ext = [
2071            0x20, 0x00, 0x00, 0x00, // ext header
2072            0x00, 0x0C, 0x03, 0x03, // obj_len=12, class=3, c_type=3
2073            0x00, 0x01, 0x04, 0x00, // AFI=1 (IPv4), AddrLen=4, Reserved=0
2074            0x0A, 0x00, 0x00, 0x01, // 10.0.0.1
2075        ];
2076        let pkt = build_time_exceeded_with_extensions(&ext);
2077        let mut buf = DissectBuffer::new();
2078        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2079
2080        let objs = extension_object_ranges(&buf, 0);
2081        let obj = buf.nested_fields(&objs[0]);
2082        let afi = obj.iter().find(|f| f.descriptor.name == "afi").unwrap();
2083        assert_eq!(afi.value, FieldValue::U16(1));
2084        let alen = obj
2085            .iter()
2086            .find(|f| f.descriptor.name == "address_length")
2087            .unwrap();
2088        assert_eq!(alen.value, FieldValue::U8(4));
2089        let ip = obj
2090            .iter()
2091            .find(|f| f.descriptor.name == "ipv4_address")
2092            .unwrap();
2093        assert_eq!(ip.value, FieldValue::Ipv4Addr([10, 0, 0, 1]));
2094    }
2095
2096    #[test]
2097    fn parse_extension_interface_id_class3_by_address_ipv6() {
2098        // RFC 8335 §2.1 — Class-Num=3, C-Type=3, AFI=2, IPv6 address.
2099        // Object length = 4 (header) + 4 (AFI/AddrLen/Reserved) + 16 (address) = 24
2100        let ext = [
2101            0x20, 0x00, 0x00, 0x00, // ext header
2102            0x00, 0x18, 0x03, 0x03, // obj_len=24, class=3, c_type=3
2103            0x00, 0x02, 0x10, 0x00, // AFI=2 (IPv6), AddrLen=16, Reserved=0
2104            0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fe80::...
2105            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, // ...::5
2106        ];
2107        let pkt = build_time_exceeded_with_extensions(&ext);
2108        let mut buf = DissectBuffer::new();
2109        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2110
2111        let objs = extension_object_ranges(&buf, 0);
2112        let obj = buf.nested_fields(&objs[0]);
2113        let ipv6 = obj
2114            .iter()
2115            .find(|f| f.descriptor.name == "ipv6_address")
2116            .unwrap();
2117        let mut expected = [0u8; 16];
2118        expected[0] = 0xfe;
2119        expected[1] = 0x80;
2120        expected[15] = 0x05;
2121        assert_eq!(ipv6.value, FieldValue::Ipv6Addr(expected));
2122    }
2123
2124    #[test]
2125    fn parse_extension_unknown_class_preserves_payload() {
2126        // Unknown class-num — raw payload bytes must be captured under the
2127        // object's `payload` child.
2128        let ext = [
2129            0x20, 0x00, 0x00, 0x00, // ext header
2130            0x00, 0x08, 0x7F, 0x05, // obj_len=8, class=127 (unknown), c_type=5
2131            0xDE, 0xAD, 0xBE, 0xEF, // payload
2132        ];
2133        let pkt = build_time_exceeded_with_extensions(&ext);
2134        let mut buf = DissectBuffer::new();
2135        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2136
2137        let objs = extension_object_ranges(&buf, 0);
2138        let obj = buf.nested_fields(&objs[0]);
2139        let payload = obj.iter().find(|f| f.descriptor.name == "payload").unwrap();
2140        assert_eq!(payload.value, FieldValue::Bytes(&[0xDE, 0xAD, 0xBE, 0xEF]));
2141    }
2142
2143    #[test]
2144    fn parse_extension_malformed_object_length_stops_parsing() {
2145        // Postel's Law — an object with length < 4 must stop the loop without
2146        // panicking, and must not emit a malformed object child.
2147        let ext = [
2148            0x20, 0x00, 0x00, 0x00, // ext header
2149            0x00, 0x03, 0x01, 0x01, // obj_len=3 (< 4) -> malformed
2150            0xAA, 0xBB, 0xCC, 0xDD, // tail (ignored)
2151        ];
2152        let pkt = build_time_exceeded_with_extensions(&ext);
2153        let mut buf = DissectBuffer::new();
2154        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2155
2156        // The extensions object is present (header parsed) but the objects
2157        // array should be empty.
2158        let objs = extension_object_ranges(&buf, 0);
2159        assert!(
2160            objs.is_empty(),
2161            "malformed object length must not yield objects"
2162        );
2163    }
2164
2165    #[test]
2166    fn parse_extended_echo_request_with_interface_id_extension() {
2167        // RFC 8335 §2 — Body of Type 42 is itself an Extension Structure
2168        // carrying the Interface Identification Object (Class-Num 3).
2169        let mut pkt = vec![
2170            42, 0, 0x00, 0x00, // type=42, code=0, checksum
2171            0x00, 0x01, 0x01, 0x01, // identifier=1, seq=1, L-bit set
2172        ];
2173        let ext = [
2174            0x20, 0x00, 0x00, 0x00, // ext header
2175            0x00, 0x08, 0x03, 0x02, // obj_len=8, class=3, c_type=2 (ByIndex)
2176            0x00, 0x00, 0x00, 0x03, // ifIndex=3
2177        ];
2178        pkt.extend_from_slice(&ext);
2179
2180        let mut buf = DissectBuffer::new();
2181        IcmpDissector.dissect(&pkt, &mut buf, 0).unwrap();
2182
2183        let objs = extension_object_ranges(&buf, 0);
2184        assert_eq!(objs.len(), 1);
2185        let obj = buf.nested_fields(&objs[0]);
2186        let ifi = obj
2187            .iter()
2188            .find(|f| f.descriptor.name == "if_index")
2189            .unwrap();
2190        assert_eq!(ifi.value, FieldValue::U32(3));
2191    }
2192
2193    #[test]
2194    fn rfc4884_extension_offset_padding_semantics() {
2195        // Length=32 means 128 octets (already padded); ext starts at 8+128=136.
2196        assert_eq!(rfc4884_extension_offset(200, 32), Some(HEADER_SIZE + 128));
2197        // Length=10 means 40 octets; MUST be padded to 128 before extensions.
2198        assert_eq!(rfc4884_extension_offset(200, 10), Some(HEADER_SIZE + 128));
2199        // Length=40 means 160 octets; no padding because already >= 128.
2200        assert_eq!(rfc4884_extension_offset(300, 40), Some(HEADER_SIZE + 160));
2201        // Length=0 -> no extensions parsed.
2202        assert_eq!(rfc4884_extension_offset(200, 0), None);
2203        // Not enough trailing bytes for even an Extension Header -> None.
2204        assert_eq!(rfc4884_extension_offset(100, 32), None);
2205    }
2206}