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