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}