Skip to main content

packet_strata/packet/
sll.rs

1//! Linux Cooked Capture (SLL and SLLv2) header implementations
2//!
3//! SLL (Linux Cooked Capture) is a pseudo-header used by libpcap when capturing
4//! on the "any" device or on devices that don't have a link-layer header.
5//!
6//! # SLL Header Format (16 bytes)
7//!
8//! ```text
9//!  0                   1                   2                   3
10//!  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
11//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12//! |         Packet Type           |          ARPHRD Type          |
13//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14//! |    Link-layer address length  |                               |
15//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
16//! |                    Link-layer address (8 bytes)               |
17//! +                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18//! |                               |          Protocol Type        |
19//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20//! ```
21//!
22//! # SLLv2 Header Format (20 bytes)
23//!
24//! ```text
25//!  0                   1                   2                   3
26//!  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
27//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28//! |         Protocol Type         |         Reserved (MBZ)        |
29//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30//! |                        Interface Index                        |
31//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32//! |          ARPHRD Type          |         Packet Type           |
33//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34//! |    Link-layer address length  |                               |
35//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
36//! |                    Link-layer address (8 bytes)               |
37//! +                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38//! |                               |
39//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40//! ```
41//!
42//! # Examples
43//!
44//! ## Basic SLL parsing
45//!
46//! ```
47//! use packet_strata::packet::sll::SllHeader;
48//! use packet_strata::packet::protocol::EtherProto;
49//! use packet_strata::packet::HeaderParser;
50//!
51//! // SLL header with IPv4 payload
52//! let packet = vec![
53//!     0x00, 0x00,                          // Packet type: Host (0)
54//!     0x00, 0x01,                          // ARPHRD type: Ethernet (1)
55//!     0x00, 0x06,                          // Address length: 6
56//!     0x00, 0x11, 0x22, 0x33, 0x44, 0x55,  // Link-layer address
57//!     0x00, 0x00,                          // Padding
58//!     0x08, 0x00,                          // Protocol: IPv4
59//!     // IPv4 payload follows...
60//! ];
61//!
62//! let (header, payload) = SllHeader::from_bytes(&packet).unwrap();
63//! assert_eq!(header.protocol(), EtherProto::IPV4);
64//! assert_eq!(header.ll_addr_len(), 6);
65//! ```
66//!
67//! ## SLLv2 parsing
68//!
69//! ```
70//! use packet_strata::packet::sll::Sllv2Header;
71//! use packet_strata::packet::protocol::EtherProto;
72//! use packet_strata::packet::HeaderParser;
73//!
74//! // SLLv2 header with IPv6 payload
75//! let packet = vec![
76//!     0x86, 0xDD,                          // Protocol: IPv6
77//!     0x00, 0x00,                          // Reserved
78//!     0x00, 0x00, 0x00, 0x02,              // Interface index: 2
79//!     0x00, 0x01,                          // ARPHRD type: Ethernet
80//!     0x04,                                // Packet type: Outgoing (4)
81//!     0x06,                                // Address length: 6
82//!     0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,  // Link-layer address
83//!     0x00, 0x00,                          // Padding
84//!     // IPv6 payload follows...
85//! ];
86//!
87//! let (header, payload) = Sllv2Header::from_bytes(&packet).unwrap();
88//! assert_eq!(header.protocol(), EtherProto::IPV6);
89//! assert_eq!(header.interface_index(), 2);
90//! ```
91//!
92//! # References
93//!
94//! - https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html
95//! - https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL2.html
96
97use core::fmt;
98use std::fmt::{Display, Formatter};
99
100use zerocopy::byteorder::{BigEndian, U16, U32};
101use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
102
103use crate::packet::protocol::EtherProto;
104use crate::packet::{HeaderParser, PacketHeader};
105
106/// SLL packet type indicating the direction/type of the packet
107#[repr(u16)]
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum SllPacketType {
110    /// Packet was sent to us by somebody else
111    Host = 0,
112    /// Packet was broadcast by somebody else
113    Broadcast = 1,
114    /// Packet was multicast, but not broadcast, by somebody else
115    Multicast = 2,
116    /// Packet was sent by somebody else to somebody else
117    OtherHost = 3,
118    /// Packet was sent by us
119    Outgoing = 4,
120    /// Packet was sent by us (kernel loopback)
121    LoopbackOutgoing = 5,
122    /// Packet was fastrouted (internal use)
123    FastRoute = 6,
124    /// Unknown packet type
125    Unknown(u16),
126}
127
128impl From<u16> for SllPacketType {
129    fn from(value: u16) -> Self {
130        match value {
131            0 => SllPacketType::Host,
132            1 => SllPacketType::Broadcast,
133            2 => SllPacketType::Multicast,
134            3 => SllPacketType::OtherHost,
135            4 => SllPacketType::Outgoing,
136            5 => SllPacketType::LoopbackOutgoing,
137            6 => SllPacketType::FastRoute,
138            v => SllPacketType::Unknown(v),
139        }
140    }
141}
142
143impl Display for SllPacketType {
144    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
145        match self {
146            SllPacketType::Host => write!(f, "Host"),
147            SllPacketType::Broadcast => write!(f, "Broadcast"),
148            SllPacketType::Multicast => write!(f, "Multicast"),
149            SllPacketType::OtherHost => write!(f, "OtherHost"),
150            SllPacketType::Outgoing => write!(f, "Outgoing"),
151            SllPacketType::LoopbackOutgoing => write!(f, "LoopbackOutgoing"),
152            SllPacketType::FastRoute => write!(f, "FastRoute"),
153            SllPacketType::Unknown(v) => write!(f, "Unknown({})", v),
154        }
155    }
156}
157
158/// ARPHRD types (subset of common ones)
159#[repr(u16)]
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum ArphrdType {
162    /// Ethernet 10/100Mbps
163    Ether = 1,
164    /// IEEE 802.11
165    Ieee80211 = 801,
166    /// IEEE 802.11 + Radiotap header
167    Ieee80211Radiotap = 803,
168    /// Loopback device
169    Loopback = 772,
170    /// PPP
171    Ppp = 512,
172    /// Unknown type
173    Unknown(u16),
174}
175
176impl From<u16> for ArphrdType {
177    fn from(value: u16) -> Self {
178        match value {
179            1 => ArphrdType::Ether,
180            801 => ArphrdType::Ieee80211,
181            803 => ArphrdType::Ieee80211Radiotap,
182            772 => ArphrdType::Loopback,
183            512 => ArphrdType::Ppp,
184            v => ArphrdType::Unknown(v),
185        }
186    }
187}
188
189impl Display for ArphrdType {
190    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
191        match self {
192            ArphrdType::Ether => write!(f, "Ethernet"),
193            ArphrdType::Ieee80211 => write!(f, "IEEE802.11"),
194            ArphrdType::Ieee80211Radiotap => write!(f, "IEEE802.11+Radiotap"),
195            ArphrdType::Loopback => write!(f, "Loopback"),
196            ArphrdType::Ppp => write!(f, "PPP"),
197            ArphrdType::Unknown(v) => write!(f, "Unknown({})", v),
198        }
199    }
200}
201
202// ============================================================================
203// SLL (Linux Cooked Capture v1) - 16 bytes
204// ============================================================================
205
206/// SLL Header (Linux Cooked Capture v1)
207///
208/// Fixed 16-byte header format:
209/// ```text
210/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
211/// |         Packet Type           |        ARPHRD Type            |
212/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
213/// |     Link-layer addr length    |                               |
214/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
215/// |                    Link-layer address (8 bytes)               |
216/// +                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
217/// |                               |         Protocol Type         |
218/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
219/// ```
220#[repr(C, packed)]
221#[derive(FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout, Debug, Clone, Copy)]
222pub struct SllHeader {
223    /// Packet type (incoming, outgoing, etc.)
224    packet_type: U16<BigEndian>,
225    /// ARPHRD type (hardware type)
226    arphrd_type: U16<BigEndian>,
227    /// Link-layer address length
228    ll_addr_len: U16<BigEndian>,
229    /// Link-layer address (8 bytes, zero-padded if shorter)
230    ll_addr: [u8; 8],
231    /// Protocol type (EtherType)
232    protocol: EtherProto,
233}
234
235impl SllHeader {
236    /// Returns the packet type
237    #[inline]
238    pub fn packet_type(&self) -> SllPacketType {
239        SllPacketType::from(self.packet_type.get())
240    }
241
242    /// Returns the raw packet type value
243    #[inline]
244    pub fn packet_type_raw(&self) -> u16 {
245        self.packet_type.get()
246    }
247
248    /// Returns the ARPHRD type
249    #[inline]
250    pub fn arphrd_type(&self) -> ArphrdType {
251        ArphrdType::from(self.arphrd_type.get())
252    }
253
254    /// Returns the raw ARPHRD type value
255    #[inline]
256    pub fn arphrd_type_raw(&self) -> u16 {
257        self.arphrd_type.get()
258    }
259
260    /// Returns the link-layer address length
261    #[inline]
262    pub fn ll_addr_len(&self) -> u16 {
263        self.ll_addr_len.get()
264    }
265
266    /// Returns the link-layer address (up to 8 bytes, use ll_addr_len for actual length)
267    #[inline]
268    pub fn ll_addr(&self) -> &[u8] {
269        let len = std::cmp::min(self.ll_addr_len.get() as usize, 8);
270        &self.ll_addr[..len]
271    }
272
273    /// Returns the full 8-byte link-layer address field
274    #[inline]
275    pub fn ll_addr_raw(&self) -> &[u8; 8] {
276        &self.ll_addr
277    }
278
279    /// Returns the protocol type (EtherType)
280    #[inline]
281    pub fn protocol(&self) -> EtherProto {
282        self.protocol
283    }
284}
285
286impl PacketHeader for SllHeader {
287    const NAME: &'static str = "SllHeader";
288    type InnerType = EtherProto;
289
290    #[inline]
291    fn inner_type(&self) -> Self::InnerType {
292        self.protocol
293    }
294}
295
296impl HeaderParser for SllHeader {
297    type Output<'a> = &'a SllHeader;
298
299    #[inline]
300    fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
301        header
302    }
303}
304
305impl Display for SllHeader {
306    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
307        write!(
308            f,
309            "SLL type={} hw={} proto={}",
310            self.packet_type(),
311            self.arphrd_type(),
312            self.protocol()
313        )?;
314
315        // Format link-layer address if present
316        let addr_len = self.ll_addr_len() as usize;
317        if addr_len > 0 && addr_len <= 8 {
318            write!(f, " addr=")?;
319            for (i, byte) in self.ll_addr[..addr_len].iter().enumerate() {
320                if i > 0 {
321                    write!(f, ":")?;
322                }
323                write!(f, "{:02x}", byte)?;
324            }
325        }
326
327        Ok(())
328    }
329}
330
331// ============================================================================
332// SLLv2 (Linux Cooked Capture v2) - 20 bytes
333// ============================================================================
334
335/// SLLv2 Header (Linux Cooked Capture v2)
336///
337/// Fixed 20-byte header format:
338/// ```text
339/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
340/// |         Protocol Type         |           Reserved            |
341/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
342/// |                        Interface Index                        |
343/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
344/// |         ARPHRD Type           |  Packet Type  | LL Addr Len   |
345/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
346/// |                                                               |
347/// +                    Link-layer address (8 bytes)               +
348/// |                                                               |
349/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
350/// ```
351#[repr(C, packed)]
352#[derive(FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout, Debug, Clone, Copy)]
353pub struct Sllv2Header {
354    /// Protocol type (EtherType)
355    protocol: EtherProto,
356    /// Reserved (must be zero)
357    reserved: U16<BigEndian>,
358    /// Interface index
359    interface_index: U32<BigEndian>,
360    /// ARPHRD type (hardware type)
361    arphrd_type: U16<BigEndian>,
362    /// Packet type (incoming, outgoing, etc.) - 1 byte in v2
363    packet_type: u8,
364    /// Link-layer address length - 1 byte in v2
365    ll_addr_len: u8,
366    /// Link-layer address (8 bytes, zero-padded if shorter)
367    ll_addr: [u8; 8],
368}
369
370impl Sllv2Header {
371    /// Returns the protocol type (EtherType)
372    #[inline]
373    pub fn protocol(&self) -> EtherProto {
374        self.protocol
375    }
376
377    /// Returns the interface index
378    #[inline]
379    pub fn interface_index(&self) -> u32 {
380        self.interface_index.get()
381    }
382
383    /// Returns the ARPHRD type
384    #[inline]
385    pub fn arphrd_type(&self) -> ArphrdType {
386        ArphrdType::from(self.arphrd_type.get())
387    }
388
389    /// Returns the raw ARPHRD type value
390    #[inline]
391    pub fn arphrd_type_raw(&self) -> u16 {
392        self.arphrd_type.get()
393    }
394
395    /// Returns the packet type
396    #[inline]
397    pub fn packet_type(&self) -> SllPacketType {
398        SllPacketType::from(self.packet_type as u16)
399    }
400
401    /// Returns the raw packet type value
402    #[inline]
403    pub fn packet_type_raw(&self) -> u8 {
404        self.packet_type
405    }
406
407    /// Returns the link-layer address length
408    #[inline]
409    pub fn ll_addr_len(&self) -> u8 {
410        self.ll_addr_len
411    }
412
413    /// Returns the link-layer address (up to 8 bytes, use ll_addr_len for actual length)
414    #[inline]
415    pub fn ll_addr(&self) -> &[u8] {
416        let len = std::cmp::min(self.ll_addr_len as usize, 8);
417        &self.ll_addr[..len]
418    }
419
420    /// Returns the full 8-byte link-layer address field
421    #[inline]
422    pub fn ll_addr_raw(&self) -> &[u8; 8] {
423        &self.ll_addr
424    }
425}
426
427impl PacketHeader for Sllv2Header {
428    const NAME: &'static str = "Sllv2Header";
429    type InnerType = EtherProto;
430
431    #[inline]
432    fn inner_type(&self) -> Self::InnerType {
433        self.protocol
434    }
435}
436
437impl HeaderParser for Sllv2Header {
438    type Output<'a> = &'a Sllv2Header;
439
440    #[inline]
441    fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
442        header
443    }
444}
445
446impl Display for Sllv2Header {
447    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
448        write!(
449            f,
450            "SLLv2 if={} type={} hw={} proto={}",
451            self.interface_index(),
452            self.packet_type(),
453            self.arphrd_type(),
454            self.protocol()
455        )?;
456
457        // Format link-layer address if present
458        let addr_len = self.ll_addr_len as usize;
459        if addr_len > 0 && addr_len <= 8 {
460            write!(f, " addr=")?;
461            for (i, byte) in self.ll_addr[..addr_len].iter().enumerate() {
462                if i > 0 {
463                    write!(f, ":")?;
464                }
465                write!(f, "{:02x}", byte)?;
466            }
467        }
468
469        Ok(())
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476    use std::mem;
477
478    #[test]
479    fn test_sll_header_size() {
480        assert_eq!(mem::size_of::<SllHeader>(), 16);
481        assert_eq!(SllHeader::FIXED_LEN, 16);
482    }
483
484    #[test]
485    fn test_sllv2_header_size() {
486        assert_eq!(mem::size_of::<Sllv2Header>(), 20);
487        assert_eq!(Sllv2Header::FIXED_LEN, 20);
488    }
489
490    #[test]
491    fn test_sll_header_parse() {
492        // Example SLL header: Host, Ethernet, 6-byte MAC, IPv4
493        let packet: [u8; 16] = [
494            0x00, 0x00, // packet_type: Host (0)
495            0x00, 0x01, // arphrd_type: Ethernet (1)
496            0x00, 0x06, // ll_addr_len: 6
497            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x00, // ll_addr (6 bytes + 2 padding)
498            0x08, 0x00, // protocol: IPv4
499        ];
500
501        let (header, remaining) =
502            SllHeader::from_bytes(&packet).expect("Failed to parse SllHeader");
503
504        assert_eq!(header.packet_type(), SllPacketType::Host);
505        assert_eq!(header.arphrd_type(), ArphrdType::Ether);
506        assert_eq!(header.ll_addr_len(), 6);
507        assert_eq!(header.ll_addr(), &[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
508        assert_eq!(header.protocol(), EtherProto::IPV4);
509        assert_eq!(remaining.len(), 0);
510    }
511
512    #[test]
513    fn test_sll_header_outgoing() {
514        let packet: [u8; 16] = [
515            0x00, 0x04, // packet_type: Outgoing (4)
516            0x00, 0x01, // arphrd_type: Ethernet (1)
517            0x00, 0x06, // ll_addr_len: 6
518            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x00, 0x00, // ll_addr
519            0x86, 0xdd, // protocol: IPv6
520        ];
521
522        let (header, _) = SllHeader::from_bytes(&packet).expect("Failed to parse SllHeader");
523
524        assert_eq!(header.packet_type(), SllPacketType::Outgoing);
525        assert_eq!(header.protocol(), EtherProto::IPV6);
526    }
527
528    #[test]
529    fn test_sllv2_header_parse() {
530        // Example SLLv2 header
531        let packet: [u8; 20] = [
532            0x08, 0x00, // protocol: IPv4
533            0x00, 0x00, // reserved
534            0x00, 0x00, 0x00, 0x02, // interface_index: 2
535            0x00, 0x01, // arphrd_type: Ethernet (1)
536            0x00, // packet_type: Host (0)
537            0x06, // ll_addr_len: 6
538            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x00, // ll_addr
539        ];
540
541        let (header, remaining) =
542            Sllv2Header::from_bytes(&packet).expect("Failed to parse Sllv2Header");
543
544        assert_eq!(header.protocol(), EtherProto::IPV4);
545        assert_eq!(header.interface_index(), 2);
546        assert_eq!(header.arphrd_type(), ArphrdType::Ether);
547        assert_eq!(header.packet_type(), SllPacketType::Host);
548        assert_eq!(header.ll_addr_len(), 6);
549        assert_eq!(header.ll_addr(), &[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
550        assert_eq!(remaining.len(), 0);
551    }
552
553    #[test]
554    fn test_sllv2_header_with_payload() {
555        let mut packet = Vec::new();
556
557        // SLLv2 header
558        packet.extend_from_slice(&[
559            0x08, 0x00, // protocol: IPv4
560            0x00, 0x00, // reserved
561            0x00, 0x00, 0x00, 0x05, // interface_index: 5
562            0x00, 0x01, // arphrd_type: Ethernet (1)
563            0x04, // packet_type: Outgoing (4)
564            0x06, // ll_addr_len: 6
565            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x00, 0x00, // ll_addr
566        ]);
567
568        // Add some payload
569        packet.extend_from_slice(b"test payload");
570
571        let (header, remaining) =
572            Sllv2Header::from_bytes(&packet).expect("Failed to parse Sllv2Header");
573
574        assert_eq!(header.protocol(), EtherProto::IPV4);
575        assert_eq!(header.interface_index(), 5);
576        assert_eq!(header.packet_type(), SllPacketType::Outgoing);
577        assert_eq!(remaining, b"test payload");
578    }
579
580    #[test]
581    fn test_sll_display() {
582        let packet: [u8; 16] = [
583            0x00, 0x00, // packet_type: Host
584            0x00, 0x01, // arphrd_type: Ethernet
585            0x00, 0x06, // ll_addr_len: 6
586            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x00, 0x08, 0x00, // protocol: IPv4
587        ];
588
589        let (header, _) = SllHeader::from_bytes(&packet).unwrap();
590        let display = format!("{}", header);
591
592        assert!(display.contains("SLL"));
593        assert!(display.contains("Host"));
594        assert!(display.contains("Ethernet"));
595        assert!(display.contains("aa:bb:cc:dd:ee:ff"));
596    }
597
598    #[test]
599    fn test_sllv2_display() {
600        let packet: [u8; 20] = [
601            0x08, 0x00, // protocol: IPv4
602            0x00, 0x00, // reserved
603            0x00, 0x00, 0x00, 0x03, // interface_index: 3
604            0x00, 0x01, // arphrd_type: Ethernet
605            0x00, // packet_type: Host
606            0x06, // ll_addr_len: 6
607            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x00, 0x00,
608        ];
609
610        let (header, _) = Sllv2Header::from_bytes(&packet).unwrap();
611        let display = format!("{}", header);
612
613        assert!(display.contains("SLLv2"));
614        assert!(display.contains("if=3"));
615        assert!(display.contains("Host"));
616        assert!(display.contains("11:22:33:44:55:66"));
617    }
618
619    #[test]
620    fn test_packet_type_display() {
621        assert_eq!(format!("{}", SllPacketType::Host), "Host");
622        assert_eq!(format!("{}", SllPacketType::Broadcast), "Broadcast");
623        assert_eq!(format!("{}", SllPacketType::Multicast), "Multicast");
624        assert_eq!(format!("{}", SllPacketType::OtherHost), "OtherHost");
625        assert_eq!(format!("{}", SllPacketType::Outgoing), "Outgoing");
626        assert_eq!(format!("{}", SllPacketType::Unknown(99)), "Unknown(99)");
627    }
628
629    #[test]
630    fn test_arphrd_type_display() {
631        assert_eq!(format!("{}", ArphrdType::Ether), "Ethernet");
632        assert_eq!(format!("{}", ArphrdType::Loopback), "Loopback");
633        assert_eq!(format!("{}", ArphrdType::Ieee80211), "IEEE802.11");
634        assert_eq!(format!("{}", ArphrdType::Unknown(999)), "Unknown(999)");
635    }
636
637    #[test]
638    fn test_sll_too_short() {
639        let packet: [u8; 10] = [0; 10]; // Too short for SLL header
640
641        let result = SllHeader::from_bytes(&packet);
642        assert!(result.is_err());
643    }
644
645    #[test]
646    fn test_sllv2_too_short() {
647        let packet: [u8; 15] = [0; 15]; // Too short for SLLv2 header
648
649        let result = Sllv2Header::from_bytes(&packet);
650        assert!(result.is_err());
651    }
652
653    #[test]
654    fn test_sll_loopback() {
655        let packet: [u8; 16] = [
656            0x00, 0x04, // packet_type: Outgoing
657            0x03, 0x04, // arphrd_type: Loopback (772 = 0x0304)
658            0x00, 0x00, // ll_addr_len: 0 (loopback has no link-layer address)
659            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, // protocol: IPv4
660        ];
661
662        let (header, _) = SllHeader::from_bytes(&packet).expect("Failed to parse");
663
664        assert_eq!(header.arphrd_type(), ArphrdType::Loopback);
665        assert_eq!(header.ll_addr_len(), 0);
666        assert_eq!(header.ll_addr(), &[]);
667    }
668}