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