Skip to main content

packet_strata/packet/
icmp.rs

1//! ICMP (Internet Control Message Protocol) packet parser
2//!
3//! This module implements parsing for ICMP messages as defined in RFC 792.
4//! ICMP is used by network devices to send error messages and operational
5//! information (e.g., ping requests and replies).
6//!
7//! # ICMP 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//! |                       Rest of Header                          |
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! ```
18//!
19//! # Key characteristics
20//!
21//! - Header size: 8 bytes (fixed)
22//! - Type field: identifies the ICMP message type
23//! - Code field: provides additional context for the type
24//! - Common types: Echo Request (8), Echo Reply (0), Destination Unreachable (3)
25//!
26//! # Examples
27//!
28//! ## ICMP Echo Request (ping)
29//!
30//! ```
31//! use packet_strata::packet::icmp::{IcmpHeader, IcmpType};
32//! use packet_strata::packet::HeaderParser;
33//!
34//! // ICMP Echo Request packet
35//! let packet = vec![
36//!     0x08,              // Type: Echo Request (8)
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) = IcmpHeader::from_bytes(&packet).unwrap();
45//! assert_eq!(header.icmp_type(), IcmpType::ECHO);
46//! assert_eq!(header.code(), 0);
47//! assert_eq!(header.echo_id(), 1);
48//! assert_eq!(header.echo_sequence(), 1);
49//! ```
50//!
51//! ## ICMP Echo Reply
52//!
53//! ```
54//! use packet_strata::packet::icmp::{IcmpHeader, IcmpType};
55//! use packet_strata::packet::HeaderParser;
56//!
57//! // ICMP Echo Reply packet
58//! let packet = vec![
59//!     0x00,              // Type: Echo Reply (0)
60//!     0x00,              // Code: 0
61//!     0x00, 0x00,        // Checksum
62//!     0x00, 0x01,        // Identifier: 1
63//!     0x00, 0x02,        // Sequence Number: 2
64//! ];
65//!
66//! let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
67//! assert_eq!(header.icmp_type(), IcmpType::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/// ICMP 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 IcmpType(pub u8);
97
98impl IcmpType {
99    pub const ECHO_REPLY: IcmpType = IcmpType(0); // Echo Reply
100    pub const DEST_UNREACH: IcmpType = IcmpType(3); // Destination Unreachable
101    pub const SOURCE_QUENCH: IcmpType = IcmpType(4); // Source Quench
102    pub const REDIRECT: IcmpType = IcmpType(5); // Redirect (change route)
103    pub const ECHO: IcmpType = IcmpType(8); // Echo Request
104    pub const ROUTER_ADV: IcmpType = IcmpType(9); // Router Advertisement
105    pub const ROUTER_SOLICIT: IcmpType = IcmpType(10); // Router Solicitation
106    pub const TIME_EXCEEDED: IcmpType = IcmpType(11); // Time Exceeded
107    pub const PARAMETER_PROBLEM: IcmpType = IcmpType(12); // Parameter Problem
108    pub const TIMESTAMP: IcmpType = IcmpType(13); // Timestamp Request
109    pub const TIMESTAMP_REPLY: IcmpType = IcmpType(14); // Timestamp Reply
110    pub const INFO_REQUEST: IcmpType = IcmpType(15); // Information Request
111    pub const INFO_REPLY: IcmpType = IcmpType(16); // Information Reply
112    pub const ADDRESS: IcmpType = IcmpType(17); // Address Mask Request
113    pub const ADDRESS_REPLY: IcmpType = IcmpType(18); // Address Mask Reply
114    pub const EX_ECHO: IcmpType = IcmpType(42); // Extended Echo Request
115    pub const EX_ECHO_REPLY: IcmpType = IcmpType(43); // Extended Echo Reply
116}
117
118impl From<u8> for IcmpType {
119    fn from(value: u8) -> Self {
120        IcmpType(value)
121    }
122}
123
124impl From<IcmpType> for u8 {
125    fn from(value: IcmpType) -> Self {
126        value.0
127    }
128}
129
130impl fmt::Display for IcmpType {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let s = match self.0 {
133            0 => "echo-reply",
134            3 => "dest-unreachable",
135            4 => "source-quench",
136            5 => "redirect",
137            8 => "echo-request",
138            9 => "router-adv",
139            10 => "router-solicit",
140            11 => "time-exceeded",
141            12 => "param-problem",
142            13 => "timestamp-request",
143            14 => "timestamp-reply",
144            15 => "info-request",
145            16 => "info-reply",
146            17 => "address-request",
147            18 => "address-reply",
148            42 => "ex-echo-request",
149            43 => "ex-echo-reply",
150            _ => return write!(f, "unknown-{}", self.0),
151        };
152        write!(f, "{}", s)
153    }
154}
155
156/// ICMP Code for Destination Unreachable
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
158#[repr(transparent)]
159pub struct IcmpCodeUnreachable(pub u8);
160
161impl IcmpCodeUnreachable {
162    pub const NET_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(0); // Network Unreachable
163    pub const HOST_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(1); // Host Unreachable
164    pub const PROT_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(2); // Protocol Unreachable
165    pub const PORT_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(3); // Port Unreachable
166    pub const FRAG_NEEDED: IcmpCodeUnreachable = IcmpCodeUnreachable(4); // Fragmentation Needed/DF set
167    pub const SR_FAILED: IcmpCodeUnreachable = IcmpCodeUnreachable(5); // Source Route failed
168    pub const NET_UNKNOWN: IcmpCodeUnreachable = IcmpCodeUnreachable(6); // Network Unknown
169    pub const HOST_UNKNOWN: IcmpCodeUnreachable = IcmpCodeUnreachable(7); // Host Unknown
170    pub const HOST_ISOLATED: IcmpCodeUnreachable = IcmpCodeUnreachable(8); // Host Isolated
171    pub const NET_ANO: IcmpCodeUnreachable = IcmpCodeUnreachable(9); // Network ANO
172    pub const HOST_ANO: IcmpCodeUnreachable = IcmpCodeUnreachable(10); // Host ANO
173    pub const NET_UNR_TOS: IcmpCodeUnreachable = IcmpCodeUnreachable(11); // Network Unreachable for TOS
174    pub const HOST_UNR_TOS: IcmpCodeUnreachable = IcmpCodeUnreachable(12); // Host Unreachable for TOS
175    pub const PKT_FILTERED: IcmpCodeUnreachable = IcmpCodeUnreachable(13); // Packet filtered
176    pub const PREC_VIOLATION: IcmpCodeUnreachable = IcmpCodeUnreachable(14); // Precedence violation
177    pub const PREC_CUTOFF: IcmpCodeUnreachable = IcmpCodeUnreachable(15); // Precedence cut off
178}
179
180impl From<u8> for IcmpCodeUnreachable {
181    fn from(value: u8) -> Self {
182        IcmpCodeUnreachable(value)
183    }
184}
185
186impl From<IcmpCodeUnreachable> for u8 {
187    fn from(value: IcmpCodeUnreachable) -> Self {
188        value.0
189    }
190}
191
192impl fmt::Display for IcmpCodeUnreachable {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        let s = match self.0 {
195            0 => "net-unreachable",
196            1 => "host-unreachable",
197            2 => "protocol-unreachable",
198            3 => "port-unreachable",
199            4 => "frag-needed",
200            5 => "source-route-failed",
201            6 => "dest-net-unknown",
202            7 => "dest-host-unknown",
203            8 => "source-host-isolated",
204            9 => "dest-net-prohibited",
205            10 => "dest-host-prohibited",
206            11 => "net-unreachable-tos",
207            12 => "host-unreachable-tos",
208            13 => "pkt-filtered",
209            14 => "precedence-violation",
210            15 => "precedence-cutoff",
211            _ => return write!(f, "unknown-{}", self.0),
212        };
213        write!(f, "{}", s)
214    }
215}
216
217/// ICMP Code for Redirect
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
219#[repr(transparent)]
220pub struct IcmpCodeRedirect(pub u8);
221
222impl IcmpCodeRedirect {
223    pub const REDIR_NET: IcmpCodeRedirect = IcmpCodeRedirect(0); // Redirect Net
224    pub const REDIR_HOST: IcmpCodeRedirect = IcmpCodeRedirect(1); // Redirect Host
225    pub const REDIR_NETTOS: IcmpCodeRedirect = IcmpCodeRedirect(2); // Redirect Net for TOS
226    pub const REDIR_HOSTTOS: IcmpCodeRedirect = IcmpCodeRedirect(3); // Redirect Host for TOS
227}
228
229impl From<u8> for IcmpCodeRedirect {
230    fn from(value: u8) -> Self {
231        IcmpCodeRedirect(value)
232    }
233}
234
235impl From<IcmpCodeRedirect> for u8 {
236    fn from(value: IcmpCodeRedirect) -> Self {
237        value.0
238    }
239}
240
241impl fmt::Display for IcmpCodeRedirect {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        let s = match self.0 {
244            0 => "redirect-net",
245            1 => "redirect-host",
246            2 => "redirect-net-tos",
247            3 => "redirect-host-tos",
248            _ => return write!(f, "unknown-{}", self.0),
249        };
250        write!(f, "{}", s)
251    }
252}
253
254/// ICMP Code for Time Exceeded
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256#[repr(transparent)]
257pub struct IcmpCodeTimeExceed(pub u8);
258
259impl IcmpCodeTimeExceed {
260    pub const EXC_TTL: IcmpCodeTimeExceed = IcmpCodeTimeExceed(0); // TTL count exceeded
261    pub const EXC_FRAGTIME: IcmpCodeTimeExceed = IcmpCodeTimeExceed(1); // Fragment Reass time exceeded
262}
263
264impl From<u8> for IcmpCodeTimeExceed {
265    fn from(value: u8) -> Self {
266        IcmpCodeTimeExceed(value)
267    }
268}
269
270impl From<IcmpCodeTimeExceed> for u8 {
271    fn from(value: IcmpCodeTimeExceed) -> Self {
272        value.0
273    }
274}
275
276impl fmt::Display for IcmpCodeTimeExceed {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        let s = match self.0 {
279            0 => "ttl-exceeded",
280            1 => "frag-time-exceeded",
281            _ => return write!(f, "unknown-{}", self.0),
282        };
283        write!(f, "{}", s)
284    }
285}
286
287/// ICMP Header structure as defined in RFC 792
288#[repr(C, packed)]
289#[derive(
290    FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
291)]
292pub struct IcmpHeader {
293    icmp_type: IcmpType,
294    code: u8,
295    checksum: U16<BigEndian>,
296    // Union data - we represent it as raw bytes
297    // Can be interpreted as:
298    // - echo: id (u16) + sequence (u16)
299    // - gateway: gateway address (u32)
300    // - frag: reserved (u16) + mtu (u16)
301    un: U32<BigEndian>,
302}
303
304impl IcmpHeader {
305    pub const FIXED_LEN: usize = mem::size_of::<IcmpHeader>();
306
307    /// Returns the ICMP message type
308    #[inline]
309    pub fn icmp_type(&self) -> IcmpType {
310        self.icmp_type
311    }
312
313    /// Returns the ICMP code
314    #[inline]
315    pub fn code(&self) -> u8 {
316        self.code
317    }
318
319    /// Returns the checksum
320    #[inline]
321    pub fn checksum(&self) -> u16 {
322        self.checksum.get()
323    }
324
325    /// Returns the raw union data
326    #[inline]
327    pub fn un(&self) -> u32 {
328        self.un.get()
329    }
330
331    /// For Echo/Echo Reply: returns the identifier
332    #[inline]
333    pub fn echo_id(&self) -> u16 {
334        (self.un.get() >> 16) as u16
335    }
336
337    /// For Echo/Echo Reply: returns the sequence number
338    #[inline]
339    pub fn echo_sequence(&self) -> u16 {
340        (self.un.get() & 0xFFFF) as u16
341    }
342
343    /// For Redirect: returns the gateway address
344    #[inline]
345    pub fn gateway(&self) -> u32 {
346        self.un.get()
347    }
348
349    /// For Fragmentation Needed: returns the MTU
350    #[inline]
351    pub fn frag_mtu(&self) -> u16 {
352        (self.un.get() & 0xFFFF) as u16
353    }
354
355    /// Validates the ICMP header
356    #[inline]
357    pub fn is_valid(&self) -> bool {
358        // Basic validation - all ICMP headers are valid structurally
359        // Additional validation can be done based on type/code combinations
360        true
361    }
362
363    /// Compute ICMP checksum
364    pub fn compute_checksum(icmp_data: &[u8]) -> u16 {
365        let mut sum: u32 = 0;
366
367        let mut i = 0;
368        while i < icmp_data.len() {
369            if i + 1 < icmp_data.len() {
370                let word = u16::from_be_bytes([icmp_data[i], icmp_data[i + 1]]);
371                sum += word as u32;
372                i += 2;
373            } else {
374                // Odd length: pad with zero
375                let word = u16::from_be_bytes([icmp_data[i], 0]);
376                sum += word as u32;
377                i += 1;
378            }
379        }
380
381        // Fold 32-bit sum to 16 bits
382        while sum >> 16 != 0 {
383            sum = (sum & 0xFFFF) + (sum >> 16);
384        }
385
386        // One's complement
387        !sum as u16
388    }
389
390    /// Verify ICMP checksum
391    pub fn verify_checksum(&self, icmp_data: &[u8]) -> bool {
392        let computed = Self::compute_checksum(icmp_data);
393        computed == 0 || computed == 0xFFFF
394    }
395}
396
397impl PacketHeader for IcmpHeader {
398    const NAME: &'static str = "IcmpHeader";
399    type InnerType = IcmpType;
400
401    #[inline]
402    fn inner_type(&self) -> Self::InnerType {
403        self.icmp_type
404    }
405
406    #[inline]
407    fn total_len(&self, _buf: &[u8]) -> usize {
408        Self::FIXED_LEN
409    }
410
411    #[inline]
412    fn is_valid(&self) -> bool {
413        self.is_valid()
414    }
415}
416
417impl HeaderParser for IcmpHeader {
418    type Output<'a> = &'a IcmpHeader;
419
420    #[inline]
421    fn into_view<'a>(header: &'a Self, _: &'a [u8]) -> Self::Output<'a> {
422        header
423    }
424}
425
426impl fmt::Display for IcmpHeader {
427    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
428        write!(f, "ICMP {}", self.icmp_type())?;
429
430        // Add type-specific information
431        match self.icmp_type() {
432            IcmpType::ECHO | IcmpType::ECHO_REPLY => {
433                write!(f, " id={} seq={}", self.echo_id(), self.echo_sequence())?;
434            }
435            IcmpType::DEST_UNREACH => {
436                let code = IcmpCodeUnreachable::from(self.code());
437                write!(f, " code={}", code)?;
438                if self.code() == IcmpCodeUnreachable::FRAG_NEEDED.into() {
439                    write!(f, " mtu={}", self.frag_mtu())?;
440                }
441            }
442            IcmpType::REDIRECT => {
443                let code = IcmpCodeRedirect::from(self.code());
444                write!(f, " code={} gateway={}", code, self.gateway())?;
445            }
446            IcmpType::TIME_EXCEEDED => {
447                let code = IcmpCodeTimeExceed::from(self.code());
448                write!(f, " code={}", code)?;
449            }
450            _ => {
451                if self.code() != 0 {
452                    write!(f, " code={}", self.code())?;
453                }
454            }
455        }
456
457        Ok(())
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    #[test]
466    fn test_icmp_type_constants() {
467        assert_eq!(IcmpType::ECHO_REPLY.0, 0);
468        assert_eq!(IcmpType::DEST_UNREACH.0, 3);
469        assert_eq!(IcmpType::ECHO.0, 8);
470        assert_eq!(IcmpType::TIME_EXCEEDED.0, 11);
471    }
472
473    #[test]
474    fn test_icmp_type_as_str() {
475        assert_eq!(format!("{}", IcmpType::ECHO), "echo-request");
476        assert_eq!(format!("{}", IcmpType::ECHO_REPLY), "echo-reply");
477        assert_eq!(format!("{}", IcmpType::DEST_UNREACH), "dest-unreachable");
478        assert_eq!(format!("{}", IcmpType::TIME_EXCEEDED), "time-exceeded");
479        assert_eq!(format!("{}", IcmpType::from(99)), "unknown-99");
480    }
481
482    #[test]
483    fn test_icmp_code_constants() {
484        assert_eq!(IcmpCodeUnreachable::NET_UNREACH.0, 0);
485        assert_eq!(IcmpCodeUnreachable::HOST_UNREACH.0, 1);
486        assert_eq!(IcmpCodeUnreachable::PORT_UNREACH.0, 3);
487
488        assert_eq!(IcmpCodeRedirect::REDIR_NET.0, 0);
489        assert_eq!(IcmpCodeRedirect::REDIR_HOST.0, 1);
490
491        assert_eq!(IcmpCodeTimeExceed::EXC_TTL.0, 0);
492        assert_eq!(IcmpCodeTimeExceed::EXC_FRAGTIME.0, 1);
493    }
494
495    #[test]
496    fn test_icmp_code_display() {
497        assert_eq!(
498            format!("{}", IcmpCodeUnreachable::PORT_UNREACH),
499            "port-unreachable"
500        );
501        assert_eq!(
502            format!("{}", IcmpCodeUnreachable::HOST_UNREACH),
503            "host-unreachable"
504        );
505        assert_eq!(format!("{}", IcmpCodeUnreachable::from(99)), "unknown-99");
506
507        assert_eq!(format!("{}", IcmpCodeRedirect::REDIR_HOST), "redirect-host");
508        assert_eq!(format!("{}", IcmpCodeRedirect::from(99)), "unknown-99");
509
510        assert_eq!(format!("{}", IcmpCodeTimeExceed::EXC_TTL), "ttl-exceeded");
511        assert_eq!(format!("{}", IcmpCodeTimeExceed::from(99)), "unknown-99");
512    }
513
514    #[test]
515    fn test_icmp_header_size() {
516        assert_eq!(mem::size_of::<IcmpHeader>(), 8);
517        assert_eq!(IcmpHeader::FIXED_LEN, 8);
518    }
519
520    #[test]
521    fn test_icmp_echo_request() {
522        let mut packet = Vec::new();
523
524        // ICMP Echo Request
525        packet.push(8); // Type: Echo Request
526        packet.push(0); // Code: 0
527        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum (will be computed)
528        packet.extend_from_slice(&1234u16.to_be_bytes()); // ID
529        packet.extend_from_slice(&5678u16.to_be_bytes()); // Sequence
530
531        let result = IcmpHeader::from_bytes(&packet);
532        assert!(result.is_ok());
533
534        let (header, _) = result.unwrap();
535        assert_eq!(header.icmp_type(), IcmpType::ECHO);
536        assert_eq!(header.code(), 0);
537        assert_eq!(header.echo_id(), 1234);
538        assert_eq!(header.echo_sequence(), 5678);
539        assert!(header.is_valid());
540    }
541
542    #[test]
543    fn test_icmp_echo_reply() {
544        let mut packet = Vec::new();
545
546        // ICMP Echo Reply
547        packet.push(0); // Type: Echo Reply
548        packet.push(0); // Code: 0
549        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
550        packet.extend_from_slice(&1234u16.to_be_bytes()); // ID
551        packet.extend_from_slice(&5678u16.to_be_bytes()); // Sequence
552
553        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
554
555        assert_eq!(header.icmp_type(), IcmpType::ECHO_REPLY);
556        assert_eq!(header.code(), 0);
557        assert_eq!(header.echo_id(), 1234);
558        assert_eq!(header.echo_sequence(), 5678);
559    }
560
561    #[test]
562    fn test_icmp_dest_unreachable() {
563        let mut packet = Vec::new();
564
565        // ICMP Destination Unreachable - Port Unreachable
566        packet.push(3); // Type: Dest Unreachable
567        packet.push(IcmpCodeUnreachable::PORT_UNREACH.0); // Code: Port Unreachable
568        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
569        packet.extend_from_slice(&0u32.to_be_bytes()); // Unused
570
571        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
572
573        assert_eq!(header.icmp_type(), IcmpType::DEST_UNREACH);
574        assert_eq!(header.code(), IcmpCodeUnreachable::PORT_UNREACH.0);
575    }
576
577    #[test]
578    fn test_icmp_time_exceeded() {
579        let mut packet = Vec::new();
580
581        // ICMP Time Exceeded - TTL exceeded
582        packet.push(11); // Type: Time Exceeded
583        packet.push(IcmpCodeTimeExceed::EXC_TTL.0); // Code: TTL exceeded
584        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
585        packet.extend_from_slice(&0u32.to_be_bytes()); // Unused
586
587        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
588
589        assert_eq!(header.icmp_type(), IcmpType::TIME_EXCEEDED);
590        assert_eq!(header.code(), IcmpCodeTimeExceed::EXC_TTL.0);
591    }
592
593    #[test]
594    fn test_icmp_redirect() {
595        let mut packet = Vec::new();
596
597        // ICMP Redirect - Redirect for Host
598        packet.push(5); // Type: Redirect
599        packet.push(IcmpCodeRedirect::REDIR_HOST.0); // Code: Redirect Host
600        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
601
602        // Gateway address: 192.168.1.1
603        let gateway = 0xC0A80101u32;
604        packet.extend_from_slice(&gateway.to_be_bytes());
605
606        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
607
608        assert_eq!(header.icmp_type(), IcmpType::REDIRECT);
609        assert_eq!(header.code(), IcmpCodeRedirect::REDIR_HOST.0);
610        assert_eq!(header.gateway(), gateway);
611    }
612
613    #[test]
614    fn test_icmp_frag_needed() {
615        let mut packet = Vec::new();
616
617        // ICMP Destination Unreachable - Fragmentation Needed
618        packet.push(3); // Type: Dest Unreachable
619        packet.push(IcmpCodeUnreachable::FRAG_NEEDED.0); // Code: Frag Needed
620        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
621        packet.extend_from_slice(&0u16.to_be_bytes()); // Reserved
622        packet.extend_from_slice(&1500u16.to_be_bytes()); // MTU
623
624        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
625
626        assert_eq!(header.icmp_type(), IcmpType::DEST_UNREACH);
627        assert_eq!(header.code(), IcmpCodeUnreachable::FRAG_NEEDED.0);
628        assert_eq!(header.frag_mtu(), 1500);
629    }
630
631    #[test]
632    fn test_icmp_parsing_too_small() {
633        let packet = vec![0u8; 7]; // Only 7 bytes, need 8
634
635        let result = IcmpHeader::from_bytes(&packet);
636        assert!(result.is_err());
637    }
638
639    #[test]
640    fn test_icmp_total_len() {
641        let packet = create_test_echo_packet();
642        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
643
644        // ICMP header is always 8 bytes (no variable length like TCP)
645        assert_eq!(header.total_len(&packet), 8);
646    }
647
648    #[test]
649    fn test_icmp_checksum_computation() {
650        let mut packet = Vec::new();
651
652        // ICMP Echo Request
653        packet.push(8); // Type
654        packet.push(0); // Code
655        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum (zero for computation)
656        packet.extend_from_slice(&0x1234u16.to_be_bytes()); // ID
657        packet.extend_from_slice(&0x5678u16.to_be_bytes()); // Sequence
658
659        // Add some payload
660        packet.extend_from_slice(b"test data");
661
662        let checksum = IcmpHeader::compute_checksum(&packet);
663
664        // Checksum should be non-zero
665        assert_ne!(checksum, 0);
666
667        // Now set the checksum in the packet
668        packet[2..4].copy_from_slice(&checksum.to_be_bytes());
669
670        // Verify should pass
671        let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
672        assert!(header.verify_checksum(&packet));
673    }
674
675    #[test]
676    fn test_icmp_from_bytes_with_payload() {
677        let mut packet = Vec::new();
678
679        // ICMP Echo Request
680        packet.push(8); // Type: Echo Request
681        packet.push(0); // Code: 0
682        packet.extend_from_slice(&0u16.to_be_bytes()); // Checksum
683        packet.extend_from_slice(&1u16.to_be_bytes()); // ID
684        packet.extend_from_slice(&1u16.to_be_bytes()); // Sequence
685
686        // Add payload
687        let payload_data = b"Hello ICMP!";
688        packet.extend_from_slice(payload_data);
689
690        let result = IcmpHeader::from_bytes(&packet);
691        assert!(result.is_ok());
692
693        let (header, payload) = result.unwrap();
694
695        // Verify header fields
696        assert_eq!(header.icmp_type(), IcmpType::ECHO);
697        assert_eq!(header.code(), 0);
698        assert_eq!(header.echo_id(), 1);
699        assert_eq!(header.echo_sequence(), 1);
700
701        // Verify payload separation
702        assert_eq!(payload.len(), payload_data.len());
703        assert_eq!(payload, payload_data);
704    }
705
706    // Helper function to create a test ICMP Echo packet
707    fn create_test_echo_packet() -> Vec<u8> {
708        let mut packet = Vec::new();
709
710        // Type: Echo Request
711        packet.push(8);
712
713        // Code: 0
714        packet.push(0);
715
716        // Checksum: 0
717        packet.extend_from_slice(&0u16.to_be_bytes());
718
719        // ID: 1234
720        packet.extend_from_slice(&1234u16.to_be_bytes());
721
722        // Sequence: 1
723        packet.extend_from_slice(&1u16.to_be_bytes());
724
725        packet
726    }
727}