Skip to main content

stackforge_core/layer/icmpv6/
mod.rs

1//! `ICMPv6` (Internet Control Message Protocol for IPv6) layer implementation.
2//!
3//! This module provides types and functions for working with `ICMPv6` packets,
4//! including parsing, field access, and checksum calculation using the IPv6
5//! pseudo-header.
6
7pub mod builder;
8
9pub use builder::Icmpv6Builder;
10
11use crate::layer::field::{FieldError, FieldValue};
12use crate::layer::{Layer, LayerIndex, LayerKind};
13use std::net::Ipv6Addr;
14
15/// `ICMPv6` minimum header length (8 bytes).
16pub const ICMPV6_MIN_HEADER_LEN: usize = 8;
17
18/// Field offsets within the `ICMPv6` header.
19pub mod offsets {
20    /// `ICMPv6` Type (8 bits)
21    pub const TYPE: usize = 0;
22    /// `ICMPv6` Code (8 bits)
23    pub const CODE: usize = 1;
24    /// Checksum (16 bits)
25    pub const CHECKSUM: usize = 2;
26    /// Type-specific bytes start at offset 4
27    /// Echo request/reply: ID at offset 4, Seq at offset 6
28    pub const ID: usize = 4;
29    pub const SEQ: usize = 6;
30    /// Neighbor Solicit/Advert & Router Advert: target address at offset 8
31    pub const TARGET_ADDR: usize = 8;
32    /// Packet Too Big: MTU at offset 4 (4 bytes)
33    pub const MTU: usize = 4;
34}
35
36/// `ICMPv6` type constants.
37pub mod types {
38    /// Destination Unreachable
39    pub const DEST_UNREACH: u8 = 1;
40    /// Packet Too Big
41    pub const PKT_TOO_BIG: u8 = 2;
42    /// Time Exceeded
43    pub const TIME_EXCEEDED: u8 = 3;
44    /// Parameter Problem
45    pub const PARAM_PROBLEM: u8 = 4;
46    /// Echo Request
47    pub const ECHO_REQUEST: u8 = 128;
48    /// Echo Reply
49    pub const ECHO_REPLY: u8 = 129;
50    /// Router Solicitation (NDP)
51    pub const ROUTER_SOLICIT: u8 = 133;
52    /// Router Advertisement (NDP)
53    pub const ROUTER_ADVERT: u8 = 134;
54    /// Neighbor Solicitation (NDP)
55    pub const NEIGHBOR_SOLICIT: u8 = 135;
56    /// Neighbor Advertisement (NDP)
57    pub const NEIGHBOR_ADVERT: u8 = 136;
58    /// Redirect Message (NDP)
59    pub const REDIRECT: u8 = 137;
60
61    /// Get the name of an `ICMPv6` type.
62    #[must_use]
63    pub fn name(t: u8) -> &'static str {
64        match t {
65            DEST_UNREACH => "dest-unreach",
66            PKT_TOO_BIG => "pkt-too-big",
67            TIME_EXCEEDED => "time-exceeded",
68            PARAM_PROBLEM => "param-problem",
69            ECHO_REQUEST => "echo-request",
70            ECHO_REPLY => "echo-reply",
71            ROUTER_SOLICIT => "router-solicit",
72            ROUTER_ADVERT => "router-advert",
73            NEIGHBOR_SOLICIT => "neighbor-solicit",
74            NEIGHBOR_ADVERT => "neighbor-advert",
75            REDIRECT => "redirect",
76            _ => "unknown",
77        }
78    }
79}
80
81/// Compute the `ICMPv6` checksum over the IPv6 pseudo-header + `ICMPv6` data.
82///
83/// The `ICMPv6` checksum covers:
84/// 1. IPv6 pseudo-header: src (16 bytes), dst (16 bytes), upper-layer length (4 bytes),
85///    zeros (3 bytes), next header = 58 (1 byte)
86/// 2. The `ICMPv6` message (type, code, checksum=0, and payload)
87///
88/// Returns the 16-bit checksum in network byte order.
89#[must_use]
90pub fn icmpv6_checksum(src: Ipv6Addr, dst: Ipv6Addr, icmpv6_data: &[u8]) -> u16 {
91    let mut sum: u32 = 0;
92
93    // IPv6 pseudo-header
94    let src_bytes = src.octets();
95    let dst_bytes = dst.octets();
96
97    // Sum source address (16 bytes as 8 x u16)
98    for chunk in src_bytes.chunks(2) {
99        sum += u32::from(u16::from_be_bytes([chunk[0], chunk[1]]));
100    }
101
102    // Sum destination address (16 bytes as 8 x u16)
103    for chunk in dst_bytes.chunks(2) {
104        sum += u32::from(u16::from_be_bytes([chunk[0], chunk[1]]));
105    }
106
107    // Upper-layer packet length (4 bytes big-endian)
108    let upper_len = icmpv6_data.len() as u32;
109    sum += upper_len >> 16;
110    sum += upper_len & 0xFFFF;
111
112    // Next header = 58 (ICMPv6), preceded by 3 zero bytes
113    // In the pseudo-header format: [0, 0, 0, 58]
114    // As two u16s: [0x0000, 0x003A]
115    sum += 0x003Au32; // 58 = 0x3A
116
117    // Sum ICMPv6 data
118    let mut chunks = icmpv6_data.chunks_exact(2);
119    for chunk in &mut chunks {
120        sum += u32::from(u16::from_be_bytes([chunk[0], chunk[1]]));
121    }
122    // Handle odd byte
123    if let Some(&last) = chunks.remainder().first() {
124        sum += u32::from(last) << 8;
125    }
126
127    // Fold carries
128    while sum >> 16 != 0 {
129        sum = (sum & 0xFFFF) + (sum >> 16);
130    }
131
132    !(sum as u16)
133}
134
135/// Verify an `ICMPv6` checksum.
136///
137/// Returns true if the checksum is valid (result of checksumming the full
138/// message with checksum field included should be 0 or 0xFFFF).
139#[must_use]
140pub fn verify_icmpv6_checksum(src: Ipv6Addr, dst: Ipv6Addr, icmpv6_data: &[u8]) -> bool {
141    let result = icmpv6_checksum(src, dst, icmpv6_data);
142    result == 0 || result == 0xFFFF
143}
144
145/// An `ICMPv6` layer view.
146///
147/// Uses the "Lazy Zero-Copy View" pattern: holds only layer boundaries
148/// and reads fields directly from the buffer on demand.
149#[derive(Debug, Clone)]
150pub struct Icmpv6Layer {
151    pub index: LayerIndex,
152}
153
154impl Icmpv6Layer {
155    /// Create a new `ICMPv6` layer view.
156    #[must_use]
157    pub fn new(index: LayerIndex) -> Self {
158        Self { index }
159    }
160
161    /// Create a layer at offset 0.
162    #[must_use]
163    pub const fn at_start() -> Self {
164        Self {
165            index: LayerIndex::new(LayerKind::Icmpv6, 0, ICMPV6_MIN_HEADER_LEN),
166        }
167    }
168
169    // ========== Field Readers ==========
170
171    /// Get the `ICMPv6` type.
172    pub fn icmpv6_type(&self, buf: &[u8]) -> Result<u8, FieldError> {
173        let slice = self.index.slice(buf);
174        if slice.is_empty() {
175            return Err(FieldError::BufferTooShort {
176                offset: self.index.start + offsets::TYPE,
177                need: 1,
178                have: 0,
179            });
180        }
181        Ok(slice[offsets::TYPE])
182    }
183
184    /// Get the `ICMPv6` code.
185    pub fn code(&self, buf: &[u8]) -> Result<u8, FieldError> {
186        let slice = self.index.slice(buf);
187        if slice.len() < offsets::CODE + 1 {
188            return Err(FieldError::BufferTooShort {
189                offset: self.index.start + offsets::CODE,
190                need: 1,
191                have: slice.len().saturating_sub(offsets::CODE),
192            });
193        }
194        Ok(slice[offsets::CODE])
195    }
196
197    /// Get the checksum.
198    pub fn checksum(&self, buf: &[u8]) -> Result<u16, FieldError> {
199        let slice = self.index.slice(buf);
200        if slice.len() < offsets::CHECKSUM + 2 {
201            return Err(FieldError::BufferTooShort {
202                offset: self.index.start + offsets::CHECKSUM,
203                need: 2,
204                have: slice.len().saturating_sub(offsets::CHECKSUM),
205            });
206        }
207        Ok(u16::from_be_bytes([
208            slice[offsets::CHECKSUM],
209            slice[offsets::CHECKSUM + 1],
210        ]))
211    }
212
213    /// Get the identifier field (for echo request/reply only).
214    ///
215    /// Returns None if this `ICMPv6` type doesn't have an ID field.
216    pub fn id(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
217        let icmpv6_type = self.icmpv6_type(buf)?;
218        if !matches!(icmpv6_type, types::ECHO_REQUEST | types::ECHO_REPLY) {
219            return Ok(None);
220        }
221        let slice = self.index.slice(buf);
222        if slice.len() < offsets::ID + 2 {
223            return Err(FieldError::BufferTooShort {
224                offset: self.index.start + offsets::ID,
225                need: 2,
226                have: slice.len().saturating_sub(offsets::ID),
227            });
228        }
229        Ok(Some(u16::from_be_bytes([
230            slice[offsets::ID],
231            slice[offsets::ID + 1],
232        ])))
233    }
234
235    /// Get the sequence number field (for echo request/reply only).
236    ///
237    /// Returns None if this `ICMPv6` type doesn't have a sequence field.
238    pub fn seq(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
239        let icmpv6_type = self.icmpv6_type(buf)?;
240        if !matches!(icmpv6_type, types::ECHO_REQUEST | types::ECHO_REPLY) {
241            return Ok(None);
242        }
243        let slice = self.index.slice(buf);
244        if slice.len() < offsets::SEQ + 2 {
245            return Err(FieldError::BufferTooShort {
246                offset: self.index.start + offsets::SEQ,
247                need: 2,
248                have: slice.len().saturating_sub(offsets::SEQ),
249            });
250        }
251        Ok(Some(u16::from_be_bytes([
252            slice[offsets::SEQ],
253            slice[offsets::SEQ + 1],
254        ])))
255    }
256
257    /// Get the target address (for Neighbor Solicitation/Advertisement).
258    ///
259    /// Returns None if this `ICMPv6` type doesn't have a target address.
260    pub fn target_addr(&self, buf: &[u8]) -> Result<Option<Ipv6Addr>, FieldError> {
261        let icmpv6_type = self.icmpv6_type(buf)?;
262        if !matches!(
263            icmpv6_type,
264            types::NEIGHBOR_SOLICIT | types::NEIGHBOR_ADVERT | types::REDIRECT
265        ) {
266            return Ok(None);
267        }
268        let start = self.index.start + offsets::TARGET_ADDR;
269        if buf.len() < start + 16 {
270            return Err(FieldError::BufferTooShort {
271                offset: start,
272                need: 16,
273                have: buf.len().saturating_sub(start),
274            });
275        }
276        let mut addr_bytes = [0u8; 16];
277        addr_bytes.copy_from_slice(&buf[start..start + 16]);
278        Ok(Some(Ipv6Addr::from(addr_bytes)))
279    }
280
281    /// Get the MTU (for Packet Too Big only).
282    ///
283    /// Returns None if this is not a Packet Too Big message.
284    pub fn mtu(&self, buf: &[u8]) -> Result<Option<u32>, FieldError> {
285        let icmpv6_type = self.icmpv6_type(buf)?;
286        if icmpv6_type != types::PKT_TOO_BIG {
287            return Ok(None);
288        }
289        let slice = self.index.slice(buf);
290        if slice.len() < offsets::MTU + 4 {
291            return Err(FieldError::BufferTooShort {
292                offset: self.index.start + offsets::MTU,
293                need: 4,
294                have: slice.len().saturating_sub(offsets::MTU),
295            });
296        }
297        Ok(Some(u32::from_be_bytes([
298            slice[offsets::MTU],
299            slice[offsets::MTU + 1],
300            slice[offsets::MTU + 2],
301            slice[offsets::MTU + 3],
302        ])))
303    }
304
305    // ========== Field Writers ==========
306
307    /// Set the `ICMPv6` type.
308    pub fn set_type(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
309        let offset = self.index.start + offsets::TYPE;
310        if buf.len() <= offset {
311            return Err(FieldError::BufferTooShort {
312                offset,
313                need: 1,
314                have: buf.len().saturating_sub(offset),
315            });
316        }
317        buf[offset] = value;
318        Ok(())
319    }
320
321    /// Set the `ICMPv6` code.
322    pub fn set_code(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
323        let offset = self.index.start + offsets::CODE;
324        if buf.len() <= offset {
325            return Err(FieldError::BufferTooShort {
326                offset,
327                need: 1,
328                have: buf.len().saturating_sub(offset),
329            });
330        }
331        buf[offset] = value;
332        Ok(())
333    }
334
335    /// Set the checksum.
336    pub fn set_checksum(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
337        let offset = self.index.start + offsets::CHECKSUM;
338        if buf.len() < offset + 2 {
339            return Err(FieldError::BufferTooShort {
340                offset,
341                need: 2,
342                have: buf.len().saturating_sub(offset),
343            });
344        }
345        buf[offset..offset + 2].copy_from_slice(&value.to_be_bytes());
346        Ok(())
347    }
348
349    /// Set the identifier field (for echo request/reply).
350    pub fn set_id(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
351        let offset = self.index.start + offsets::ID;
352        if buf.len() < offset + 2 {
353            return Err(FieldError::BufferTooShort {
354                offset,
355                need: 2,
356                have: buf.len().saturating_sub(offset),
357            });
358        }
359        buf[offset..offset + 2].copy_from_slice(&value.to_be_bytes());
360        Ok(())
361    }
362
363    /// Set the sequence number field.
364    pub fn set_seq(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
365        let offset = self.index.start + offsets::SEQ;
366        if buf.len() < offset + 2 {
367            return Err(FieldError::BufferTooShort {
368                offset,
369                need: 2,
370                have: buf.len().saturating_sub(offset),
371            });
372        }
373        buf[offset..offset + 2].copy_from_slice(&value.to_be_bytes());
374        Ok(())
375    }
376
377    // ========== Dynamic Field Access ==========
378
379    /// Get a field value by name.
380    pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
381        match name {
382            "type" => Some(self.icmpv6_type(buf).map(FieldValue::U8)),
383            "code" => Some(self.code(buf).map(FieldValue::U8)),
384            "chksum" => Some(self.checksum(buf).map(FieldValue::U16)),
385            "id" => Some(
386                self.id(buf)
387                    .and_then(|opt| {
388                        opt.ok_or(FieldError::InvalidValue(
389                            "id field not available for this ICMPv6 type".into(),
390                        ))
391                    })
392                    .map(FieldValue::U16),
393            ),
394            "seq" => Some(
395                self.seq(buf)
396                    .and_then(|opt| {
397                        opt.ok_or(FieldError::InvalidValue(
398                            "seq field not available for this ICMPv6 type".into(),
399                        ))
400                    })
401                    .map(FieldValue::U16),
402            ),
403            _ => None,
404        }
405    }
406
407    /// Set a field value by name.
408    pub fn set_field(
409        &self,
410        buf: &mut [u8],
411        name: &str,
412        value: FieldValue,
413    ) -> Option<Result<(), FieldError>> {
414        match (name, value) {
415            ("type", FieldValue::U8(v)) => Some(self.set_type(buf, v)),
416            ("code", FieldValue::U8(v)) => Some(self.set_code(buf, v)),
417            ("chksum", FieldValue::U16(v)) => Some(self.set_checksum(buf, v)),
418            ("id", FieldValue::U16(v)) => Some(self.set_id(buf, v)),
419            ("seq", FieldValue::U16(v)) => Some(self.set_seq(buf, v)),
420            (name, value) => Some(Err(FieldError::InvalidValue(format!(
421                "unknown field '{name}' or type mismatch for {value:?}"
422            )))),
423        }
424    }
425
426    /// Get the list of field names for this layer.
427    #[must_use]
428    pub fn field_names() -> &'static [&'static str] {
429        &["type", "code", "chksum", "id", "seq"]
430    }
431
432    // ========== Utility Methods ==========
433
434    /// Generate a human-readable summary string.
435    #[must_use]
436    pub fn summary(&self, buf: &[u8]) -> String {
437        if let (Ok(icmpv6_type), Ok(code)) = (self.icmpv6_type(buf), self.code(buf)) {
438            let type_name = types::name(icmpv6_type);
439
440            let details = match icmpv6_type {
441                types::ECHO_REQUEST | types::ECHO_REPLY => {
442                    let id_str = self
443                        .id(buf)
444                        .ok()
445                        .flatten()
446                        .map(|id| format!(" id={id:#06x}"))
447                        .unwrap_or_default();
448                    let seq_str = self
449                        .seq(buf)
450                        .ok()
451                        .flatten()
452                        .map(|seq| format!(" seq={seq}"))
453                        .unwrap_or_default();
454                    format!("{id_str}{seq_str}")
455                },
456                types::PKT_TOO_BIG => self
457                    .mtu(buf)
458                    .ok()
459                    .flatten()
460                    .map(|mtu| format!(" mtu={mtu}"))
461                    .unwrap_or_default(),
462                types::NEIGHBOR_SOLICIT | types::NEIGHBOR_ADVERT => self
463                    .target_addr(buf)
464                    .ok()
465                    .flatten()
466                    .map(|addr| format!(" target={addr}"))
467                    .unwrap_or_default(),
468                _ => String::new(),
469            };
470
471            if code == 0 {
472                format!("ICMPv6 {type_name}{details}")
473            } else {
474                format!("ICMPv6 {type_name} code={code}{details}")
475            }
476        } else {
477            "ICMPv6".to_string()
478        }
479    }
480
481    /// Get the `ICMPv6` header length.
482    ///
483    /// The base header is always 8 bytes. Type-specific fields (like target
484    /// addresses for NDP) are counted as part of the "body" beyond the 8-byte
485    /// header.
486    #[must_use]
487    pub fn header_len(&self, _buf: &[u8]) -> usize {
488        ICMPV6_MIN_HEADER_LEN
489    }
490
491    /// Compute hash for packet matching.
492    #[must_use]
493    pub fn hashret(&self, buf: &[u8]) -> Vec<u8> {
494        let icmpv6_type = self.icmpv6_type(buf).unwrap_or(0);
495        match icmpv6_type {
496            types::ECHO_REQUEST | types::ECHO_REPLY => {
497                // Hash on id + seq for echo matching
498                let id = self.id(buf).ok().flatten().unwrap_or(0);
499                let seq = self.seq(buf).ok().flatten().unwrap_or(0);
500                vec![
501                    (id >> 8) as u8,
502                    (id & 0xFF) as u8,
503                    (seq >> 8) as u8,
504                    (seq & 0xFF) as u8,
505                ]
506            },
507            _ => vec![],
508        }
509    }
510
511    /// Check if this packet answers another `ICMPv6` packet.
512    #[must_use]
513    pub fn answers(&self, buf: &[u8], other: &Icmpv6Layer, other_buf: &[u8]) -> bool {
514        let self_type = self.icmpv6_type(buf).unwrap_or(0);
515        let other_type = other.icmpv6_type(other_buf).unwrap_or(0);
516
517        match (self_type, other_type) {
518            (types::ECHO_REPLY, types::ECHO_REQUEST) => {
519                // ID and seq must match
520                let self_id = self.id(buf).ok().flatten();
521                let other_id = other.id(other_buf).ok().flatten();
522                let self_seq = self.seq(buf).ok().flatten();
523                let other_seq = other.seq(other_buf).ok().flatten();
524                self_id == other_id && self_seq == other_seq
525            },
526            _ => false,
527        }
528    }
529}
530
531impl Layer for Icmpv6Layer {
532    fn kind(&self) -> LayerKind {
533        LayerKind::Icmpv6
534    }
535
536    fn summary(&self, data: &[u8]) -> String {
537        self.summary(data)
538    }
539
540    fn header_len(&self, data: &[u8]) -> usize {
541        self.header_len(data)
542    }
543
544    fn hashret(&self, data: &[u8]) -> Vec<u8> {
545        self.hashret(data)
546    }
547
548    fn answers(&self, data: &[u8], other: &Self, other_data: &[u8]) -> bool {
549        self.answers(data, other, other_data)
550    }
551
552    fn field_names(&self) -> &'static [&'static str] {
553        Self::field_names()
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560
561    fn make_echo_request(id: u16, seq: u16) -> Vec<u8> {
562        let mut buf = vec![0u8; 8];
563        buf[0] = types::ECHO_REQUEST;
564        buf[1] = 0; // code
565        buf[2] = 0; // checksum (zero for now)
566        buf[3] = 0;
567        buf[4] = (id >> 8) as u8;
568        buf[5] = (id & 0xFF) as u8;
569        buf[6] = (seq >> 8) as u8;
570        buf[7] = (seq & 0xFF) as u8;
571        buf
572    }
573
574    fn make_echo_reply(id: u16, seq: u16) -> Vec<u8> {
575        let mut buf = make_echo_request(id, seq);
576        buf[0] = types::ECHO_REPLY;
577        buf
578    }
579
580    fn make_ns(target: Ipv6Addr) -> Vec<u8> {
581        let mut buf = vec![0u8; 24]; // 8 byte header + 16 byte target
582        buf[0] = types::NEIGHBOR_SOLICIT;
583        buf[1] = 0;
584        // bytes 4-7: reserved (zero)
585        buf[8..24].copy_from_slice(&target.octets());
586        buf
587    }
588
589    #[test]
590    fn test_icmpv6_type_echo_request() {
591        let buf = make_echo_request(0x1234, 5);
592        let layer = Icmpv6Layer::at_start();
593        assert_eq!(layer.icmpv6_type(&buf).unwrap(), types::ECHO_REQUEST);
594    }
595
596    #[test]
597    fn test_icmpv6_code() {
598        let buf = make_echo_request(1, 1);
599        let layer = Icmpv6Layer::at_start();
600        assert_eq!(layer.code(&buf).unwrap(), 0);
601    }
602
603    #[test]
604    fn test_icmpv6_id_seq() {
605        let buf = make_echo_request(0x5678, 42);
606        let layer = Icmpv6Layer::at_start();
607        assert_eq!(layer.id(&buf).unwrap(), Some(0x5678));
608        assert_eq!(layer.seq(&buf).unwrap(), Some(42));
609    }
610
611    #[test]
612    fn test_icmpv6_id_seq_not_available() {
613        let mut buf = vec![0u8; 8];
614        buf[0] = types::NEIGHBOR_SOLICIT;
615        let layer = Icmpv6Layer::at_start();
616        // NS doesn't have id/seq fields
617        assert_eq!(layer.id(&buf).unwrap(), None);
618        assert_eq!(layer.seq(&buf).unwrap(), None);
619    }
620
621    #[test]
622    fn test_icmpv6_target_addr() {
623        let target = Ipv6Addr::new(0xfe80, 0, 0, 0, 1, 0, 0, 1);
624        let mut buf = make_ns(target);
625        // Create a layer that spans the whole buffer including the target addr
626        let layer = Icmpv6Layer::new(LayerIndex::new(LayerKind::Icmpv6, 0, buf.len()));
627        let addr = layer.target_addr(&buf).unwrap();
628        assert_eq!(addr, Some(target));
629    }
630
631    #[test]
632    fn test_icmpv6_mtu() {
633        let mut buf = vec![0u8; 8];
634        buf[0] = types::PKT_TOO_BIG;
635        buf[4] = 0x00;
636        buf[5] = 0x00;
637        buf[6] = 0x05;
638        buf[7] = 0xDC; // 1500
639        let layer = Icmpv6Layer::at_start();
640        assert_eq!(layer.mtu(&buf).unwrap(), Some(1500));
641    }
642
643    #[test]
644    fn test_icmpv6_mtu_not_available() {
645        let buf = make_echo_request(1, 1);
646        let layer = Icmpv6Layer::at_start();
647        assert_eq!(layer.mtu(&buf).unwrap(), None);
648    }
649
650    #[test]
651    fn test_icmpv6_summary_echo_request() {
652        let buf = make_echo_request(0x1234, 5);
653        let layer = Icmpv6Layer::at_start();
654        let s = layer.summary(&buf);
655        assert!(s.contains("echo-request"));
656        assert!(s.contains("0x1234"));
657        assert!(s.contains("5"));
658    }
659
660    #[test]
661    fn test_icmpv6_summary_ns() {
662        let target = Ipv6Addr::new(0xfe80, 0, 0, 0, 1, 0, 0, 1);
663        let buf = make_ns(target);
664        let layer = Icmpv6Layer::new(LayerIndex::new(LayerKind::Icmpv6, 0, buf.len()));
665        let s = layer.summary(&buf);
666        assert!(s.contains("neighbor-solicit"));
667    }
668
669    #[test]
670    fn test_icmpv6_answers_echo() {
671        let req_buf = make_echo_request(0xABCD, 10);
672        let rep_buf = make_echo_reply(0xABCD, 10);
673
674        let req_layer = Icmpv6Layer::at_start();
675        let rep_layer = Icmpv6Layer::at_start();
676
677        assert!(rep_layer.answers(&rep_buf, &req_layer, &req_buf));
678    }
679
680    #[test]
681    fn test_icmpv6_answers_echo_wrong_id() {
682        let req_buf = make_echo_request(0xABCD, 10);
683        let rep_buf = make_echo_reply(0x0001, 10);
684
685        let req_layer = Icmpv6Layer::at_start();
686        let rep_layer = Icmpv6Layer::at_start();
687
688        assert!(!rep_layer.answers(&rep_buf, &req_layer, &req_buf));
689    }
690
691    #[test]
692    fn test_icmpv6_checksum() {
693        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
694        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
695
696        // Build an echo request with zeroed checksum
697        let mut buf = make_echo_request(0x1234, 1);
698        // Calculate and set checksum
699        let chksum = icmpv6_checksum(src, dst, &buf);
700        buf[2] = (chksum >> 8) as u8;
701        buf[3] = (chksum & 0xFF) as u8;
702
703        // Verify
704        assert!(verify_icmpv6_checksum(src, dst, &buf));
705    }
706
707    #[test]
708    fn test_icmpv6_checksum_wrong() {
709        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
710        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
711        let buf = make_echo_request(0x1234, 1);
712        // Checksum is zero, which should be wrong for this packet
713        // (unless by coincidence it's valid — we just test the function runs)
714        let _ = verify_icmpv6_checksum(src, dst, &buf);
715    }
716
717    #[test]
718    fn test_icmpv6_get_field() {
719        let buf = make_echo_request(0x5678, 3);
720        let layer = Icmpv6Layer::at_start();
721
722        if let Some(Ok(FieldValue::U8(t))) = layer.get_field(&buf, "type") {
723            assert_eq!(t, types::ECHO_REQUEST);
724        } else {
725            panic!("expected type field");
726        }
727
728        if let Some(Ok(FieldValue::U16(id))) = layer.get_field(&buf, "id") {
729            assert_eq!(id, 0x5678);
730        } else {
731            panic!("expected id field");
732        }
733    }
734
735    #[test]
736    fn test_icmpv6_set_field() {
737        let mut buf = make_echo_request(1, 1);
738        let layer = Icmpv6Layer::at_start();
739        layer
740            .set_field(&mut buf, "id", FieldValue::U16(0xDEAD))
741            .unwrap()
742            .unwrap();
743        assert_eq!(layer.id(&buf).unwrap(), Some(0xDEAD));
744    }
745
746    #[test]
747    fn test_icmpv6_header_len() {
748        let buf = make_echo_request(1, 1);
749        let layer = Icmpv6Layer::at_start();
750        assert_eq!(layer.header_len(&buf), ICMPV6_MIN_HEADER_LEN);
751        assert_eq!(ICMPV6_MIN_HEADER_LEN, 8);
752    }
753
754    #[test]
755    fn test_icmpv6_field_names() {
756        let names = Icmpv6Layer::field_names();
757        assert!(names.contains(&"type"));
758        assert!(names.contains(&"code"));
759        assert!(names.contains(&"chksum"));
760        assert!(names.contains(&"id"));
761        assert!(names.contains(&"seq"));
762    }
763}