Skip to main content

stackforge_core/layer/icmp/
mod.rs

1//! ICMP (Internet Control Message Protocol) layer implementation.
2//!
3//! This module provides types and functions for working with ICMP packets,
4//! including parsing, field access, and checksum calculation.
5
6pub mod builder;
7pub mod checksum;
8pub mod error;
9pub mod extensions;
10pub mod types;
11
12// Re-export builder
13pub use builder::IcmpBuilder;
14
15// Re-export checksum functions
16pub use checksum::{icmp_checksum, verify_icmp_checksum};
17pub use error::{ERROR_MIN_PAYLOAD, ERROR_TYPES, error_payload_offset, is_error_type};
18pub use extensions::{
19    InterfaceInformation, IpAddr, class_name as extension_class_name, has_extensions,
20    parse_extension_header, parse_extension_object, parse_interface_information,
21};
22pub use types::{ICMP_ANSWERS, code_name, type_name};
23
24use crate::layer::field::{FieldDesc, FieldError, FieldType, FieldValue};
25use crate::layer::{Layer, LayerIndex, LayerKind};
26use std::net::Ipv4Addr;
27
28/// ICMP minimum header length (8 bytes).
29pub const ICMP_MIN_HEADER_LEN: usize = 8;
30
31/// Field offsets within the ICMP header.
32pub mod offsets {
33    pub const TYPE: usize = 0;
34    pub const CODE: usize = 1;
35    pub const CHECKSUM: usize = 2;
36
37    // Conditional fields based on type
38
39    // Echo request/reply (types 0, 8) and other ID/Seq types
40    pub const ID: usize = 4;
41    pub const SEQ: usize = 6;
42
43    // Redirect (type 5)
44    pub const GATEWAY: usize = 4;
45
46    // Destination unreachable (type 3)
47    pub const UNUSED_DU: usize = 4;
48    pub const LENGTH_DU: usize = 5;
49    pub const NEXT_HOP_MTU: usize = 6;
50
51    // Time exceeded (type 11)
52    pub const UNUSED_TE: usize = 4;
53    pub const LENGTH_TE: usize = 6;
54
55    // Parameter problem (type 12)
56    pub const PTR: usize = 4;
57    pub const UNUSED_PP: usize = 5;
58    pub const LENGTH_PP: usize = 6;
59
60    // Timestamp request/reply (types 13, 14)
61    pub const TS_ORI: usize = 8;
62    pub const TS_RX: usize = 12;
63    pub const TS_TX: usize = 16;
64
65    // Address mask request/reply (types 17, 18)
66    pub const ADDR_MASK: usize = 4;
67}
68
69/// ICMP field descriptors for dynamic access.
70pub static FIELDS: &[FieldDesc] = &[
71    FieldDesc::new("type", offsets::TYPE, 1, FieldType::U8),
72    FieldDesc::new("code", offsets::CODE, 1, FieldType::U8),
73    FieldDesc::new("chksum", offsets::CHECKSUM, 2, FieldType::U16),
74    // Conditional fields - available based on ICMP type
75    FieldDesc::new("id", offsets::ID, 2, FieldType::U16),
76    FieldDesc::new("seq", offsets::SEQ, 2, FieldType::U16),
77];
78
79/// ICMP layer representation.
80///
81/// ICMP is the control protocol for IP, defined in RFC 792.
82/// The header format varies based on message type:
83///
84/// ```text
85/// Base header (all types):
86///  0                   1                   2                   3
87///  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
88/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89/// |     Type      |     Code      |          Checksum             |
90/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91/// |                    (Type-specific data)                       |
92/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93///
94/// Echo/Echo Reply (type 0, 8):
95/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96/// |          Identifier           |        Sequence Number        |
97/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
98///
99/// Redirect (type 5):
100/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101/// |                     Gateway IP Address                        |
102/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
103/// ```
104#[derive(Debug, Clone)]
105pub struct IcmpLayer {
106    pub index: LayerIndex,
107}
108
109impl IcmpLayer {
110    /// Create a new ICMP layer from a layer index.
111    pub fn new(index: LayerIndex) -> Self {
112        Self { index }
113    }
114
115    /// Get the ICMP type.
116    pub fn icmp_type(&self, buf: &[u8]) -> Result<u8, FieldError> {
117        let slice = self.index.slice(buf);
118        if slice.is_empty() {
119            return Err(FieldError::BufferTooShort {
120                offset: self.index.start + offsets::TYPE,
121                need: 1,
122                have: 0,
123            });
124        }
125        Ok(slice[offsets::TYPE])
126    }
127
128    /// Get the ICMP code.
129    pub fn code(&self, buf: &[u8]) -> Result<u8, FieldError> {
130        let slice = self.index.slice(buf);
131        if slice.len() < offsets::CODE + 1 {
132            return Err(FieldError::BufferTooShort {
133                offset: self.index.start + offsets::CODE,
134                need: 1,
135                have: slice.len().saturating_sub(offsets::CODE),
136            });
137        }
138        Ok(slice[offsets::CODE])
139    }
140
141    /// Get the checksum.
142    pub fn checksum(&self, buf: &[u8]) -> Result<u16, FieldError> {
143        let slice = self.index.slice(buf);
144        if slice.len() < offsets::CHECKSUM + 2 {
145            return Err(FieldError::BufferTooShort {
146                offset: self.index.start + offsets::CHECKSUM,
147                need: 2,
148                have: slice.len().saturating_sub(offsets::CHECKSUM),
149            });
150        }
151        Ok(u16::from_be_bytes([
152            slice[offsets::CHECKSUM],
153            slice[offsets::CHECKSUM + 1],
154        ]))
155    }
156
157    /// Get the identifier (for echo, timestamp).
158    ///
159    /// Returns None if this ICMP type doesn't have an ID field.
160    pub fn id(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
161        let icmp_type = self.icmp_type(buf)?;
162
163        // ID field only exists for certain types
164        if !matches!(
165            icmp_type,
166            types::types::ECHO_REQUEST
167                | types::types::ECHO_REPLY
168                | types::types::TIMESTAMP
169                | types::types::TIMESTAMP_REPLY
170                | types::types::INFO_REQUEST
171                | types::types::INFO_REPLY
172                | types::types::ADDRESS_MASK_REQUEST
173                | types::types::ADDRESS_MASK_REPLY
174        ) {
175            return Ok(None);
176        }
177
178        let slice = self.index.slice(buf);
179        if slice.len() < offsets::ID + 2 {
180            return Err(FieldError::BufferTooShort {
181                offset: self.index.start + offsets::ID,
182                need: 2,
183                have: slice.len().saturating_sub(offsets::ID),
184            });
185        }
186        Ok(Some(u16::from_be_bytes([
187            slice[offsets::ID],
188            slice[offsets::ID + 1],
189        ])))
190    }
191
192    /// Get the sequence number (for echo, timestamp).
193    ///
194    /// Returns None if this ICMP type doesn't have a sequence field.
195    pub fn seq(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
196        let icmp_type = self.icmp_type(buf)?;
197
198        // Sequence field only exists for certain types
199        if !matches!(
200            icmp_type,
201            types::types::ECHO_REQUEST
202                | types::types::ECHO_REPLY
203                | types::types::TIMESTAMP
204                | types::types::TIMESTAMP_REPLY
205                | types::types::INFO_REQUEST
206                | types::types::INFO_REPLY
207                | types::types::ADDRESS_MASK_REQUEST
208                | types::types::ADDRESS_MASK_REPLY
209        ) {
210            return Ok(None);
211        }
212
213        let slice = self.index.slice(buf);
214        if slice.len() < offsets::SEQ + 2 {
215            return Err(FieldError::BufferTooShort {
216                offset: self.index.start + offsets::SEQ,
217                need: 2,
218                have: slice.len().saturating_sub(offsets::SEQ),
219            });
220        }
221        Ok(Some(u16::from_be_bytes([
222            slice[offsets::SEQ],
223            slice[offsets::SEQ + 1],
224        ])))
225    }
226
227    /// Get the gateway address (for redirect messages).
228    ///
229    /// Returns None if this is not a redirect message.
230    pub fn gateway(&self, buf: &[u8]) -> Result<Option<Ipv4Addr>, FieldError> {
231        let icmp_type = self.icmp_type(buf)?;
232
233        if icmp_type != types::types::REDIRECT {
234            return Ok(None);
235        }
236
237        let slice = self.index.slice(buf);
238        if slice.len() < offsets::GATEWAY + 4 {
239            return Err(FieldError::BufferTooShort {
240                offset: self.index.start + offsets::GATEWAY,
241                need: 4,
242                have: slice.len().saturating_sub(offsets::GATEWAY),
243            });
244        }
245
246        Ok(Some(Ipv4Addr::new(
247            slice[offsets::GATEWAY],
248            slice[offsets::GATEWAY + 1],
249            slice[offsets::GATEWAY + 2],
250            slice[offsets::GATEWAY + 3],
251        )))
252    }
253
254    /// Get the next-hop MTU (for destination unreachable, fragmentation needed).
255    ///
256    /// Returns None if this is not a dest unreachable with code 4 (fragmentation needed).
257    pub fn next_hop_mtu(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
258        let icmp_type = self.icmp_type(buf)?;
259        let code = self.code(buf)?;
260
261        // Only valid for dest unreachable type 3, code 4 (fragmentation needed)
262        if icmp_type != types::types::DEST_UNREACH || code != 4 {
263            return Ok(None);
264        }
265
266        let slice = self.index.slice(buf);
267        if slice.len() < offsets::NEXT_HOP_MTU + 2 {
268            return Err(FieldError::BufferTooShort {
269                offset: self.index.start + offsets::NEXT_HOP_MTU,
270                need: 2,
271                have: slice.len().saturating_sub(offsets::NEXT_HOP_MTU),
272            });
273        }
274
275        Ok(Some(u16::from_be_bytes([
276            slice[offsets::NEXT_HOP_MTU],
277            slice[offsets::NEXT_HOP_MTU + 1],
278        ])))
279    }
280
281    /// Get the pointer field (for parameter problem).
282    ///
283    /// Returns None if this is not a parameter problem message.
284    pub fn ptr(&self, buf: &[u8]) -> Result<Option<u8>, FieldError> {
285        let icmp_type = self.icmp_type(buf)?;
286
287        if icmp_type != types::types::PARAM_PROBLEM {
288            return Ok(None);
289        }
290
291        let slice = self.index.slice(buf);
292        if slice.len() < offsets::PTR + 1 {
293            return Err(FieldError::BufferTooShort {
294                offset: self.index.start + offsets::PTR,
295                need: 1,
296                have: slice.len().saturating_sub(offsets::PTR),
297            });
298        }
299
300        Ok(Some(slice[offsets::PTR]))
301    }
302
303    /// Get the originate timestamp (for timestamp request/reply).
304    ///
305    /// Returns None if this is not a timestamp message.
306    /// Timestamp is in milliseconds since midnight UT.
307    pub fn ts_ori(&self, buf: &[u8]) -> Result<Option<u32>, FieldError> {
308        let icmp_type = self.icmp_type(buf)?;
309
310        if !matches!(
311            icmp_type,
312            types::types::TIMESTAMP | types::types::TIMESTAMP_REPLY
313        ) {
314            return Ok(None);
315        }
316
317        let slice = self.index.slice(buf);
318        if slice.len() < offsets::TS_ORI + 4 {
319            return Err(FieldError::BufferTooShort {
320                offset: self.index.start + offsets::TS_ORI,
321                need: 4,
322                have: slice.len().saturating_sub(offsets::TS_ORI),
323            });
324        }
325
326        Ok(Some(u32::from_be_bytes([
327            slice[offsets::TS_ORI],
328            slice[offsets::TS_ORI + 1],
329            slice[offsets::TS_ORI + 2],
330            slice[offsets::TS_ORI + 3],
331        ])))
332    }
333
334    /// Get the receive timestamp (for timestamp request/reply).
335    ///
336    /// Returns None if this is not a timestamp message.
337    /// Timestamp is in milliseconds since midnight UT.
338    pub fn ts_rx(&self, buf: &[u8]) -> Result<Option<u32>, FieldError> {
339        let icmp_type = self.icmp_type(buf)?;
340
341        if !matches!(
342            icmp_type,
343            types::types::TIMESTAMP | types::types::TIMESTAMP_REPLY
344        ) {
345            return Ok(None);
346        }
347
348        let slice = self.index.slice(buf);
349        if slice.len() < offsets::TS_RX + 4 {
350            return Err(FieldError::BufferTooShort {
351                offset: self.index.start + offsets::TS_RX,
352                need: 4,
353                have: slice.len().saturating_sub(offsets::TS_RX),
354            });
355        }
356
357        Ok(Some(u32::from_be_bytes([
358            slice[offsets::TS_RX],
359            slice[offsets::TS_RX + 1],
360            slice[offsets::TS_RX + 2],
361            slice[offsets::TS_RX + 3],
362        ])))
363    }
364
365    /// Get the transmit timestamp (for timestamp request/reply).
366    ///
367    /// Returns None if this is not a timestamp message.
368    /// Timestamp is in milliseconds since midnight UT.
369    pub fn ts_tx(&self, buf: &[u8]) -> Result<Option<u32>, FieldError> {
370        let icmp_type = self.icmp_type(buf)?;
371
372        if !matches!(
373            icmp_type,
374            types::types::TIMESTAMP | types::types::TIMESTAMP_REPLY
375        ) {
376            return Ok(None);
377        }
378
379        let slice = self.index.slice(buf);
380        if slice.len() < offsets::TS_TX + 4 {
381            return Err(FieldError::BufferTooShort {
382                offset: self.index.start + offsets::TS_TX,
383                need: 4,
384                have: slice.len().saturating_sub(offsets::TS_TX),
385            });
386        }
387
388        Ok(Some(u32::from_be_bytes([
389            slice[offsets::TS_TX],
390            slice[offsets::TS_TX + 1],
391            slice[offsets::TS_TX + 2],
392            slice[offsets::TS_TX + 3],
393        ])))
394    }
395
396    /// Get the address mask (for address mask request/reply).
397    ///
398    /// Returns None if this is not an address mask message.
399    pub fn addr_mask(&self, buf: &[u8]) -> Result<Option<Ipv4Addr>, FieldError> {
400        let icmp_type = self.icmp_type(buf)?;
401
402        if !matches!(
403            icmp_type,
404            types::types::ADDRESS_MASK_REQUEST | types::types::ADDRESS_MASK_REPLY
405        ) {
406            return Ok(None);
407        }
408
409        let slice = self.index.slice(buf);
410        if slice.len() < offsets::ADDR_MASK + 4 {
411            return Err(FieldError::BufferTooShort {
412                offset: self.index.start + offsets::ADDR_MASK,
413                need: 4,
414                have: slice.len().saturating_sub(offsets::ADDR_MASK),
415            });
416        }
417
418        Ok(Some(Ipv4Addr::new(
419            slice[offsets::ADDR_MASK],
420            slice[offsets::ADDR_MASK + 1],
421            slice[offsets::ADDR_MASK + 2],
422            slice[offsets::ADDR_MASK + 3],
423        )))
424    }
425
426    /// Set the ICMP type.
427    pub fn set_type(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
428        let start = self.index.start + offsets::TYPE;
429        if buf.len() < start + 1 {
430            return Err(FieldError::BufferTooShort {
431                offset: start,
432                need: 1,
433                have: buf.len().saturating_sub(start),
434            });
435        }
436        buf[start] = value;
437        Ok(())
438    }
439
440    /// Set the ICMP code.
441    pub fn set_code(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
442        let start = self.index.start + offsets::CODE;
443        if buf.len() < start + 1 {
444            return Err(FieldError::BufferTooShort {
445                offset: start,
446                need: 1,
447                have: buf.len().saturating_sub(start),
448            });
449        }
450        buf[start] = value;
451        Ok(())
452    }
453
454    /// Set the checksum.
455    pub fn set_checksum(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
456        let start = self.index.start + offsets::CHECKSUM;
457        if buf.len() < start + 2 {
458            return Err(FieldError::BufferTooShort {
459                offset: start,
460                need: 2,
461                have: buf.len().saturating_sub(start),
462            });
463        }
464        buf[start..start + 2].copy_from_slice(&value.to_be_bytes());
465        Ok(())
466    }
467
468    /// Set the identifier (for echo, timestamp).
469    pub fn set_id(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
470        let start = self.index.start + offsets::ID;
471        if buf.len() < start + 2 {
472            return Err(FieldError::BufferTooShort {
473                offset: start,
474                need: 2,
475                have: buf.len().saturating_sub(start),
476            });
477        }
478        buf[start..start + 2].copy_from_slice(&value.to_be_bytes());
479        Ok(())
480    }
481
482    /// Set the sequence number (for echo, timestamp).
483    pub fn set_seq(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
484        let start = self.index.start + offsets::SEQ;
485        if buf.len() < start + 2 {
486            return Err(FieldError::BufferTooShort {
487                offset: start,
488                need: 2,
489                have: buf.len().saturating_sub(start),
490            });
491        }
492        buf[start..start + 2].copy_from_slice(&value.to_be_bytes());
493        Ok(())
494    }
495
496    /// Set the gateway address (for redirect).
497    pub fn set_gateway(&self, buf: &mut [u8], value: Ipv4Addr) -> Result<(), FieldError> {
498        let start = self.index.start + offsets::GATEWAY;
499        if buf.len() < start + 4 {
500            return Err(FieldError::BufferTooShort {
501                offset: start,
502                need: 4,
503                have: buf.len().saturating_sub(start),
504            });
505        }
506        buf[start..start + 4].copy_from_slice(&value.octets());
507        Ok(())
508    }
509
510    /// Set the next-hop MTU (for destination unreachable, fragmentation needed).
511    pub fn set_next_hop_mtu(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
512        let start = self.index.start + offsets::NEXT_HOP_MTU;
513        if buf.len() < start + 2 {
514            return Err(FieldError::BufferTooShort {
515                offset: start,
516                need: 2,
517                have: buf.len().saturating_sub(start),
518            });
519        }
520        buf[start..start + 2].copy_from_slice(&value.to_be_bytes());
521        Ok(())
522    }
523
524    /// Set the pointer field (for parameter problem).
525    pub fn set_ptr(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
526        let start = self.index.start + offsets::PTR;
527        if buf.len() < start + 1 {
528            return Err(FieldError::BufferTooShort {
529                offset: start,
530                need: 1,
531                have: buf.len().saturating_sub(start),
532            });
533        }
534        buf[start] = value;
535        Ok(())
536    }
537
538    /// Set the originate timestamp (for timestamp request/reply).
539    pub fn set_ts_ori(&self, buf: &mut [u8], value: u32) -> Result<(), FieldError> {
540        let start = self.index.start + offsets::TS_ORI;
541        if buf.len() < start + 4 {
542            return Err(FieldError::BufferTooShort {
543                offset: start,
544                need: 4,
545                have: buf.len().saturating_sub(start),
546            });
547        }
548        buf[start..start + 4].copy_from_slice(&value.to_be_bytes());
549        Ok(())
550    }
551
552    /// Set the receive timestamp (for timestamp request/reply).
553    pub fn set_ts_rx(&self, buf: &mut [u8], value: u32) -> Result<(), FieldError> {
554        let start = self.index.start + offsets::TS_RX;
555        if buf.len() < start + 4 {
556            return Err(FieldError::BufferTooShort {
557                offset: start,
558                need: 4,
559                have: buf.len().saturating_sub(start),
560            });
561        }
562        buf[start..start + 4].copy_from_slice(&value.to_be_bytes());
563        Ok(())
564    }
565
566    /// Set the transmit timestamp (for timestamp request/reply).
567    pub fn set_ts_tx(&self, buf: &mut [u8], value: u32) -> Result<(), FieldError> {
568        let start = self.index.start + offsets::TS_TX;
569        if buf.len() < start + 4 {
570            return Err(FieldError::BufferTooShort {
571                offset: start,
572                need: 4,
573                have: buf.len().saturating_sub(start),
574            });
575        }
576        buf[start..start + 4].copy_from_slice(&value.to_be_bytes());
577        Ok(())
578    }
579
580    /// Set the address mask (for address mask request/reply).
581    pub fn set_addr_mask(&self, buf: &mut [u8], value: Ipv4Addr) -> Result<(), FieldError> {
582        let start = self.index.start + offsets::ADDR_MASK;
583        if buf.len() < start + 4 {
584            return Err(FieldError::BufferTooShort {
585                offset: start,
586                need: 4,
587                have: buf.len().saturating_sub(start),
588            });
589        }
590        buf[start..start + 4].copy_from_slice(&value.octets());
591        Ok(())
592    }
593
594    /// Generate a summary string for display.
595    pub fn summary(&self, buf: &[u8]) -> String {
596        if let (Ok(icmp_type), Ok(code)) = (self.icmp_type(buf), self.code(buf)) {
597            let type_str = type_name(icmp_type);
598            let code_str = code_name(icmp_type, code);
599
600            // Add type-specific details
601            let details = match icmp_type {
602                types::types::REDIRECT => {
603                    if let Ok(Some(gw)) = self.gateway(buf) {
604                        format!(" gw={}", gw)
605                    } else {
606                        String::new()
607                    }
608                }
609                types::types::DEST_UNREACH if code == 4 => {
610                    // Fragmentation needed
611                    if let Ok(Some(mtu)) = self.next_hop_mtu(buf) {
612                        format!(" mtu={}", mtu)
613                    } else {
614                        String::new()
615                    }
616                }
617                types::types::PARAM_PROBLEM => {
618                    if let Ok(Some(ptr)) = self.ptr(buf) {
619                        format!(" ptr={}", ptr)
620                    } else {
621                        String::new()
622                    }
623                }
624                types::types::ECHO_REQUEST | types::types::ECHO_REPLY => {
625                    let id_str = self
626                        .id(buf)
627                        .ok()
628                        .flatten()
629                        .map(|id| format!(" id={}", id))
630                        .unwrap_or_default();
631                    let seq_str = self
632                        .seq(buf)
633                        .ok()
634                        .flatten()
635                        .map(|seq| format!(" seq={}", seq))
636                        .unwrap_or_default();
637                    format!("{}{}", id_str, seq_str)
638                }
639                _ => String::new(),
640            };
641
642            format!("ICMP {} {}{}", type_str, code_str, details)
643        } else {
644            "ICMP".to_string()
645        }
646    }
647
648    /// Get the ICMP header length (variable based on type).
649    pub fn header_len(&self, buf: &[u8]) -> usize {
650        // Most ICMP types have 8-byte header
651        // Timestamp has 20-byte header (8 base + 12 for timestamps)
652        if let Ok(icmp_type) = self.icmp_type(buf) {
653            match icmp_type {
654                types::types::TIMESTAMP | types::types::TIMESTAMP_REPLY => 20,
655                _ => ICMP_MIN_HEADER_LEN,
656            }
657        } else {
658            ICMP_MIN_HEADER_LEN
659        }
660    }
661
662    /// Get field names for this layer.
663    pub fn field_names(&self) -> &'static [&'static str] {
664        &[
665            "type",
666            "code",
667            "chksum",
668            "id",
669            "seq",
670            "gw",
671            "ptr",
672            "mtu",
673            "ts_ori",
674            "ts_rx",
675            "ts_tx",
676            "addr_mask",
677        ]
678    }
679
680    /// Get a field value by name.
681    pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
682        match name {
683            "type" => Some(self.icmp_type(buf).map(FieldValue::U8)),
684            "code" => Some(self.code(buf).map(FieldValue::U8)),
685            "chksum" => Some(self.checksum(buf).map(FieldValue::U16)),
686            "id" => Some(
687                self.id(buf)
688                    .and_then(|opt| {
689                        opt.ok_or(FieldError::InvalidValue(
690                            "id field not available for this ICMP type".into(),
691                        ))
692                    })
693                    .map(FieldValue::U16),
694            ),
695            "seq" => Some(
696                self.seq(buf)
697                    .and_then(|opt| {
698                        opt.ok_or(FieldError::InvalidValue(
699                            "seq field not available for this ICMP type".into(),
700                        ))
701                    })
702                    .map(FieldValue::U16),
703            ),
704            "gw" => Some(
705                self.gateway(buf)
706                    .and_then(|opt| {
707                        opt.ok_or(FieldError::InvalidValue(
708                            "gw field not available for this ICMP type".into(),
709                        ))
710                    })
711                    .map(|addr| FieldValue::Ipv4(addr)),
712            ),
713            "ptr" => Some(
714                self.ptr(buf)
715                    .and_then(|opt| {
716                        opt.ok_or(FieldError::InvalidValue(
717                            "ptr field not available for this ICMP type".into(),
718                        ))
719                    })
720                    .map(FieldValue::U8),
721            ),
722            "mtu" => Some(
723                self.next_hop_mtu(buf)
724                    .and_then(|opt| {
725                        opt.ok_or(FieldError::InvalidValue(
726                            "mtu field not available for this ICMP type".into(),
727                        ))
728                    })
729                    .map(FieldValue::U16),
730            ),
731            "ts_ori" => Some(
732                self.ts_ori(buf)
733                    .and_then(|opt| {
734                        opt.ok_or(FieldError::InvalidValue(
735                            "ts_ori field not available for this ICMP type".into(),
736                        ))
737                    })
738                    .map(FieldValue::U32),
739            ),
740            "ts_rx" => Some(
741                self.ts_rx(buf)
742                    .and_then(|opt| {
743                        opt.ok_or(FieldError::InvalidValue(
744                            "ts_rx field not available for this ICMP type".into(),
745                        ))
746                    })
747                    .map(FieldValue::U32),
748            ),
749            "ts_tx" => Some(
750                self.ts_tx(buf)
751                    .and_then(|opt| {
752                        opt.ok_or(FieldError::InvalidValue(
753                            "ts_tx field not available for this ICMP type".into(),
754                        ))
755                    })
756                    .map(FieldValue::U32),
757            ),
758            "addr_mask" => Some(
759                self.addr_mask(buf)
760                    .and_then(|opt| {
761                        opt.ok_or(FieldError::InvalidValue(
762                            "addr_mask field not available for this ICMP type".into(),
763                        ))
764                    })
765                    .map(|addr| FieldValue::Ipv4(addr)),
766            ),
767            _ => None,
768        }
769    }
770
771    /// Set a field value by name.
772    pub fn set_field(
773        &self,
774        buf: &mut [u8],
775        name: &str,
776        value: FieldValue,
777    ) -> Option<Result<(), FieldError>> {
778        match name {
779            "type" => {
780                if let FieldValue::U8(v) = value {
781                    Some(self.set_type(buf, v))
782                } else {
783                    Some(Err(FieldError::InvalidValue(format!(
784                        "type: expected U8, got {:?}",
785                        value
786                    ))))
787                }
788            }
789            "code" => {
790                if let FieldValue::U8(v) = value {
791                    Some(self.set_code(buf, v))
792                } else {
793                    Some(Err(FieldError::InvalidValue(format!(
794                        "code: expected U8, got {:?}",
795                        value
796                    ))))
797                }
798            }
799            "chksum" => {
800                if let FieldValue::U16(v) = value {
801                    Some(self.set_checksum(buf, v))
802                } else {
803                    Some(Err(FieldError::InvalidValue(format!(
804                        "chksum: expected U16, got {:?}",
805                        value
806                    ))))
807                }
808            }
809            "id" => {
810                if let FieldValue::U16(v) = value {
811                    Some(self.set_id(buf, v))
812                } else {
813                    Some(Err(FieldError::InvalidValue(format!(
814                        "id: expected U16, got {:?}",
815                        value
816                    ))))
817                }
818            }
819            "seq" => {
820                if let FieldValue::U16(v) = value {
821                    Some(self.set_seq(buf, v))
822                } else {
823                    Some(Err(FieldError::InvalidValue(format!(
824                        "seq: expected U16, got {:?}",
825                        value
826                    ))))
827                }
828            }
829            "gw" => {
830                if let FieldValue::Ipv4(v) = value {
831                    Some(self.set_gateway(buf, v))
832                } else {
833                    Some(Err(FieldError::InvalidValue(format!(
834                        "gw: expected Ipv4, got {:?}",
835                        value
836                    ))))
837                }
838            }
839            "ptr" => {
840                if let FieldValue::U8(v) = value {
841                    Some(self.set_ptr(buf, v))
842                } else {
843                    Some(Err(FieldError::InvalidValue(format!(
844                        "ptr: expected U8, got {:?}",
845                        value
846                    ))))
847                }
848            }
849            "mtu" => {
850                if let FieldValue::U16(v) = value {
851                    Some(self.set_next_hop_mtu(buf, v))
852                } else {
853                    Some(Err(FieldError::InvalidValue(format!(
854                        "mtu: expected U16, got {:?}",
855                        value
856                    ))))
857                }
858            }
859            "ts_ori" => {
860                if let FieldValue::U32(v) = value {
861                    Some(self.set_ts_ori(buf, v))
862                } else {
863                    Some(Err(FieldError::InvalidValue(format!(
864                        "ts_ori: expected U32, got {:?}",
865                        value
866                    ))))
867                }
868            }
869            "ts_rx" => {
870                if let FieldValue::U32(v) = value {
871                    Some(self.set_ts_rx(buf, v))
872                } else {
873                    Some(Err(FieldError::InvalidValue(format!(
874                        "ts_rx: expected U32, got {:?}",
875                        value
876                    ))))
877                }
878            }
879            "ts_tx" => {
880                if let FieldValue::U32(v) = value {
881                    Some(self.set_ts_tx(buf, v))
882                } else {
883                    Some(Err(FieldError::InvalidValue(format!(
884                        "ts_tx: expected U32, got {:?}",
885                        value
886                    ))))
887                }
888            }
889            "addr_mask" => {
890                if let FieldValue::Ipv4(v) = value {
891                    Some(self.set_addr_mask(buf, v))
892                } else {
893                    Some(Err(FieldError::InvalidValue(format!(
894                        "addr_mask: expected Ipv4, got {:?}",
895                        value
896                    ))))
897                }
898            }
899            _ => None,
900        }
901    }
902}
903
904impl Layer for IcmpLayer {
905    fn kind(&self) -> LayerKind {
906        LayerKind::Icmp
907    }
908
909    fn summary(&self, data: &[u8]) -> String {
910        self.summary(data)
911    }
912
913    fn header_len(&self, data: &[u8]) -> usize {
914        self.header_len(data)
915    }
916
917    fn hashret(&self, data: &[u8]) -> Vec<u8> {
918        // For echo request/reply, return id+seq as hash
919        if let (Ok(Some(id)), Ok(Some(seq))) = (self.id(data), self.seq(data)) {
920            let mut hash = Vec::with_capacity(4);
921            hash.extend_from_slice(&id.to_be_bytes());
922            hash.extend_from_slice(&seq.to_be_bytes());
923            hash
924        } else {
925            // For other types, return empty (no session concept)
926            vec![]
927        }
928    }
929
930    fn answers(&self, data: &[u8], other: &Self, other_data: &[u8]) -> bool {
931        // Check if this ICMP message answers another
932        if let (Ok(self_type), Ok(other_type)) = (self.icmp_type(data), other.icmp_type(other_data))
933        {
934            // Check type pairs
935            for &(req_type, reply_type) in ICMP_ANSWERS {
936                if self_type == reply_type && other_type == req_type {
937                    // For echo and similar, also check id+seq match
938                    if let (
939                        Ok(Some(self_id)),
940                        Ok(Some(other_id)),
941                        Ok(Some(self_seq)),
942                        Ok(Some(other_seq)),
943                    ) = (
944                        self.id(data),
945                        other.id(other_data),
946                        self.seq(data),
947                        other.seq(other_data),
948                    ) {
949                        return self_id == other_id && self_seq == other_seq;
950                    }
951                    return true;
952                }
953            }
954        }
955        false
956    }
957
958    fn extract_padding<'a>(&self, data: &'a [u8]) -> (&'a [u8], &'a [u8]) {
959        // ICMP doesn't have a length field, so we can't determine padding
960        // Return the entire ICMP message as data, no padding
961        let header_len = self.header_len(data);
962        let start = self.index.start;
963        let end = data.len();
964
965        if start + header_len <= end {
966            (&data[start..end], &[])
967        } else {
968            (&data[start..end], &[])
969        }
970    }
971
972    fn field_names(&self) -> &'static [&'static str] {
973        self.field_names()
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980
981    #[test]
982    fn test_icmp_echo_parse() {
983        // ICMP echo request: type=8, code=0, checksum=0x1234, id=0x5678, seq=0x0001
984        let data = [
985            0x08, // type = 8 (echo request)
986            0x00, // code = 0
987            0x12, 0x34, // checksum
988            0x56, 0x78, // id = 0x5678
989            0x00, 0x01, // seq = 1
990            // Payload
991            0x48, 0x65, 0x6c, 0x6c, 0x6f, // "Hello"
992        ];
993
994        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
995        let icmp = IcmpLayer::new(index);
996
997        assert_eq!(icmp.icmp_type(&data).unwrap(), 8);
998        assert_eq!(icmp.code(&data).unwrap(), 0);
999        assert_eq!(icmp.checksum(&data).unwrap(), 0x1234);
1000        assert_eq!(icmp.id(&data).unwrap(), Some(0x5678));
1001        assert_eq!(icmp.seq(&data).unwrap(), Some(1));
1002    }
1003
1004    #[test]
1005    fn test_icmp_summary() {
1006        let data = [
1007            0x08, // type = 8 (echo request)
1008            0x00, // code = 0
1009            0x00, 0x00, // checksum
1010            0x00, 0x01, // id
1011            0x00, 0x01, // seq
1012        ];
1013
1014        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1015        let icmp = IcmpLayer::new(index);
1016
1017        let summary = icmp.summary(&data);
1018        assert!(summary.contains("echo-request"));
1019    }
1020
1021    #[test]
1022    fn test_icmp_set_fields() {
1023        let mut data = vec![0u8; 8];
1024        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1025        let icmp = IcmpLayer::new(index);
1026
1027        icmp.set_type(&mut data, types::types::ECHO_REPLY).unwrap();
1028        icmp.set_code(&mut data, 0).unwrap();
1029        icmp.set_checksum(&mut data, 0xABCD).unwrap();
1030        icmp.set_id(&mut data, 0x1234).unwrap();
1031        icmp.set_seq(&mut data, 42).unwrap();
1032
1033        assert_eq!(icmp.icmp_type(&data).unwrap(), types::types::ECHO_REPLY);
1034        assert_eq!(icmp.code(&data).unwrap(), 0);
1035        assert_eq!(icmp.checksum(&data).unwrap(), 0xABCD);
1036        assert_eq!(icmp.id(&data).unwrap(), Some(0x1234));
1037        assert_eq!(icmp.seq(&data).unwrap(), Some(42));
1038    }
1039
1040    #[test]
1041    fn test_icmp_answers() {
1042        // Echo request
1043        let request_data = [
1044            0x08, 0x00, // type=8, code=0
1045            0x00, 0x00, // checksum
1046            0x12, 0x34, // id=0x1234
1047            0x00, 0x05, // seq=5
1048        ];
1049
1050        // Echo reply
1051        let reply_data = [
1052            0x00, 0x00, // type=0, code=0
1053            0x00, 0x00, // checksum
1054            0x12, 0x34, // id=0x1234 (same)
1055            0x00, 0x05, // seq=5 (same)
1056        ];
1057
1058        let request_index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1059        let reply_index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1060
1061        let request = IcmpLayer::new(request_index);
1062        let reply = IcmpLayer::new(reply_index);
1063
1064        assert!(reply.answers(&reply_data, &request, &request_data));
1065        assert!(!request.answers(&request_data, &reply, &reply_data));
1066    }
1067
1068    #[test]
1069    fn test_icmp_dest_unreach_no_id() {
1070        // Destination unreachable has no id/seq fields
1071        let data = [
1072            0x03, // type = 3 (dest unreachable)
1073            0x03, // code = 3 (port unreachable)
1074            0x00, 0x00, // checksum
1075            0x00, // unused
1076            0x00, // length
1077            0x00, 0x00, // unused/next-hop MTU
1078        ];
1079
1080        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1081        let icmp = IcmpLayer::new(index);
1082
1083        assert_eq!(icmp.icmp_type(&data).unwrap(), 3);
1084        assert_eq!(icmp.code(&data).unwrap(), 3);
1085        assert_eq!(icmp.id(&data).unwrap(), None);
1086        assert_eq!(icmp.seq(&data).unwrap(), None);
1087    }
1088
1089    #[test]
1090    fn test_icmp_redirect_gateway() {
1091        // Redirect message with gateway
1092        let data = [
1093            0x05, // type = 5 (redirect)
1094            0x01, // code = 1 (redirect host)
1095            0x00, 0x00, // checksum
1096            192, 168, 1, 1, // gateway = 192.168.1.1
1097        ];
1098
1099        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1100        let icmp = IcmpLayer::new(index);
1101
1102        assert_eq!(icmp.icmp_type(&data).unwrap(), 5);
1103        assert_eq!(
1104            icmp.gateway(&data).unwrap(),
1105            Some(Ipv4Addr::new(192, 168, 1, 1))
1106        );
1107    }
1108
1109    #[test]
1110    fn test_icmp_dest_unreach_mtu() {
1111        // Destination unreachable, fragmentation needed (code 4)
1112        let mut data = vec![
1113            0x03, // type = 3 (dest unreachable)
1114            0x04, // code = 4 (fragmentation needed)
1115            0x00, 0x00, // checksum
1116            0x00, // unused
1117            0x00, // length
1118            0x05, 0xdc, // next-hop MTU = 1500
1119        ];
1120
1121        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1122        let icmp = IcmpLayer::new(index);
1123
1124        assert_eq!(icmp.icmp_type(&data).unwrap(), 3);
1125        assert_eq!(icmp.code(&data).unwrap(), 4);
1126        assert_eq!(icmp.next_hop_mtu(&data).unwrap(), Some(1500));
1127
1128        // Test setter
1129        icmp.set_next_hop_mtu(&mut data, 1400).unwrap();
1130        assert_eq!(icmp.next_hop_mtu(&data).unwrap(), Some(1400));
1131    }
1132
1133    #[test]
1134    fn test_icmp_param_problem_ptr() {
1135        // Parameter problem with pointer
1136        let mut data = vec![
1137            0x0c, // type = 12 (parameter problem)
1138            0x00, // code = 0
1139            0x00, 0x00, // checksum
1140            0x14, // ptr = 20 (byte offset of problem)
1141            0x00, // unused
1142            0x00, 0x00, // length/unused
1143        ];
1144
1145        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1146        let icmp = IcmpLayer::new(index);
1147
1148        assert_eq!(icmp.icmp_type(&data).unwrap(), 12);
1149        assert_eq!(icmp.ptr(&data).unwrap(), Some(20));
1150
1151        // Test setter
1152        icmp.set_ptr(&mut data, 30).unwrap();
1153        assert_eq!(icmp.ptr(&data).unwrap(), Some(30));
1154    }
1155
1156    #[test]
1157    fn test_icmp_timestamp_fields() {
1158        // Timestamp request with all timestamp fields
1159        let mut data = vec![
1160            0x0d, // type = 13 (timestamp request)
1161            0x00, // code = 0
1162            0x00, 0x00, // checksum
1163            0x12, 0x34, // id
1164            0x00, 0x01, // seq
1165            // Originate timestamp (32 bits)
1166            0x00, 0x00, 0x10, 0x00, // ts_ori = 4096
1167            // Receive timestamp (32 bits)
1168            0x00, 0x00, 0x20, 0x00, // ts_rx = 8192
1169            // Transmit timestamp (32 bits)
1170            0x00, 0x00, 0x30, 0x00, // ts_tx = 12288
1171        ];
1172
1173        let index = LayerIndex::new(LayerKind::Icmp, 0, 20); // Timestamp header is 20 bytes
1174        let icmp = IcmpLayer::new(index);
1175
1176        assert_eq!(icmp.icmp_type(&data).unwrap(), 13);
1177        assert_eq!(icmp.code(&data).unwrap(), 0);
1178        assert_eq!(icmp.id(&data).unwrap(), Some(0x1234));
1179        assert_eq!(icmp.seq(&data).unwrap(), Some(1));
1180        assert_eq!(icmp.ts_ori(&data).unwrap(), Some(4096));
1181        assert_eq!(icmp.ts_rx(&data).unwrap(), Some(8192));
1182        assert_eq!(icmp.ts_tx(&data).unwrap(), Some(12288));
1183
1184        // Test setters
1185        icmp.set_ts_ori(&mut data, 5000).unwrap();
1186        icmp.set_ts_rx(&mut data, 6000).unwrap();
1187        icmp.set_ts_tx(&mut data, 7000).unwrap();
1188
1189        assert_eq!(icmp.ts_ori(&data).unwrap(), Some(5000));
1190        assert_eq!(icmp.ts_rx(&data).unwrap(), Some(6000));
1191        assert_eq!(icmp.ts_tx(&data).unwrap(), Some(7000));
1192    }
1193
1194    #[test]
1195    fn test_icmp_timestamp_header_len() {
1196        // Timestamp messages have 20-byte header
1197        let data = vec![
1198            0x0d, // type = 13 (timestamp request)
1199            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1200            0x00, 0x00, 0x00, 0x00, 0x00,
1201        ];
1202
1203        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1204        let icmp = IcmpLayer::new(index);
1205
1206        assert_eq!(icmp.header_len(&data), 20);
1207    }
1208
1209    #[test]
1210    fn test_icmp_address_mask() {
1211        // Address mask request
1212        let mut data = vec![
1213            0x11, // type = 17 (address mask request)
1214            0x00, // code = 0
1215            0x00, 0x00, // checksum
1216            255, 255, 255, 0, // addr_mask = 255.255.255.0
1217        ];
1218
1219        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1220        let icmp = IcmpLayer::new(index);
1221
1222        assert_eq!(icmp.icmp_type(&data).unwrap(), 17);
1223        assert_eq!(
1224            icmp.addr_mask(&data).unwrap(),
1225            Some(Ipv4Addr::new(255, 255, 255, 0))
1226        );
1227
1228        // Test setter
1229        icmp.set_addr_mask(&mut data, Ipv4Addr::new(255, 255, 0, 0))
1230            .unwrap();
1231        assert_eq!(
1232            icmp.addr_mask(&data).unwrap(),
1233            Some(Ipv4Addr::new(255, 255, 0, 0))
1234        );
1235    }
1236
1237    #[test]
1238    fn test_icmp_conditional_fields_none() {
1239        // Echo request should not have gateway, ptr, mtu, or addr_mask
1240        let data = vec![
1241            0x08, // type = 8 (echo request)
1242            0x00, 0x00, 0x00, 0x12, 0x34, 0x00, 0x01,
1243        ];
1244
1245        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1246        let icmp = IcmpLayer::new(index);
1247
1248        assert_eq!(icmp.gateway(&data).unwrap(), None);
1249        assert_eq!(icmp.ptr(&data).unwrap(), None);
1250        assert_eq!(icmp.next_hop_mtu(&data).unwrap(), None);
1251        assert_eq!(icmp.addr_mask(&data).unwrap(), None);
1252        assert_eq!(icmp.ts_ori(&data).unwrap(), None);
1253        assert_eq!(icmp.ts_rx(&data).unwrap(), None);
1254        assert_eq!(icmp.ts_tx(&data).unwrap(), None);
1255    }
1256
1257    #[test]
1258    fn test_icmp_summary_with_details() {
1259        // Test that summary includes type-specific details
1260
1261        // Echo with id and seq
1262        let echo_data = vec![
1263            0x08, 0x00, 0x00, 0x00, 0x12, 0x34, // id = 0x1234
1264            0x00, 0x05, // seq = 5
1265        ];
1266        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1267        let icmp = IcmpLayer::new(index);
1268        let summary = icmp.summary(&echo_data);
1269        assert!(summary.contains("id="));
1270        assert!(summary.contains("seq="));
1271
1272        // Redirect with gateway
1273        let redirect_data = vec![
1274            0x05, 0x01, 0x00, 0x00, 192, 168, 1, 1, // gateway
1275        ];
1276        let icmp = IcmpLayer::new(index);
1277        let summary = icmp.summary(&redirect_data);
1278        assert!(summary.contains("gw="));
1279
1280        // Parameter problem with ptr
1281        let pp_data = vec![
1282            0x0c, 0x00, 0x00, 0x00, 0x14, // ptr = 20
1283            0x00, 0x00, 0x00,
1284        ];
1285        let icmp = IcmpLayer::new(index);
1286        let summary = icmp.summary(&pp_data);
1287        assert!(summary.contains("ptr="));
1288
1289        // Dest unreachable with MTU
1290        let du_data = vec![
1291            0x03, 0x04, 0x00, 0x00, // type=3, code=4 (frag needed)
1292            0x00, 0x00, 0x05, 0xdc, // mtu = 1500
1293        ];
1294        let icmp = IcmpLayer::new(index);
1295        let summary = icmp.summary(&du_data);
1296        assert!(summary.contains("mtu="));
1297    }
1298
1299    #[test]
1300    fn test_icmp_set_gateway() {
1301        // Test setting gateway for redirect
1302        let mut data = vec![
1303            0x05, 0x01, 0x00, 0x00, 0, 0, 0, 0, // gateway (to be set)
1304        ];
1305
1306        let index = LayerIndex::new(LayerKind::Icmp, 0, ICMP_MIN_HEADER_LEN);
1307        let icmp = IcmpLayer::new(index);
1308
1309        icmp.set_gateway(&mut data, Ipv4Addr::new(10, 0, 0, 1))
1310            .unwrap();
1311        assert_eq!(
1312            icmp.gateway(&data).unwrap(),
1313            Some(Ipv4Addr::new(10, 0, 0, 1))
1314        );
1315    }
1316
1317    #[test]
1318    fn test_icmp_error_type_detection() {
1319        // Test that error type detection works
1320        assert!(error::is_error_type(types::types::DEST_UNREACH));
1321        assert!(error::is_error_type(types::types::TIME_EXCEEDED));
1322        assert!(error::is_error_type(types::types::PARAM_PROBLEM));
1323        assert!(error::is_error_type(types::types::SOURCE_QUENCH));
1324        assert!(error::is_error_type(types::types::REDIRECT));
1325
1326        assert!(!error::is_error_type(types::types::ECHO_REQUEST));
1327        assert!(!error::is_error_type(types::types::ECHO_REPLY));
1328    }
1329
1330    #[test]
1331    fn test_icmp_error_payload_offset() {
1332        // Error types should have payload at offset 8
1333        assert_eq!(
1334            error::error_payload_offset(types::types::DEST_UNREACH),
1335            Some(8)
1336        );
1337        assert_eq!(
1338            error::error_payload_offset(types::types::TIME_EXCEEDED),
1339            Some(8)
1340        );
1341
1342        // Non-error types should return None
1343        assert_eq!(
1344            error::error_payload_offset(types::types::ECHO_REQUEST),
1345            None
1346        );
1347    }
1348}