Skip to main content

wireforge_core/
vlan.rs

1//! IEEE 802.1Q / 802.1ad VLAN tag parser.
2
3use alloc::vec::Vec;
4
5use crate::types::EtherType;
6use crate::util::read_u16be;
7
8/// Length of a single VLAN tag header (TPID + TCI).
9pub const VLAN_HEADER_LEN: usize = 4;
10
11/// Maximum number of VLAN tags to parse before stopping (prevents loops).
12pub const MAX_VLAN_DEPTH: usize = 2;
13
14/// Zero-copy 802.1Q VLAN tag parser.
15///
16/// Holds a reference to the underlying byte slice starting at the VLAN tag.
17#[derive(Debug, Clone)]
18pub struct VlanPacket<'a> {
19    buf: &'a [u8],
20}
21
22impl<'a> VlanPacket<'a> {
23    crate::parser_new!(VLAN_HEADER_LEN);
24
25    /// Raw bytes backing this tag.
26    #[inline]
27    pub fn as_bytes(&self) -> &'a [u8] { self.buf }
28
29    /// Priority Code Point (3 bits).
30    #[inline]
31    pub fn pcp(&self) -> u8 {
32        self.buf[0] >> 5
33    }
34
35    /// Drop Eligible Indicator (1 bit).
36    #[inline]
37    pub fn dei(&self) -> bool {
38        self.buf[0] & 0x10 != 0
39    }
40
41    /// VLAN Identifier (12 bits).
42    #[inline]
43    pub fn vid(&self) -> u16 {
44        read_u16be(&self.buf[..2]) & 0x0FFF
45    }
46
47    /// The TPID / encapsulating EtherType.
48    #[inline]
49    pub fn tpid(&self) -> EtherType {
50        EtherType::Unknown(read_u16be(&self.buf[..2]))
51    }
52
53    /// The inner EtherType (next protocol after the VLAN tag).
54    #[inline]
55    pub fn ethertype(&self) -> EtherType {
56        EtherType::from(read_u16be(&self.buf[2..4]))
57    }
58
59    /// Raw inner ethertype value.
60    #[inline]
61    pub fn raw_ethertype(&self) -> u16 {
62        read_u16be(&self.buf[2..4])
63    }
64
65    /// Data following this VLAN tag (may be another VLAN tag or actual payload).
66    #[inline]
67    pub fn inner(&self) -> &'a [u8] {
68        &self.buf[VLAN_HEADER_LEN..]
69    }
70}
71
72/// Parse a chain of 802.1Q / 802.1ad VLAN tags.
73///
74/// Returns the list of VLAN layers, the final EtherType, and the remaining
75/// payload after all VLAN tags. Stops after [`MAX_VLAN_DEPTH`] tags.
76pub fn parse_vlan_chain(buf: &[u8]) -> Option<(Vec<VlanPacket<'_>>, EtherType, &[u8])> {
77    let mut tags = Vec::new();
78    let mut remaining = buf;
79
80    for _ in 0..MAX_VLAN_DEPTH {
81        let vlan = VlanPacket::new(remaining)?;
82        let inner_et = vlan.raw_ethertype();
83        tags.push(vlan);
84        remaining = &remaining[VLAN_HEADER_LEN..];
85
86        // Stop if the next protocol is not another VLAN tag
87        if inner_et != 0x8100 && inner_et != 0x88A8 {
88            return Some((tags, EtherType::from(inner_et), remaining));
89        }
90    }
91    // Hit max depth — return whatever we have
92    if tags.is_empty() {
93        None
94    } else {
95        let last_et = tags.last().unwrap().raw_ethertype();
96        Some((tags, EtherType::from(last_et), remaining))
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn parse_single_vlan_tag() {
106        // TPID=0x8100, PCP=3, DEI=1, VID=100, inner EtherType=0x0800
107        let data = [
108            0x70, 0x64, // PCP=3, DEI=1, VID=100
109            0x08, 0x00, // IPv4
110        ];
111        let vlan = VlanPacket::new(&data).unwrap();
112        assert_eq!(vlan.pcp(), 3);
113        assert!(vlan.dei());
114        assert_eq!(vlan.vid(), 100);
115        assert_eq!(vlan.ethertype(), EtherType::Ipv4);
116    }
117
118    #[test]
119    fn parse_vlan_tag_no_dei() {
120        let data = [
121            0x20, 0x0A, // PCP=1, DEI=0, VID=10
122            0x08, 0x06, // ARP
123        ];
124        let vlan = VlanPacket::new(&data).unwrap();
125        assert_eq!(vlan.pcp(), 1);
126        assert!(!vlan.dei());
127        assert_eq!(vlan.vid(), 10);
128        assert_eq!(vlan.ethertype(), EtherType::Arp);
129    }
130
131    #[test]
132    fn vlan_too_short() {
133        assert!(VlanPacket::new(&[]).is_none());
134        assert!(VlanPacket::new(&[0u8; 3]).is_none());
135    }
136
137    #[test]
138    fn vlan_chain_single() {
139        // 802.1Q tag → IPv4
140        let data = [
141            0x00, 0x64, // VID=100
142            0x08, 0x00, // EtherType = IPv4
143            0x45, // payload
144        ];
145        let (tags, final_et, payload) = parse_vlan_chain(&data).unwrap();
146        assert_eq!(tags.len(), 1);
147        assert_eq!(tags[0].vid(), 100);
148        assert_eq!(final_et, EtherType::Ipv4);
149        assert_eq!(payload, &[0x45]);
150    }
151}