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