Skip to main content

packet_strata/packet/
icmp6.rs

1//! ICMPv6 (Internet Control Message Protocol for IPv6) packet parser
2//!
3//! This module implements parsing for ICMPv6 messages as defined in RFC 4443.
4//! ICMPv6 is used for error reporting, diagnostics, and Neighbor Discovery
5//! Protocol (NDP) in IPv6 networks.
6//!
7//! # ICMPv6 Header Format
8//!
9//! ```text
10//!  0                   1                   2                   3
11//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
12//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13//! |     Type      |     Code      |          Checksum             |
14//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15//! |                       Message Body                            |
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! ```
18//!
19//! # Key characteristics
20//!
21//! - Header size: 8 bytes (fixed)
22//! - Type values 0-127: Error messages
23//! - Type values 128-255: Informational messages
24//! - Checksum is mandatory (unlike ICMPv4 over IPv4)
25//!
26//! # Examples
27//!
28//! ## ICMPv6 Echo Request (ping6)
29//!
30//! ```
31//! use packet_strata::packet::icmp6::{Icmp6Header, Icmp6Type};
32//! use packet_strata::packet::HeaderParser;
33//!
34//! // ICMPv6 Echo Request packet
35//! let packet = vec![
36//!     0x80,              // Type: Echo Request (128)
37//!     0x00,              // Code: 0
38//!     0x00, 0x00,        // Checksum
39//!     0x00, 0x01,        // Identifier: 1
40//!     0x00, 0x01,        // Sequence Number: 1
41//!     // Payload follows...
42//! ];
43//!
44//! let (header, payload) = Icmp6Header::from_bytes(&packet).unwrap();
45//! assert_eq!(header.icmp6_type(), Icmp6Type::ECHO_REQUEST);
46//! assert_eq!(header.code(), 0);
47//! assert_eq!(header.echo_id(), 1);
48//! assert_eq!(header.echo_sequence(), 1);
49//! ```
50//!
51//! ## ICMPv6 Echo Reply
52//!
53//! ```
54//! use packet_strata::packet::icmp6::{Icmp6Header, Icmp6Type};
55//! use packet_strata::packet::HeaderParser;
56//!
57//! // ICMPv6 Echo Reply packet
58//! let packet = vec![
59//!     0x81,              // Type: Echo Reply (129)
60//!     0x00,              // Code: 0
61//!     0x00, 0x00,        // Checksum
62//!     0x00, 0x01,        // Identifier: 1
63//!     0x00, 0x02,        // Sequence Number: 2
64//! ];
65//!
66//! let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
67//! assert_eq!(header.icmp6_type(), Icmp6Type::ECHO_REPLY);
68//! assert_eq!(header.echo_id(), 1);
69//! assert_eq!(header.echo_sequence(), 2);
70//! ```
71
72use std::fmt::{self, Formatter};
73use std::mem;
74
75use zerocopy::byteorder::{BigEndian, U16, U32};
76use zerocopy::{FromBytes, IntoBytes, Unaligned};
77
78use crate::packet::HeaderParser;
79use crate::packet::PacketHeader;
80
81/// ICMPv6 Message Type
82#[derive(
83    Debug,
84    Clone,
85    Copy,
86    PartialEq,
87    Eq,
88    Hash,
89    FromBytes,
90    IntoBytes,
91    Unaligned,
92    zerocopy::Immutable,
93    zerocopy::KnownLayout,
94)]
95#[repr(transparent)]
96pub struct Icmp6Type(pub u8);
97
98impl Icmp6Type {
99    pub const DST_UNREACH: Icmp6Type = Icmp6Type(1);
100    pub const PACKET_TOO_BIG: Icmp6Type = Icmp6Type(2);
101    pub const TIME_EXCEEDED: Icmp6Type = Icmp6Type(3);
102    pub const PARAM_PROB: Icmp6Type = Icmp6Type(4);
103    pub const ECHO_REQUEST: Icmp6Type = Icmp6Type(128);
104    pub const ECHO_REPLY: Icmp6Type = Icmp6Type(129);
105    pub const MLD_LISTENER_QUERY: Icmp6Type = Icmp6Type(130);
106    pub const MLD_LISTENER_REPORT: Icmp6Type = Icmp6Type(131);
107    pub const MLD_LISTENER_REDUCTION: Icmp6Type = Icmp6Type(132);
108
109    pub const ROUTER_SOLICITATION: Icmp6Type = Icmp6Type(133);
110    pub const ROUTER_ADVERTISEMENT: Icmp6Type = Icmp6Type(134);
111    pub const NEIGHBOR_SOLICITATION: Icmp6Type = Icmp6Type(135);
112    pub const NEIGHBOR_ADVERTISEMENT: Icmp6Type = Icmp6Type(136);
113    pub const REDIRECT_MESSAGE: Icmp6Type = Icmp6Type(137);
114    pub const ROUTER_RENUMBERING: Icmp6Type = Icmp6Type(138);
115    pub const NODE_INFORMATION_QUERY: Icmp6Type = Icmp6Type(139);
116    pub const NODE_INFORMATION_RESPONSE: Icmp6Type = Icmp6Type(140);
117
118    pub const INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION: Icmp6Type = Icmp6Type(141);
119    pub const INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT: Icmp6Type = Icmp6Type(142);
120    pub const MULTICAST_LISTENER_DISCOVERY_REPORTS: Icmp6Type = Icmp6Type(143);
121    pub const HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: Icmp6Type = Icmp6Type(144);
122    pub const HOME_AGENT_ADDRESS_DISCOVERY_REPLY: Icmp6Type = Icmp6Type(145);
123    pub const MOBILE_PREFIX_SOLICITATION: Icmp6Type = Icmp6Type(146);
124    pub const MOBILE_PREFIX_ADVERTISEMENT: Icmp6Type = Icmp6Type(147);
125    pub const CERTIFICATION_PATH_SOLICITATION: Icmp6Type = Icmp6Type(148);
126    pub const CERTIFICATION_PATH_ADVERTISEMENT: Icmp6Type = Icmp6Type(149);
127    pub const EXPERIMENTAL_MOBILITY: Icmp6Type = Icmp6Type(150);
128    pub const MULTICAST_ROUTER_ADVERTISEMENT: Icmp6Type = Icmp6Type(151);
129    pub const MULTICAST_ROUTER_SOLICITATION: Icmp6Type = Icmp6Type(152);
130    pub const MULTICAST_ROUTER_TERMINATION: Icmp6Type = Icmp6Type(153);
131    pub const FMIPV6: Icmp6Type = Icmp6Type(154);
132    pub const RPL_CONTROL_MESSAGE: Icmp6Type = Icmp6Type(155);
133    pub const ILNPV6_LOCATOR_UPDATE: Icmp6Type = Icmp6Type(156);
134    pub const DUPLICATE_ADDRESS_REQUEST: Icmp6Type = Icmp6Type(157);
135    pub const DUPLICATE_ADDRESS_CONFIRM: Icmp6Type = Icmp6Type(158);
136    pub const MPL_CONTROL_MESSAGE: Icmp6Type = Icmp6Type(159);
137    pub const EXTENDED_ECHO_REQUEST: Icmp6Type = Icmp6Type(160);
138    pub const EXTENDED_ECHO_REPLY: Icmp6Type = Icmp6Type(161);
139}
140
141impl From<u8> for Icmp6Type {
142    fn from(value: u8) -> Self {
143        Icmp6Type(value)
144    }
145}
146
147impl From<Icmp6Type> for u8 {
148    fn from(value: Icmp6Type) -> Self {
149        value.0
150    }
151}
152
153impl fmt::Display for Icmp6Type {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        let s = match self.0 {
156            1 => "destination-unreachable",
157            2 => "packet-too-big",
158            3 => "time-exceeded",
159            4 => "parameter-problem",
160            128 => "echo-request",
161            129 => "echo-reply",
162            130 => "multicast-listener-query",
163            131 => "multicast-listener-report",
164            132 => "multicast-listener-reduction",
165            133 => "router-solicitation",
166            134 => "router-advertisement",
167            135 => "neighbor-solicitation",
168            136 => "neighbor-advertisement",
169            137 => "redirect-message",
170            138 => "router-renumbering",
171            139 => "node-information-query",
172            140 => "node-information-response",
173            141 => "inverse-neighbor-discovery-solicitation",
174            142 => "inverse-neighbor-discovery-advertisement",
175            143 => "multicast-listener-discovery-report",
176            144 => "home-agent-address-discovery-request",
177            145 => "home-agent-address-discovery-reply",
178            146 => "mobile-prefix-solicitation",
179            147 => "mobile-prefix-advertisement",
180            148 => "certification-path-solicitation",
181            149 => "certification-path-advertisement",
182            150 => "experimental-mobility",
183            151 => "multicast-router-advertisement",
184            152 => "multicast-router-solicitation",
185            153 => "multicast-router-termination",
186            154 => "fmipv6",
187            155 => "rpl-control-message",
188            156 => "ilnpv6-locator-update",
189            157 => "duplicate-address-request",
190            158 => "duplicate-address-confirmation",
191            159 => "mpl-control-message",
192            160 => "extended-echo-request",
193            161 => "extended-echo-reply",
194            _ => return write!(f, "unknown-{}", self.0),
195        };
196        write!(f, "{}", s)
197    }
198}
199
200/// ICMPv6 Code for Destination Unreachable
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202#[repr(transparent)]
203pub struct Icmp6CodeUnreachable(pub u8);
204
205impl Icmp6CodeUnreachable {
206    pub const NOROUTE: Icmp6CodeUnreachable = Icmp6CodeUnreachable(0); // no route to destination
207    pub const ADMIN: Icmp6CodeUnreachable = Icmp6CodeUnreachable(1); // communication with destination administratively prohibited
208    pub const BEYONDSCOPE: Icmp6CodeUnreachable = Icmp6CodeUnreachable(2); // beyond scope of source address
209    pub const ADDR: Icmp6CodeUnreachable = Icmp6CodeUnreachable(3); // address unreachable
210    pub const NOPORT: Icmp6CodeUnreachable = Icmp6CodeUnreachable(4); // port unreachable
211}
212
213impl From<u8> for Icmp6CodeUnreachable {
214    fn from(value: u8) -> Self {
215        Icmp6CodeUnreachable(value)
216    }
217}
218
219impl From<Icmp6CodeUnreachable> for u8 {
220    fn from(value: Icmp6CodeUnreachable) -> Self {
221        value.0
222    }
223}
224
225impl fmt::Display for Icmp6CodeUnreachable {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        let s = match self.0 {
228            0 => "no-route",
229            1 => "admin-prohibited",
230            2 => "beyond-scope",
231            3 => "address-unreachable",
232            4 => "port-unreachable",
233            _ => return write!(f, "unknown-{}", self.0),
234        };
235        write!(f, "{}", s)
236    }
237}
238
239/// ICMPv6 Code for Time Exceeded
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
241#[repr(transparent)]
242pub struct Icmp6CodeTimeExceed(pub u8);
243
244impl Icmp6CodeTimeExceed {
245    pub const TRANSIT: Icmp6CodeTimeExceed = Icmp6CodeTimeExceed(0); // Hop Limit == 0 in transit
246    pub const REASSEMBLY: Icmp6CodeTimeExceed = Icmp6CodeTimeExceed(1); // Reassembly time out
247}
248
249impl From<u8> for Icmp6CodeTimeExceed {
250    fn from(value: u8) -> Self {
251        Icmp6CodeTimeExceed(value)
252    }
253}
254
255impl From<Icmp6CodeTimeExceed> for u8 {
256    fn from(value: Icmp6CodeTimeExceed) -> Self {
257        value.0
258    }
259}
260
261impl fmt::Display for Icmp6CodeTimeExceed {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        let s = match self.0 {
264            0 => "hop-limit-exceeded",
265            1 => "reassembly-timeout",
266            _ => return write!(f, "unknown-{}", self.0),
267        };
268        write!(f, "{}", s)
269    }
270}
271
272/// ICMPv6 Code for Parameter Problem
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
274#[repr(transparent)]
275pub struct Icmp6CodeParamProb(pub u8);
276
277impl Icmp6CodeParamProb {
278    pub const HEADER: Icmp6CodeParamProb = Icmp6CodeParamProb(0); // erroneous header field
279    pub const NEXTHEADER: Icmp6CodeParamProb = Icmp6CodeParamProb(1); // unrecognized Next Header
280    pub const OPTION: Icmp6CodeParamProb = Icmp6CodeParamProb(2); // unrecognized IPv6 option
281}
282
283impl From<u8> for Icmp6CodeParamProb {
284    fn from(value: u8) -> Self {
285        Icmp6CodeParamProb(value)
286    }
287}
288
289impl From<Icmp6CodeParamProb> for u8 {
290    fn from(value: Icmp6CodeParamProb) -> Self {
291        value.0
292    }
293}
294
295impl fmt::Display for Icmp6CodeParamProb {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        let s = match self.0 {
298            0 => "erroneous-header",
299            1 => "unrecognized-next-header",
300            2 => "unrecognized-option",
301            _ => return write!(f, "unknown-{}", self.0),
302        };
303        write!(f, "{}", s)
304    }
305}
306
307/// ICMPv6 Header structure as defined in RFC 4443
308#[repr(C, packed)]
309#[derive(
310    FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
311)]
312pub struct Icmp6Header {
313    icmp6_type: Icmp6Type,
314    code: u8,
315    checksum: U16<BigEndian>,
316    // Union data - we represent it as raw bytes
317    // Can be interpreted as:
318    // - data32[1]: 32-bit data
319    // - data16[2]: 16-bit data
320    // - data8[4]: 8-bit data
321    un: U32<BigEndian>,
322}
323
324impl Icmp6Header {
325    pub const FIXED_LEN: usize = mem::size_of::<Icmp6Header>();
326
327    /// Returns the ICMPv6 message type
328    #[inline]
329    pub fn icmp6_type(&self) -> Icmp6Type {
330        self.icmp6_type
331    }
332
333    /// Returns the ICMPv6 code
334    #[inline]
335    pub fn code(&self) -> u8 {
336        self.code
337    }
338
339    /// Returns the checksum
340    #[inline]
341    pub fn checksum(&self) -> u16 {
342        self.checksum.get()
343    }
344
345    /// Returns the raw union data
346    #[inline]
347    pub fn un(&self) -> u32 {
348        self.un.get()
349    }
350
351    /// For Echo/Echo Reply: returns the identifier
352    #[inline]
353    pub fn echo_id(&self) -> u16 {
354        (self.un.get() >> 16) as u16
355    }
356
357    /// For Echo/Echo Reply: returns the sequence number
358    #[inline]
359    pub fn echo_sequence(&self) -> u16 {
360        (self.un.get() & 0xFFFF) as u16
361    }
362
363    /// For Packet Too Big: returns the MTU
364    #[inline]
365    pub fn mtu(&self) -> u32 {
366        self.un.get()
367    }
368
369    /// For Parameter Problem: returns the pointer
370    #[inline]
371    pub fn pointer(&self) -> u32 {
372        self.un.get()
373    }
374
375    /// Validates the ICMPv6 header
376    #[inline]
377    pub fn is_valid(&self) -> bool {
378        // Basic validation - all ICMPv6 headers are valid structurally
379        // Additional validation can be done based on type/code combinations
380        true
381    }
382
383    /// Compute ICMPv6 checksum including pseudo-header
384    /// Note: ICMPv6 checksum is mandatory (unlike ICMPv4)
385    pub fn compute_checksum(src_ip: &[u8; 16], dst_ip: &[u8; 16], icmp6_data: &[u8]) -> u16 {
386        let mut sum: u32 = 0;
387
388        // Pseudo-header: source IP (128 bits = 16 bytes)
389        for i in (0..16).step_by(2) {
390            let word = u16::from_be_bytes([src_ip[i], src_ip[i + 1]]);
391            sum += word as u32;
392        }
393
394        // Pseudo-header: destination IP (128 bits = 16 bytes)
395        for i in (0..16).step_by(2) {
396            let word = u16::from_be_bytes([dst_ip[i], dst_ip[i + 1]]);
397            sum += word as u32;
398        }
399
400        // Pseudo-header: ICMPv6 length (32 bits)
401        let icmp_len = icmp6_data.len() as u32;
402        sum += (icmp_len >> 16) & 0xFFFF;
403        sum += icmp_len & 0xFFFF;
404
405        // Pseudo-header: next header (ICMPv6 = 58)
406        sum += 58;
407
408        // ICMPv6 header and data
409        let mut i = 0;
410        while i < icmp6_data.len() {
411            if i + 1 < icmp6_data.len() {
412                let word = u16::from_be_bytes([icmp6_data[i], icmp6_data[i + 1]]);
413                sum += word as u32;
414                i += 2;
415            } else {
416                // Odd length: pad with zero
417                let word = u16::from_be_bytes([icmp6_data[i], 0]);
418                sum += word as u32;
419                i += 1;
420            }
421        }
422
423        // Fold 32-bit sum to 16 bits
424        while sum >> 16 != 0 {
425            sum = (sum & 0xFFFF) + (sum >> 16);
426        }
427
428        // One's complement
429        !sum as u16
430    }
431
432    /// Verify ICMPv6 checksum
433    pub fn verify_checksum(&self, src_ip: &[u8; 16], dst_ip: &[u8; 16], icmp6_data: &[u8]) -> bool {
434        let computed = Self::compute_checksum(src_ip, dst_ip, icmp6_data);
435        computed == 0 || computed == 0xFFFF
436    }
437}
438
439impl PacketHeader for Icmp6Header {
440    const NAME: &'static str = "Icmp6Header";
441    type InnerType = Icmp6Type;
442
443    #[inline]
444    fn inner_type(&self) -> Self::InnerType {
445        self.icmp6_type
446    }
447
448    #[inline]
449    fn total_len(&self, _buf: &[u8]) -> usize {
450        Self::FIXED_LEN
451    }
452
453    #[inline]
454    fn is_valid(&self) -> bool {
455        self.is_valid()
456    }
457}
458
459impl HeaderParser for Icmp6Header {
460    type Output<'a> = &'a Icmp6Header;
461
462    #[inline]
463    fn into_view<'a>(header: &'a Self, _: &'a [u8]) -> Self::Output<'a> {
464        header
465    }
466}
467
468impl fmt::Display for Icmp6Header {
469    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
470        write!(f, "ICMPv6 {}", self.icmp6_type())?;
471
472        // Add type-specific information
473        match self.icmp6_type() {
474            Icmp6Type::ECHO_REQUEST | Icmp6Type::ECHO_REPLY => {
475                write!(f, " id={} seq={}", self.echo_id(), self.echo_sequence())?;
476            }
477            Icmp6Type::DST_UNREACH => {
478                let code = Icmp6CodeUnreachable::from(self.code());
479                write!(f, " code={}", code)?;
480            }
481            Icmp6Type::PACKET_TOO_BIG => {
482                write!(f, " mtu={}", self.mtu())?;
483            }
484            Icmp6Type::TIME_EXCEEDED => {
485                let code = Icmp6CodeTimeExceed::from(self.code());
486                write!(f, " code={}", code)?;
487            }
488            Icmp6Type::PARAM_PROB => {
489                let code = Icmp6CodeParamProb::from(self.code());
490                write!(f, " code={} ptr={}", code, self.pointer())?;
491            }
492            _ => {
493                if self.code() != 0 {
494                    write!(f, " code={}", self.code())?;
495                }
496            }
497        }
498
499        Ok(())
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn test_icmp6_type_constants() {
509        assert_eq!(Icmp6Type::DST_UNREACH.0, 1);
510        assert_eq!(Icmp6Type::PACKET_TOO_BIG.0, 2);
511        assert_eq!(Icmp6Type::TIME_EXCEEDED.0, 3);
512        assert_eq!(Icmp6Type::ECHO_REQUEST.0, 128);
513        assert_eq!(Icmp6Type::ECHO_REPLY.0, 129);
514        assert_eq!(Icmp6Type::ROUTER_SOLICITATION.0, 133);
515        assert_eq!(Icmp6Type::NEIGHBOR_SOLICITATION.0, 135);
516    }
517
518    #[test]
519    fn test_icmp6_type_display() {
520        assert_eq!(format!("{}", Icmp6Type::ECHO_REQUEST), "echo-request");
521        assert_eq!(format!("{}", Icmp6Type::ECHO_REPLY), "echo-reply");
522        assert_eq!(
523            format!("{}", Icmp6Type::DST_UNREACH),
524            "destination-unreachable"
525        );
526        assert_eq!(
527            format!("{}", Icmp6Type::NEIGHBOR_SOLICITATION),
528            "neighbor-solicitation"
529        );
530        assert_eq!(
531            format!("{}", Icmp6Type::ROUTER_ADVERTISEMENT),
532            "router-advertisement"
533        );
534        assert_eq!(format!("{}", Icmp6Type::PACKET_TOO_BIG), "packet-too-big");
535        assert_eq!(format!("{}", Icmp6Type::TIME_EXCEEDED), "time-exceeded");
536        assert_eq!(format!("{}", Icmp6Type::PARAM_PROB), "parameter-problem");
537        assert_eq!(format!("{}", Icmp6Type::from(99)), "unknown-99");
538    }
539
540    #[test]
541    fn test_icmp6_code_constants() {
542        assert_eq!(Icmp6CodeUnreachable::NOROUTE.0, 0);
543        assert_eq!(Icmp6CodeUnreachable::NOPORT.0, 4);
544
545        assert_eq!(Icmp6CodeTimeExceed::TRANSIT.0, 0);
546        assert_eq!(Icmp6CodeTimeExceed::REASSEMBLY.0, 1);
547
548        assert_eq!(Icmp6CodeParamProb::HEADER.0, 0);
549        assert_eq!(Icmp6CodeParamProb::NEXTHEADER.0, 1);
550    }
551
552    #[test]
553    fn test_icmp6_code_display() {
554        assert_eq!(
555            format!("{}", Icmp6CodeUnreachable::NOPORT),
556            "port-unreachable"
557        );
558        assert_eq!(format!("{}", Icmp6CodeUnreachable::NOROUTE), "no-route");
559        assert_eq!(format!("{}", Icmp6CodeUnreachable::from(99)), "unknown-99");
560
561        assert_eq!(
562            format!("{}", Icmp6CodeTimeExceed::TRANSIT),
563            "hop-limit-exceeded"
564        );
565        assert_eq!(format!("{}", Icmp6CodeTimeExceed::from(99)), "unknown-99");
566
567        assert_eq!(
568            format!("{}", Icmp6CodeParamProb::HEADER),
569            "erroneous-header"
570        );
571        assert_eq!(format!("{}", Icmp6CodeParamProb::from(99)), "unknown-99");
572    }
573
574    #[test]
575    fn test_icmp6_header_size() {
576        assert_eq!(mem::size_of::<Icmp6Header>(), 8);
577        assert_eq!(Icmp6Header::FIXED_LEN, 8);
578    }
579
580    #[test]
581    fn test_icmp6_echo_request() {
582        let mut packet = Vec::new();
583
584        // ICMPv6 Echo Request
585        packet.push(128); // Type: Echo Request
586        packet.push(0); // Code: 0
587        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
588        packet.extend_from_slice(&1234u16.to_be_bytes()); // ID
589        packet.extend_from_slice(&5678u16.to_be_bytes()); // Sequence
590
591        let result = Icmp6Header::from_bytes(&packet);
592        assert!(result.is_ok());
593
594        let (header, _) = result.unwrap();
595        assert_eq!(header.icmp6_type(), Icmp6Type::ECHO_REQUEST);
596        assert_eq!(header.code(), 0);
597        assert_eq!(header.echo_id(), 1234);
598        assert_eq!(header.echo_sequence(), 5678);
599        assert!(header.is_valid());
600    }
601
602    #[test]
603    fn test_icmp6_echo_reply() {
604        let mut packet = Vec::new();
605
606        // ICMPv6 Echo Reply
607        packet.push(129); // Type: Echo Reply
608        packet.push(0); // Code: 0
609        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
610        packet.extend_from_slice(&1234u16.to_be_bytes()); // ID
611        packet.extend_from_slice(&5678u16.to_be_bytes()); // Sequence
612
613        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
614
615        assert_eq!(header.icmp6_type(), Icmp6Type::ECHO_REPLY);
616        assert_eq!(header.code(), 0);
617        assert_eq!(header.echo_id(), 1234);
618        assert_eq!(header.echo_sequence(), 5678);
619    }
620
621    #[test]
622    fn test_icmp6_dest_unreachable() {
623        let mut packet = Vec::new();
624
625        // ICMPv6 Destination Unreachable - Port Unreachable
626        packet.push(1); // Type: Dest Unreachable
627        packet.push(Icmp6CodeUnreachable::NOPORT.0); // Code: Port Unreachable
628        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
629        packet.extend_from_slice(&0u32.to_be_bytes()); // Unused
630
631        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
632
633        assert_eq!(header.icmp6_type(), Icmp6Type::DST_UNREACH);
634        assert_eq!(header.code(), Icmp6CodeUnreachable::NOPORT.0);
635    }
636
637    #[test]
638    fn test_icmp6_time_exceeded() {
639        let mut packet = Vec::new();
640
641        // ICMPv6 Time Exceeded - Hop Limit exceeded
642        packet.push(3); // Type: Time Exceeded
643        packet.push(Icmp6CodeTimeExceed::TRANSIT.0); // Code: Hop Limit exceeded
644        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
645        packet.extend_from_slice(&0u32.to_be_bytes()); // Unused
646
647        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
648
649        assert_eq!(header.icmp6_type(), Icmp6Type::TIME_EXCEEDED);
650        assert_eq!(header.code(), Icmp6CodeTimeExceed::TRANSIT.0);
651    }
652
653    #[test]
654    fn test_icmp6_packet_too_big() {
655        let mut packet = Vec::new();
656
657        // ICMPv6 Packet Too Big
658        packet.push(2); // Type: Packet Too Big
659        packet.push(0); // Code: 0
660        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
661
662        // MTU: 1280
663        let mtu = 1280u32;
664        packet.extend_from_slice(&mtu.to_be_bytes());
665
666        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
667
668        assert_eq!(header.icmp6_type(), Icmp6Type::PACKET_TOO_BIG);
669        assert_eq!(header.code(), 0);
670        assert_eq!(header.mtu(), mtu);
671    }
672
673    #[test]
674    fn test_icmp6_param_problem() {
675        let mut packet = Vec::new();
676
677        // ICMPv6 Parameter Problem
678        packet.push(4); // Type: Parameter Problem
679        packet.push(Icmp6CodeParamProb::HEADER.0); // Code: Erroneous header
680        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
681
682        // Pointer: 40
683        let pointer = 40u32;
684        packet.extend_from_slice(&pointer.to_be_bytes());
685
686        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
687
688        assert_eq!(header.icmp6_type(), Icmp6Type::PARAM_PROB);
689        assert_eq!(header.code(), Icmp6CodeParamProb::HEADER.0);
690        assert_eq!(header.pointer(), pointer);
691    }
692
693    #[test]
694    fn test_icmp6_neighbor_solicitation() {
695        let mut packet = Vec::new();
696
697        // ICMPv6 Neighbor Solicitation
698        packet.push(135); // Type: Neighbor Solicitation
699        packet.push(0); // Code: 0
700        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
701        packet.extend_from_slice(&0u32.to_be_bytes()); // Reserved
702
703        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
704
705        assert_eq!(header.icmp6_type(), Icmp6Type::NEIGHBOR_SOLICITATION);
706        assert_eq!(header.code(), 0);
707    }
708
709    #[test]
710    fn test_icmp6_router_advertisement() {
711        let mut packet = Vec::new();
712
713        // ICMPv6 Router Advertisement
714        packet.push(134); // Type: Router Advertisement
715        packet.push(0); // Code: 0
716        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
717        packet.extend_from_slice(&0u32.to_be_bytes()); // Cur Hop Limit, Flags, Router Lifetime
718
719        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
720
721        assert_eq!(header.icmp6_type(), Icmp6Type::ROUTER_ADVERTISEMENT);
722        assert_eq!(header.code(), 0);
723    }
724
725    #[test]
726    fn test_icmp6_parsing_too_small() {
727        let packet = vec![0u8; 7]; // Only 7 bytes, need 8
728
729        let result = Icmp6Header::from_bytes(&packet);
730        assert!(result.is_err());
731    }
732
733    #[test]
734    fn test_icmp6_total_len() {
735        let packet = create_test_echo_packet();
736        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
737
738        // ICMPv6 header is always 8 bytes (no variable length)
739        assert_eq!(header.total_len(&packet), 8);
740    }
741
742    #[test]
743    fn test_icmp6_from_bytes_with_payload() {
744        let mut packet = Vec::new();
745
746        // ICMPv6 Echo Request
747        packet.push(128); // Type: Echo Request
748        packet.push(0); // Code: 0
749        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
750        packet.extend_from_slice(&1u16.to_be_bytes()); // ID
751        packet.extend_from_slice(&1u16.to_be_bytes()); // Sequence
752
753        // Add payload
754        let payload_data = b"Hello ICMPv6!";
755        packet.extend_from_slice(payload_data);
756
757        let result = Icmp6Header::from_bytes(&packet);
758        assert!(result.is_ok());
759
760        let (header, payload) = result.unwrap();
761
762        // Verify header fields
763        assert_eq!(header.icmp6_type(), Icmp6Type::ECHO_REQUEST);
764        assert_eq!(header.code(), 0);
765        assert_eq!(header.echo_id(), 1);
766        assert_eq!(header.echo_sequence(), 1);
767
768        // Verify payload separation
769        assert_eq!(payload.len(), payload_data.len());
770        assert_eq!(payload, payload_data);
771    }
772
773    #[test]
774    fn test_icmp6_checksum_computation() {
775        // IPv6 addresses (simplified)
776        let src_ip: [u8; 16] = [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
777        let dst_ip: [u8; 16] = [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2];
778
779        let mut packet = Vec::new();
780
781        // ICMPv6 Echo Request
782        packet.push(128); // Type
783        packet.push(0); // Code
784        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum (zero for computation)
785        packet.extend_from_slice(&0x1234u16.to_be_bytes()); // ID
786        packet.extend_from_slice(&0x5678u16.to_be_bytes()); // Sequence
787
788        // Add some payload
789        packet.extend_from_slice(b"test");
790
791        let checksum = Icmp6Header::compute_checksum(&src_ip, &dst_ip, &packet);
792
793        // Checksum should be non-zero
794        assert_ne!(checksum, 0);
795
796        // Now set the checksum in the packet
797        packet[2..4].copy_from_slice(&checksum.to_be_bytes());
798
799        // Verify should pass
800        let (header, _) = Icmp6Header::from_bytes(&packet).unwrap();
801        assert!(header.verify_checksum(&src_ip, &dst_ip, &packet));
802    }
803
804    // Helper function to create a test ICMPv6 Echo packet
805    fn create_test_echo_packet() -> Vec<u8> {
806        let mut packet = Vec::new();
807
808        // Type: Echo Request
809        packet.push(128);
810
811        // Code: 0
812        packet.push(0);
813
814        // Checksum: 0
815        packet.extend_from_slice(&0u16.to_be_bytes());
816
817        // ID: 1234
818        packet.extend_from_slice(&1234u16.to_be_bytes());
819
820        // Sequence: 1
821        packet.extend_from_slice(&1u16.to_be_bytes());
822
823        packet
824    }
825}