pcapsql_core/protocol/
icmpv6.rs

1//! ICMPv6 protocol parser with NDP and MLD support.
2
3use std::net::Ipv6Addr;
4
5use compact_str::CompactString;
6use smallvec::SmallVec;
7
8use super::{FieldValue, ParseContext, ParseResult, Protocol};
9use crate::schema::{DataKind, FieldDescriptor};
10
11/// IP protocol number for ICMPv6.
12pub const IP_PROTO_ICMPV6: u8 = 58;
13
14/// ICMPv6 type constants.
15pub mod icmpv6_type {
16    // Error messages
17    pub const DESTINATION_UNREACHABLE: u8 = 1;
18    pub const PACKET_TOO_BIG: u8 = 2;
19    pub const TIME_EXCEEDED: u8 = 3;
20    pub const PARAMETER_PROBLEM: u8 = 4;
21
22    // Informational messages
23    pub const ECHO_REQUEST: u8 = 128;
24    pub const ECHO_REPLY: u8 = 129;
25
26    // MLD messages
27    pub const MLD_QUERY: u8 = 130;
28    pub const MLDV1_REPORT: u8 = 131;
29    pub const MLDV1_DONE: u8 = 132;
30    pub const MLDV2_REPORT: u8 = 143;
31
32    // NDP messages
33    pub const ROUTER_SOLICITATION: u8 = 133;
34    pub const ROUTER_ADVERTISEMENT: u8 = 134;
35    pub const NEIGHBOR_SOLICITATION: u8 = 135;
36    pub const NEIGHBOR_ADVERTISEMENT: u8 = 136;
37    pub const REDIRECT: u8 = 137;
38}
39
40/// NDP option type constants.
41pub mod ndp_option {
42    pub const SOURCE_LINK_LAYER_ADDR: u8 = 1;
43    pub const TARGET_LINK_LAYER_ADDR: u8 = 2;
44    pub const PREFIX_INFO: u8 = 3;
45    pub const MTU: u8 = 5;
46}
47
48/// ICMPv6 protocol parser.
49#[derive(Debug, Clone, Copy)]
50pub struct Icmpv6Protocol;
51
52impl Protocol for Icmpv6Protocol {
53    fn name(&self) -> &'static str {
54        "icmpv6"
55    }
56
57    fn display_name(&self) -> &'static str {
58        "ICMPv6"
59    }
60
61    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
62        // Match when IPv6 next_header equals 58 (ICMPv6)
63        match context.hint("ip_protocol") {
64            Some(proto) if proto == IP_PROTO_ICMPV6 as u64 => {
65                // Only parse for IPv6
66                match context.hint("ip_version") {
67                    Some(6) => Some(100),
68                    _ => None,
69                }
70            }
71            _ => None,
72        }
73    }
74
75    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
76        // ICMPv6 header is at least 4 bytes
77        if data.len() < 4 {
78            return ParseResult::error(
79                format!("ICMPv6 header too short: {} bytes", data.len()),
80                data,
81            );
82        }
83
84        let mut fields = SmallVec::new();
85
86        let icmpv6_type = data[0];
87        let icmpv6_code = data[1];
88        let checksum = u16::from_be_bytes([data[2], data[3]]);
89
90        fields.push(("type", FieldValue::UInt8(icmpv6_type)));
91        fields.push(("code", FieldValue::UInt8(icmpv6_code)));
92        fields.push(("checksum", FieldValue::UInt16(checksum)));
93
94        // Add type name
95        let type_name = get_type_name(icmpv6_type);
96        fields.push(("type_name", FieldValue::Str(type_name)));
97
98        // Parse type-specific fields
99        let consumed = match icmpv6_type {
100            icmpv6_type::ECHO_REQUEST | icmpv6_type::ECHO_REPLY => {
101                parse_echo(&data[4..], &mut fields)
102            }
103            icmpv6_type::PACKET_TOO_BIG => parse_packet_too_big(&data[4..], &mut fields),
104            icmpv6_type::PARAMETER_PROBLEM => parse_parameter_problem(&data[4..], &mut fields),
105            icmpv6_type::DESTINATION_UNREACHABLE | icmpv6_type::TIME_EXCEEDED => {
106                // These have 4 bytes of unused data before the invoking packet
107                if data.len() >= 8 {
108                    8
109                } else {
110                    4
111                }
112            }
113            // NDP messages
114            icmpv6_type::ROUTER_SOLICITATION => parse_router_solicitation(&data[4..], &mut fields),
115            icmpv6_type::ROUTER_ADVERTISEMENT => {
116                parse_router_advertisement(&data[4..], &mut fields)
117            }
118            icmpv6_type::NEIGHBOR_SOLICITATION => {
119                parse_neighbor_solicitation(&data[4..], &mut fields)
120            }
121            icmpv6_type::NEIGHBOR_ADVERTISEMENT => {
122                parse_neighbor_advertisement(&data[4..], &mut fields)
123            }
124            icmpv6_type::REDIRECT => parse_redirect(&data[4..], &mut fields),
125            // MLD messages
126            icmpv6_type::MLD_QUERY | icmpv6_type::MLDV1_REPORT | icmpv6_type::MLDV1_DONE => {
127                parse_mldv1(&data[4..], &mut fields)
128            }
129            icmpv6_type::MLDV2_REPORT => parse_mldv2_report(&data[4..], &mut fields),
130            _ => 4, // Just consume the header
131        };
132
133        ParseResult::success(fields, &data[consumed..], SmallVec::new())
134    }
135
136    fn schema_fields(&self) -> Vec<FieldDescriptor> {
137        vec![
138            // Core ICMPv6 fields
139            FieldDescriptor::new("icmpv6.type", DataKind::UInt8).set_nullable(true),
140            FieldDescriptor::new("icmpv6.code", DataKind::UInt8).set_nullable(true),
141            FieldDescriptor::new("icmpv6.checksum", DataKind::UInt16).set_nullable(true),
142            FieldDescriptor::new("icmpv6.type_name", DataKind::String).set_nullable(true),
143            // Echo request/reply
144            FieldDescriptor::new("icmpv6.echo_id", DataKind::UInt16).set_nullable(true),
145            FieldDescriptor::new("icmpv6.echo_seq", DataKind::UInt16).set_nullable(true),
146            // Packet Too Big
147            FieldDescriptor::new("icmpv6.mtu", DataKind::UInt32).set_nullable(true),
148            // Parameter Problem
149            FieldDescriptor::new("icmpv6.pointer", DataKind::UInt32).set_nullable(true),
150            // NDP common
151            FieldDescriptor::new("icmpv6.ndp_target_address", DataKind::String).set_nullable(true),
152            // Router Advertisement
153            FieldDescriptor::new("icmpv6.ndp_cur_hop_limit", DataKind::UInt8).set_nullable(true),
154            FieldDescriptor::new("icmpv6.ndp_managed_flag", DataKind::Bool).set_nullable(true),
155            FieldDescriptor::new("icmpv6.ndp_other_flag", DataKind::Bool).set_nullable(true),
156            FieldDescriptor::new("icmpv6.ndp_router_lifetime", DataKind::UInt16).set_nullable(true),
157            FieldDescriptor::new("icmpv6.ndp_reachable_time", DataKind::UInt32).set_nullable(true),
158            FieldDescriptor::new("icmpv6.ndp_retrans_timer", DataKind::UInt32).set_nullable(true),
159            // Neighbor Advertisement
160            FieldDescriptor::new("icmpv6.ndp_router_flag", DataKind::Bool).set_nullable(true),
161            FieldDescriptor::new("icmpv6.ndp_solicited_flag", DataKind::Bool).set_nullable(true),
162            FieldDescriptor::new("icmpv6.ndp_override_flag", DataKind::Bool).set_nullable(true),
163            // NDP Options
164            FieldDescriptor::new("icmpv6.ndp_source_mac", DataKind::String).set_nullable(true),
165            FieldDescriptor::new("icmpv6.ndp_target_mac", DataKind::String).set_nullable(true),
166            FieldDescriptor::new("icmpv6.ndp_prefix", DataKind::String).set_nullable(true),
167            FieldDescriptor::new("icmpv6.ndp_prefix_length", DataKind::UInt8).set_nullable(true),
168            // MLD
169            FieldDescriptor::new("icmpv6.mld_max_response_delay", DataKind::UInt16)
170                .set_nullable(true),
171            FieldDescriptor::new("icmpv6.mld_multicast_address", DataKind::String)
172                .set_nullable(true),
173            FieldDescriptor::new("icmpv6.mld_num_group_records", DataKind::UInt16)
174                .set_nullable(true),
175        ]
176    }
177
178    fn dependencies(&self) -> &'static [&'static str] {
179        &["ipv6"]
180    }
181}
182
183/// Get ICMPv6 type name.
184fn get_type_name(icmpv6_type: u8) -> &'static str {
185    match icmpv6_type {
186        icmpv6_type::DESTINATION_UNREACHABLE => "Destination Unreachable",
187        icmpv6_type::PACKET_TOO_BIG => "Packet Too Big",
188        icmpv6_type::TIME_EXCEEDED => "Time Exceeded",
189        icmpv6_type::PARAMETER_PROBLEM => "Parameter Problem",
190        icmpv6_type::ECHO_REQUEST => "Echo Request",
191        icmpv6_type::ECHO_REPLY => "Echo Reply",
192        icmpv6_type::MLD_QUERY => "MLD Query",
193        icmpv6_type::MLDV1_REPORT => "MLDv1 Report",
194        icmpv6_type::MLDV1_DONE => "MLDv1 Done",
195        icmpv6_type::MLDV2_REPORT => "MLDv2 Report",
196        icmpv6_type::ROUTER_SOLICITATION => "Router Solicitation",
197        icmpv6_type::ROUTER_ADVERTISEMENT => "Router Advertisement",
198        icmpv6_type::NEIGHBOR_SOLICITATION => "Neighbor Solicitation",
199        icmpv6_type::NEIGHBOR_ADVERTISEMENT => "Neighbor Advertisement",
200        icmpv6_type::REDIRECT => "Redirect",
201        _ => "Unknown",
202    }
203}
204
205/// Parse Echo Request/Reply (types 128/129).
206fn parse_echo(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) -> usize {
207    if data.len() < 4 {
208        return 4;
209    }
210    let id = u16::from_be_bytes([data[0], data[1]]);
211    let seq = u16::from_be_bytes([data[2], data[3]]);
212    fields.push(("echo_id", FieldValue::UInt16(id)));
213    fields.push(("echo_seq", FieldValue::UInt16(seq)));
214    8 // 4 bytes header + 4 bytes echo data
215}
216
217/// Parse Packet Too Big (type 2).
218fn parse_packet_too_big(
219    data: &[u8],
220    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
221) -> usize {
222    if data.len() < 4 {
223        return 4;
224    }
225    let mtu = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
226    fields.push(("mtu", FieldValue::UInt32(mtu)));
227    8 // 4 bytes header + 4 bytes MTU
228}
229
230/// Parse Parameter Problem (type 4).
231fn parse_parameter_problem(
232    data: &[u8],
233    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
234) -> usize {
235    if data.len() < 4 {
236        return 4;
237    }
238    let pointer = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
239    fields.push(("pointer", FieldValue::UInt32(pointer)));
240    8 // 4 bytes header + 4 bytes pointer
241}
242
243/// Parse Router Solicitation (type 133).
244fn parse_router_solicitation(
245    data: &[u8],
246    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
247) -> usize {
248    // Router Solicitation has 4 bytes reserved, then options
249    if data.len() < 4 {
250        return 4;
251    }
252    let mut offset = 4; // Skip reserved bytes
253    parse_ndp_options(&data[4..], fields);
254    offset += data.len().saturating_sub(4);
255    4 + offset.min(data.len())
256}
257
258/// Parse Router Advertisement (type 134).
259fn parse_router_advertisement(
260    data: &[u8],
261    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
262) -> usize {
263    // RA has 12 bytes of fixed data after the ICMPv6 header
264    if data.len() < 12 {
265        return 4 + data.len();
266    }
267
268    let cur_hop_limit = data[0];
269    let flags = data[1];
270    let router_lifetime = u16::from_be_bytes([data[2], data[3]]);
271    let reachable_time = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
272    let retrans_timer = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
273
274    fields.push(("ndp_cur_hop_limit", FieldValue::UInt8(cur_hop_limit)));
275    fields.push(("ndp_managed_flag", FieldValue::Bool((flags & 0x80) != 0)));
276    fields.push(("ndp_other_flag", FieldValue::Bool((flags & 0x40) != 0)));
277    fields.push(("ndp_router_lifetime", FieldValue::UInt16(router_lifetime)));
278    fields.push(("ndp_reachable_time", FieldValue::UInt32(reachable_time)));
279    fields.push(("ndp_retrans_timer", FieldValue::UInt32(retrans_timer)));
280
281    // Parse options
282    if data.len() > 12 {
283        parse_ndp_options(&data[12..], fields);
284    }
285
286    4 + data.len()
287}
288
289/// Parse Neighbor Solicitation (type 135).
290fn parse_neighbor_solicitation(
291    data: &[u8],
292    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
293) -> usize {
294    // NS has 4 bytes reserved + 16 bytes target address
295    if data.len() < 20 {
296        return 4 + data.len();
297    }
298
299    // Skip 4 bytes reserved
300    let target = format_ipv6(&data[4..20]);
301    fields.push((
302        "ndp_target_address",
303        FieldValue::OwnedString(CompactString::new(target)),
304    ));
305
306    // Parse options
307    if data.len() > 20 {
308        parse_ndp_options(&data[20..], fields);
309    }
310
311    4 + data.len()
312}
313
314/// Parse Neighbor Advertisement (type 136).
315fn parse_neighbor_advertisement(
316    data: &[u8],
317    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
318) -> usize {
319    // NA has 4 bytes flags + 16 bytes target address
320    if data.len() < 20 {
321        return 4 + data.len();
322    }
323
324    let flags = data[0];
325    fields.push(("ndp_router_flag", FieldValue::Bool((flags & 0x80) != 0)));
326    fields.push(("ndp_solicited_flag", FieldValue::Bool((flags & 0x40) != 0)));
327    fields.push(("ndp_override_flag", FieldValue::Bool((flags & 0x20) != 0)));
328
329    let target = format_ipv6(&data[4..20]);
330    fields.push((
331        "ndp_target_address",
332        FieldValue::OwnedString(CompactString::new(target)),
333    ));
334
335    // Parse options
336    if data.len() > 20 {
337        parse_ndp_options(&data[20..], fields);
338    }
339
340    4 + data.len()
341}
342
343/// Parse Redirect (type 137).
344fn parse_redirect(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) -> usize {
345    // Redirect has 4 bytes reserved + 16 bytes target + 16 bytes destination
346    if data.len() < 36 {
347        return 4 + data.len();
348    }
349
350    // Skip 4 bytes reserved
351    let target = format_ipv6(&data[4..20]);
352    fields.push((
353        "ndp_target_address",
354        FieldValue::OwnedString(CompactString::new(target)),
355    ));
356
357    // Parse options
358    if data.len() > 36 {
359        parse_ndp_options(&data[36..], fields);
360    }
361
362    4 + data.len()
363}
364
365/// Parse NDP options (TLV format).
366fn parse_ndp_options(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
367    let mut offset = 0;
368
369    while offset + 2 <= data.len() {
370        let opt_type = data[offset];
371        let opt_len = data[offset + 1] as usize * 8; // Length is in units of 8 bytes
372
373        if opt_len == 0 || offset + opt_len > data.len() {
374            break;
375        }
376
377        match opt_type {
378            ndp_option::SOURCE_LINK_LAYER_ADDR => {
379                if opt_len >= 8 {
380                    let mac = format_mac(&data[offset + 2..offset + 8]);
381                    fields.push((
382                        "ndp_source_mac",
383                        FieldValue::OwnedString(CompactString::new(mac)),
384                    ));
385                }
386            }
387            ndp_option::TARGET_LINK_LAYER_ADDR => {
388                if opt_len >= 8 {
389                    let mac = format_mac(&data[offset + 2..offset + 8]);
390                    fields.push((
391                        "ndp_target_mac",
392                        FieldValue::OwnedString(CompactString::new(mac)),
393                    ));
394                }
395            }
396            ndp_option::PREFIX_INFO => {
397                // Prefix Information option is 32 bytes
398                if opt_len >= 32 {
399                    let prefix_len = data[offset + 2];
400                    let prefix = format_ipv6(&data[offset + 16..offset + 32]);
401                    fields.push(("ndp_prefix_length", FieldValue::UInt8(prefix_len)));
402                    fields.push((
403                        "ndp_prefix",
404                        FieldValue::OwnedString(CompactString::new(prefix)),
405                    ));
406                }
407            }
408            ndp_option::MTU => {
409                if opt_len >= 8 {
410                    let mtu = u32::from_be_bytes([
411                        data[offset + 4],
412                        data[offset + 5],
413                        data[offset + 6],
414                        data[offset + 7],
415                    ]);
416                    fields.push(("mtu", FieldValue::UInt32(mtu)));
417                }
418            }
419            _ => {
420                // Unknown option, skip it
421            }
422        }
423
424        offset += opt_len;
425    }
426}
427
428/// Parse MLDv1 message (types 130, 131, 132).
429fn parse_mldv1(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) -> usize {
430    // MLDv1 has 2 bytes max response delay + 2 bytes reserved + 16 bytes multicast address
431    if data.len() < 20 {
432        return 4 + data.len();
433    }
434
435    let max_response_delay = u16::from_be_bytes([data[0], data[1]]);
436    fields.push((
437        "mld_max_response_delay",
438        FieldValue::UInt16(max_response_delay),
439    ));
440
441    // Skip 2 bytes reserved
442    let multicast_addr = format_ipv6(&data[4..20]);
443    fields.push((
444        "mld_multicast_address",
445        FieldValue::OwnedString(CompactString::new(multicast_addr)),
446    ));
447
448    4 + 20 // header + MLDv1 message body
449}
450
451/// Parse MLDv2 Report (type 143).
452fn parse_mldv2_report(
453    data: &[u8],
454    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
455) -> usize {
456    // MLDv2 Report has 2 bytes reserved + 2 bytes number of group records
457    if data.len() < 4 {
458        return 4 + data.len();
459    }
460
461    // Skip 2 bytes reserved
462    let num_group_records = u16::from_be_bytes([data[2], data[3]]);
463    fields.push((
464        "mld_num_group_records",
465        FieldValue::UInt16(num_group_records),
466    ));
467
468    4 + data.len() // Consume all remaining data
469}
470
471/// Format an IPv6 address from bytes.
472fn format_ipv6(bytes: &[u8]) -> String {
473    if bytes.len() >= 16 {
474        let mut arr = [0u8; 16];
475        arr.copy_from_slice(&bytes[..16]);
476        Ipv6Addr::from(arr).to_string()
477    } else {
478        "::".to_string()
479    }
480}
481
482/// Format a MAC address from bytes.
483fn format_mac(bytes: &[u8]) -> String {
484    if bytes.len() >= 6 {
485        format!(
486            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
487            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
488        )
489    } else {
490        String::new()
491    }
492}
493
494#[cfg(test)]
495mod tests {
496    use super::*;
497
498    fn create_context_icmpv6() -> ParseContext {
499        let mut context = ParseContext::new(1);
500        context.insert_hint("ip_protocol", IP_PROTO_ICMPV6 as u64);
501        context.insert_hint("ip_version", 6);
502        context
503    }
504
505    #[test]
506    fn test_can_parse_with_icmpv6() {
507        let parser = Icmpv6Protocol;
508        let context = create_context_icmpv6();
509        assert!(parser.can_parse(&context).is_some());
510    }
511
512    #[test]
513    fn test_cannot_parse_without_ipv6() {
514        let parser = Icmpv6Protocol;
515        let mut context = ParseContext::new(1);
516        context.insert_hint("ip_protocol", IP_PROTO_ICMPV6 as u64);
517        context.insert_hint("ip_version", 4);
518        assert!(parser.can_parse(&context).is_none());
519    }
520
521    #[test]
522    fn test_cannot_parse_without_hint() {
523        let parser = Icmpv6Protocol;
524        let context = ParseContext::new(1);
525        assert!(parser.can_parse(&context).is_none());
526    }
527
528    #[test]
529    fn test_parse_echo_request() {
530        let parser = Icmpv6Protocol;
531        let context = create_context_icmpv6();
532
533        // ICMPv6 Echo Request
534        let data = [
535            0x80, // Type: Echo Request (128)
536            0x00, // Code: 0
537            0x12, 0x34, // Checksum
538            0x00, 0x01, // Identifier: 1
539            0x00, 0x02, // Sequence: 2
540        ];
541
542        let result = parser.parse(&data, &context);
543
544        assert!(result.is_ok());
545        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(128)));
546        assert_eq!(result.get("code"), Some(&FieldValue::UInt8(0)));
547        assert_eq!(result.get("echo_id"), Some(&FieldValue::UInt16(1)));
548        assert_eq!(result.get("echo_seq"), Some(&FieldValue::UInt16(2)));
549        assert_eq!(
550            result.get("type_name"),
551            Some(&FieldValue::Str("Echo Request"))
552        );
553    }
554
555    #[test]
556    fn test_parse_echo_reply() {
557        let parser = Icmpv6Protocol;
558        let context = create_context_icmpv6();
559
560        let data = [
561            0x81, // Type: Echo Reply (129)
562            0x00, // Code: 0
563            0xab, 0xcd, // Checksum
564            0x12, 0x34, // Identifier: 0x1234
565            0x00, 0x0a, // Sequence: 10
566        ];
567
568        let result = parser.parse(&data, &context);
569
570        assert!(result.is_ok());
571        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(129)));
572        assert_eq!(result.get("echo_id"), Some(&FieldValue::UInt16(0x1234)));
573        assert_eq!(result.get("echo_seq"), Some(&FieldValue::UInt16(10)));
574        assert_eq!(
575            result.get("type_name"),
576            Some(&FieldValue::Str("Echo Reply"))
577        );
578    }
579
580    #[test]
581    fn test_parse_destination_unreachable() {
582        let parser = Icmpv6Protocol;
583        let context = create_context_icmpv6();
584
585        let data = [
586            0x01, // Type: Destination Unreachable (1)
587            0x04, // Code: Port Unreachable
588            0x00, 0x00, // Checksum
589            0x00, 0x00, 0x00, 0x00, // Unused
590        ];
591
592        let result = parser.parse(&data, &context);
593
594        assert!(result.is_ok());
595        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(1)));
596        assert_eq!(result.get("code"), Some(&FieldValue::UInt8(4)));
597        assert_eq!(
598            result.get("type_name"),
599            Some(&FieldValue::Str("Destination Unreachable"))
600        );
601    }
602
603    #[test]
604    fn test_parse_packet_too_big() {
605        let parser = Icmpv6Protocol;
606        let context = create_context_icmpv6();
607
608        let data = [
609            0x02, // Type: Packet Too Big (2)
610            0x00, // Code: 0
611            0x00, 0x00, // Checksum
612            0x00, 0x00, 0x05, 0xdc, // MTU: 1500
613        ];
614
615        let result = parser.parse(&data, &context);
616
617        assert!(result.is_ok());
618        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(2)));
619        assert_eq!(result.get("mtu"), Some(&FieldValue::UInt32(1500)));
620        assert_eq!(
621            result.get("type_name"),
622            Some(&FieldValue::Str("Packet Too Big"))
623        );
624    }
625
626    #[test]
627    fn test_parse_time_exceeded() {
628        let parser = Icmpv6Protocol;
629        let context = create_context_icmpv6();
630
631        let data = [
632            0x03, // Type: Time Exceeded (3)
633            0x00, // Code: Hop limit exceeded
634            0x00, 0x00, // Checksum
635            0x00, 0x00, 0x00, 0x00, // Unused
636        ];
637
638        let result = parser.parse(&data, &context);
639
640        assert!(result.is_ok());
641        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(3)));
642        assert_eq!(
643            result.get("type_name"),
644            Some(&FieldValue::Str("Time Exceeded"))
645        );
646    }
647
648    #[test]
649    fn test_parse_parameter_problem() {
650        let parser = Icmpv6Protocol;
651        let context = create_context_icmpv6();
652
653        let data = [
654            0x04, // Type: Parameter Problem (4)
655            0x02, // Code: Unrecognized next header
656            0x00, 0x00, // Checksum
657            0x00, 0x00, 0x00, 0x28, // Pointer: 40
658        ];
659
660        let result = parser.parse(&data, &context);
661
662        assert!(result.is_ok());
663        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(4)));
664        assert_eq!(result.get("code"), Some(&FieldValue::UInt8(2)));
665        assert_eq!(result.get("pointer"), Some(&FieldValue::UInt32(40)));
666        assert_eq!(
667            result.get("type_name"),
668            Some(&FieldValue::Str("Parameter Problem"))
669        );
670    }
671
672    #[test]
673    fn test_parse_too_short() {
674        let parser = Icmpv6Protocol;
675        let context = create_context_icmpv6();
676
677        let data = [0x80, 0x00]; // Only 2 bytes
678
679        let result = parser.parse(&data, &context);
680
681        assert!(!result.is_ok());
682        assert!(result.error.is_some());
683    }
684
685    #[test]
686    fn test_schema_fields() {
687        let parser = Icmpv6Protocol;
688        let fields = parser.schema_fields();
689
690        assert!(!fields.is_empty());
691        assert!(fields.iter().any(|f| f.name == "icmpv6.type"));
692        assert!(fields.iter().any(|f| f.name == "icmpv6.code"));
693        assert!(fields.iter().any(|f| f.name == "icmpv6.checksum"));
694        assert!(fields.iter().any(|f| f.name == "icmpv6.echo_id"));
695        assert!(fields.iter().any(|f| f.name == "icmpv6.mtu"));
696    }
697
698    // NDP Tests
699
700    #[test]
701    fn test_parse_router_solicitation() {
702        let parser = Icmpv6Protocol;
703        let context = create_context_icmpv6();
704
705        // Router Solicitation with Source Link-Layer Address option
706        let data = vec![
707            0x85, // Type: Router Solicitation (133)
708            0x00, // Code: 0
709            0x00, 0x00, // Checksum
710            0x00, 0x00, 0x00, 0x00, // Reserved
711            // Source Link-Layer Address option
712            0x01, // Type: 1
713            0x01, // Length: 1 (8 bytes)
714            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // MAC
715        ];
716
717        let result = parser.parse(&data, &context);
718
719        assert!(result.is_ok());
720        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(133)));
721        assert_eq!(
722            result.get("type_name"),
723            Some(&FieldValue::Str("Router Solicitation"))
724        );
725        // For constructed strings like MAC addresses, we need to compare with OwnedString
726        match result.get("ndp_source_mac") {
727            Some(FieldValue::OwnedString(s)) if s.as_str() == "00:11:22:33:44:55" => {}
728            other => panic!(
729                "Expected OwnedString(\"00:11:22:33:44:55\"), got {:?}",
730                other
731            ),
732        }
733    }
734
735    #[test]
736    fn test_parse_router_advertisement() {
737        let parser = Icmpv6Protocol;
738        let context = create_context_icmpv6();
739
740        let data = [
741            0x86, // Type: Router Advertisement (134)
742            0x00, // Code: 0
743            0x00, 0x00, // Checksum
744            0x40, // Cur Hop Limit: 64
745            0xc0, // Flags: M=1, O=1
746            0x07, 0x08, // Router Lifetime: 1800
747            0x00, 0x00, 0x00, 0x00, // Reachable Time: 0
748            0x00, 0x00, 0x00, 0x00, // Retrans Timer: 0
749        ];
750
751        let result = parser.parse(&data, &context);
752
753        assert!(result.is_ok());
754        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(134)));
755        assert_eq!(
756            result.get("ndp_cur_hop_limit"),
757            Some(&FieldValue::UInt8(64))
758        );
759        assert_eq!(
760            result.get("ndp_managed_flag"),
761            Some(&FieldValue::Bool(true))
762        );
763        assert_eq!(result.get("ndp_other_flag"), Some(&FieldValue::Bool(true)));
764        assert_eq!(
765            result.get("ndp_router_lifetime"),
766            Some(&FieldValue::UInt16(1800))
767        );
768    }
769
770    #[test]
771    fn test_parse_router_advertisement_flags() {
772        let parser = Icmpv6Protocol;
773        let context = create_context_icmpv6();
774
775        // Test with only M flag set
776        let data = [
777            0x86, 0x00, 0x00, 0x00, 0x40, 0x80, // M=1, O=0
778            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
779        ];
780
781        let result = parser.parse(&data, &context);
782        assert_eq!(
783            result.get("ndp_managed_flag"),
784            Some(&FieldValue::Bool(true))
785        );
786        assert_eq!(result.get("ndp_other_flag"), Some(&FieldValue::Bool(false)));
787
788        // Test with only O flag set
789        let data2 = [
790            0x86, 0x00, 0x00, 0x00, 0x40, 0x40, // M=0, O=1
791            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
792        ];
793
794        let result2 = parser.parse(&data2, &context);
795        assert_eq!(
796            result2.get("ndp_managed_flag"),
797            Some(&FieldValue::Bool(false))
798        );
799        assert_eq!(result2.get("ndp_other_flag"), Some(&FieldValue::Bool(true)));
800    }
801
802    #[test]
803    fn test_parse_neighbor_solicitation() {
804        let parser = Icmpv6Protocol;
805        let context = create_context_icmpv6();
806
807        // NS for 2001:db8::1
808        let data = [
809            0x87, // Type: Neighbor Solicitation (135)
810            0x00, // Code: 0
811            0x00, 0x00, // Checksum
812            0x00, 0x00, 0x00, 0x00, // Reserved
813            // Target Address: 2001:db8::1
814            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
815            0x00, 0x01,
816        ];
817
818        let result = parser.parse(&data, &context);
819
820        assert!(result.is_ok());
821        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(135)));
822        match result.get("ndp_target_address") {
823            Some(FieldValue::OwnedString(s)) if s.as_str() == "2001:db8::1" => {}
824            other => panic!("Expected OwnedString(\"2001:db8::1\"), got {:?}", other),
825        }
826    }
827
828    #[test]
829    fn test_parse_neighbor_advertisement() {
830        let parser = Icmpv6Protocol;
831        let context = create_context_icmpv6();
832
833        // NA with R=1, S=1, O=0
834        let data = [
835            0x88, // Type: Neighbor Advertisement (136)
836            0x00, // Code: 0
837            0x00, 0x00, // Checksum
838            0xc0, 0x00, 0x00, 0x00, // Flags: R=1, S=1, O=0
839            // Target Address: 2001:db8::1
840            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
841            0x00, 0x01,
842        ];
843
844        let result = parser.parse(&data, &context);
845
846        assert!(result.is_ok());
847        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(136)));
848        assert_eq!(result.get("ndp_router_flag"), Some(&FieldValue::Bool(true)));
849        assert_eq!(
850            result.get("ndp_solicited_flag"),
851            Some(&FieldValue::Bool(true))
852        );
853        assert_eq!(
854            result.get("ndp_override_flag"),
855            Some(&FieldValue::Bool(false))
856        );
857        match result.get("ndp_target_address") {
858            Some(FieldValue::OwnedString(s)) if s.as_str() == "2001:db8::1" => {}
859            other => panic!("Expected OwnedString(\"2001:db8::1\"), got {:?}", other),
860        }
861    }
862
863    #[test]
864    fn test_parse_neighbor_advertisement_flags() {
865        let parser = Icmpv6Protocol;
866        let context = create_context_icmpv6();
867
868        // Test with O flag set
869        let data = [
870            0x88, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // R=0, S=0, O=1
871            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
872            0x00, 0x01,
873        ];
874
875        let result = parser.parse(&data, &context);
876        assert_eq!(
877            result.get("ndp_router_flag"),
878            Some(&FieldValue::Bool(false))
879        );
880        assert_eq!(
881            result.get("ndp_solicited_flag"),
882            Some(&FieldValue::Bool(false))
883        );
884        assert_eq!(
885            result.get("ndp_override_flag"),
886            Some(&FieldValue::Bool(true))
887        );
888    }
889
890    #[test]
891    fn test_parse_redirect() {
892        let parser = Icmpv6Protocol;
893        let context = create_context_icmpv6();
894
895        let mut data = vec![
896            0x89, // Type: Redirect (137)
897            0x00, // Code: 0
898            0x00, 0x00, // Checksum
899            0x00, 0x00, 0x00, 0x00, // Reserved
900        ];
901        // Target Address (16 bytes)
902        data.extend_from_slice(&[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
903        // Destination Address (16 bytes)
904        data.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
905
906        let result = parser.parse(&data, &context);
907
908        assert!(result.is_ok());
909        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(137)));
910        match result.get("ndp_target_address") {
911            Some(FieldValue::OwnedString(s)) if s.as_str() == "fe80::1" => {}
912            other => panic!("Expected OwnedString(\"fe80::1\"), got {:?}", other),
913        }
914    }
915
916    #[test]
917    fn test_parse_source_link_layer_option() {
918        let parser = Icmpv6Protocol;
919        let context = create_context_icmpv6();
920
921        let mut data = vec![
922            0x87, 0x00, 0x00, 0x00, // NS header
923            0x00, 0x00, 0x00, 0x00, // Reserved
924        ];
925        // Target address
926        data.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
927        // Source Link-Layer option
928        data.extend_from_slice(&[0x01, 0x01, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
929
930        let result = parser.parse(&data, &context);
931
932        assert!(result.is_ok());
933        match result.get("ndp_source_mac") {
934            Some(FieldValue::OwnedString(s)) if s.as_str() == "aa:bb:cc:dd:ee:ff" => {}
935            other => panic!(
936                "Expected OwnedString(\"aa:bb:cc:dd:ee:ff\"), got {:?}",
937                other
938            ),
939        }
940    }
941
942    #[test]
943    fn test_parse_target_link_layer_option() {
944        let parser = Icmpv6Protocol;
945        let context = create_context_icmpv6();
946
947        let mut data = vec![
948            0x88, 0x00, 0x00, 0x00, // NA header
949            0x60, 0x00, 0x00, 0x00, // Flags
950        ];
951        // Target address
952        data.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
953        // Target Link-Layer option
954        data.extend_from_slice(&[0x02, 0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
955
956        let result = parser.parse(&data, &context);
957
958        assert!(result.is_ok());
959        match result.get("ndp_target_mac") {
960            Some(FieldValue::OwnedString(s)) if s.as_str() == "11:22:33:44:55:66" => {}
961            other => panic!(
962                "Expected OwnedString(\"11:22:33:44:55:66\"), got {:?}",
963                other
964            ),
965        }
966    }
967
968    #[test]
969    fn test_parse_prefix_info_option() {
970        let parser = Icmpv6Protocol;
971        let context = create_context_icmpv6();
972
973        let mut data = vec![
974            0x86, 0x00, 0x00, 0x00, // RA header
975            0x40, 0x00, // Cur Hop Limit, Flags
976            0x00, 0x00, // Router Lifetime
977            0x00, 0x00, 0x00, 0x00, // Reachable Time
978            0x00, 0x00, 0x00, 0x00, // Retrans Timer
979        ];
980        // Prefix Information option (32 bytes)
981        data.extend_from_slice(&[
982            0x03, 0x04, // Type=3, Len=4 (32 bytes)
983            0x40, // Prefix length: 64
984            0xc0, // Flags: L=1, A=1
985            0x00, 0x00, 0x00, 0x00, // Valid lifetime
986            0x00, 0x00, 0x00, 0x00, // Preferred lifetime
987            0x00, 0x00, 0x00, 0x00, // Reserved
988            // Prefix: 2001:db8::
989            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
990            0x00, 0x00,
991        ]);
992
993        let result = parser.parse(&data, &context);
994
995        assert!(result.is_ok());
996        assert_eq!(
997            result.get("ndp_prefix_length"),
998            Some(&FieldValue::UInt8(64))
999        );
1000        match result.get("ndp_prefix") {
1001            Some(FieldValue::OwnedString(s)) if s.as_str() == "2001:db8::" => {}
1002            other => panic!("Expected OwnedString(\"2001:db8::\"), got {:?}", other),
1003        }
1004    }
1005
1006    // MLD Tests
1007
1008    #[test]
1009    fn test_parse_mld_query() {
1010        let parser = Icmpv6Protocol;
1011        let context = create_context_icmpv6();
1012
1013        let mut data = vec![
1014            0x82, // Type: MLD Query (130)
1015            0x00, // Code: 0
1016            0x00, 0x00, // Checksum
1017            0x27, 0x10, // Max Response Delay: 10000ms
1018            0x00, 0x00, // Reserved
1019        ];
1020        // Multicast Address (all-nodes: ff02::1)
1021        data.extend_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
1022
1023        let result = parser.parse(&data, &context);
1024
1025        assert!(result.is_ok());
1026        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(130)));
1027        assert_eq!(
1028            result.get("mld_max_response_delay"),
1029            Some(&FieldValue::UInt16(10000))
1030        );
1031        match result.get("mld_multicast_address") {
1032            Some(FieldValue::OwnedString(s)) if s.as_str() == "ff02::1" => {}
1033            other => panic!("Expected OwnedString(\"ff02::1\"), got {:?}", other),
1034        }
1035    }
1036
1037    #[test]
1038    fn test_parse_mldv1_report() {
1039        let parser = Icmpv6Protocol;
1040        let context = create_context_icmpv6();
1041
1042        let mut data = vec![
1043            0x83, // Type: MLDv1 Report (131)
1044            0x00, // Code: 0
1045            0x00, 0x00, // Checksum
1046            0x00, 0x00, // Max Response Delay: 0
1047            0x00, 0x00, // Reserved
1048        ];
1049        // Multicast Address
1050        data.extend_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12]);
1051
1052        let result = parser.parse(&data, &context);
1053
1054        assert!(result.is_ok());
1055        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(131)));
1056        assert_eq!(
1057            result.get("type_name"),
1058            Some(&FieldValue::Str("MLDv1 Report"))
1059        );
1060    }
1061
1062    #[test]
1063    fn test_parse_mldv1_done() {
1064        let parser = Icmpv6Protocol;
1065        let context = create_context_icmpv6();
1066
1067        let mut data = vec![
1068            0x84, // Type: MLDv1 Done (132)
1069            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1070        ];
1071        // Multicast Address
1072        data.extend_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12]);
1073
1074        let result = parser.parse(&data, &context);
1075
1076        assert!(result.is_ok());
1077        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(132)));
1078        assert_eq!(
1079            result.get("type_name"),
1080            Some(&FieldValue::Str("MLDv1 Done"))
1081        );
1082    }
1083
1084    #[test]
1085    fn test_parse_mldv2_report() {
1086        let parser = Icmpv6Protocol;
1087        let context = create_context_icmpv6();
1088
1089        let data = [
1090            0x8f, // Type: MLDv2 Report (143)
1091            0x00, // Code: 0
1092            0x00, 0x00, // Checksum
1093            0x00, 0x00, // Reserved
1094            0x00, 0x03, // Number of Group Records: 3
1095        ];
1096
1097        let result = parser.parse(&data, &context);
1098
1099        assert!(result.is_ok());
1100        assert_eq!(result.get("type"), Some(&FieldValue::UInt8(143)));
1101        assert_eq!(
1102            result.get("mld_num_group_records"),
1103            Some(&FieldValue::UInt16(3))
1104        );
1105        assert_eq!(
1106            result.get("type_name"),
1107            Some(&FieldValue::Str("MLDv2 Report"))
1108        );
1109    }
1110
1111    #[test]
1112    fn test_multicast_address_extraction() {
1113        let parser = Icmpv6Protocol;
1114        let context = create_context_icmpv6();
1115
1116        // MLDv1 Query with ff02::16 (all-MLDv2-capable-routers)
1117        let mut data = vec![0x82, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00];
1118        data.extend_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x16]);
1119
1120        let result = parser.parse(&data, &context);
1121
1122        assert!(result.is_ok());
1123        match result.get("mld_multicast_address") {
1124            Some(FieldValue::OwnedString(s)) if s.as_str() == "ff02::16" => {}
1125            other => panic!("Expected OwnedString(\"ff02::16\"), got {:?}", other),
1126        }
1127    }
1128
1129    #[test]
1130    fn test_max_response_delay() {
1131        let parser = Icmpv6Protocol;
1132        let context = create_context_icmpv6();
1133
1134        let mut data = vec![
1135            0x82, 0x00, 0x00, 0x00, 0x03, 0xe8, // Max Response Delay: 1000
1136            0x00, 0x00,
1137        ];
1138        data.extend_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
1139
1140        let result = parser.parse(&data, &context);
1141
1142        assert!(result.is_ok());
1143        assert_eq!(
1144            result.get("mld_max_response_delay"),
1145            Some(&FieldValue::UInt16(1000))
1146        );
1147    }
1148}