pcapsql_core/protocol/
vlan.rs

1//! IEEE 802.1Q VLAN tag parser.
2
3use smallvec::SmallVec;
4
5use super::ethernet::ethertype;
6use super::{FieldValue, ParseContext, ParseResult, Protocol};
7use crate::schema::{DataKind, FieldDescriptor};
8
9/// 802.1Q VLAN tag parser.
10#[derive(Debug, Clone, Copy)]
11pub struct VlanProtocol;
12
13impl Protocol for VlanProtocol {
14    fn name(&self) -> &'static str {
15        "vlan"
16    }
17
18    fn display_name(&self) -> &'static str {
19        "802.1Q VLAN"
20    }
21
22    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
23        // Check for VLAN ethertype (0x8100) or QinQ (0x88A8)
24        match context.hint("ethertype") {
25            Some(etype) if etype == ethertype::VLAN as u64 => Some(100),
26            Some(etype) if etype == ethertype::QINQ as u64 => Some(100),
27            _ => None,
28        }
29    }
30
31    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
32        // VLAN tag is 4 bytes total, but the TPID (2 bytes) was already
33        // consumed by the Ethernet parser, so we just have TCI (2 bytes)
34        // and the inner EtherType (2 bytes).
35        if data.len() < 4 {
36            return ParseResult::error("VLAN tag too short".to_string(), data);
37        }
38
39        let mut fields = SmallVec::new();
40
41        // TCI (Tag Control Information) - 2 bytes
42        let tci = u16::from_be_bytes([data[0], data[1]]);
43
44        // PCP (Priority Code Point) - bits 13-15 (3 bits)
45        let priority = (tci >> 13) & 0x07;
46        fields.push(("priority", FieldValue::UInt8(priority as u8)));
47
48        // DEI (Drop Eligible Indicator) - bit 12 (1 bit)
49        let dei = (tci >> 12) & 0x01;
50        fields.push(("dei", FieldValue::Bool(dei != 0)));
51
52        // VID (VLAN Identifier) - bits 0-11 (12 bits)
53        let vlan_id = tci & 0x0FFF;
54        fields.push(("vlan_id", FieldValue::UInt16(vlan_id)));
55
56        // Inner EtherType - 2 bytes
57        let inner_ethertype = u16::from_be_bytes([data[2], data[3]]);
58        fields.push(("inner_ethertype", FieldValue::UInt16(inner_ethertype)));
59
60        // Set up child hints for the next layer
61        let mut child_hints = SmallVec::new();
62        child_hints.push(("ethertype", inner_ethertype as u64));
63        child_hints.push(("vlan_id", vlan_id as u64));
64
65        // VLAN tag is 4 bytes
66        ParseResult::success(fields, &data[4..], child_hints)
67    }
68
69    fn schema_fields(&self) -> Vec<FieldDescriptor> {
70        vec![
71            FieldDescriptor::new("vlan.vlan_id", DataKind::UInt16).set_nullable(true),
72            FieldDescriptor::new("vlan.priority", DataKind::UInt8).set_nullable(true),
73            FieldDescriptor::new("vlan.dei", DataKind::Bool).set_nullable(true),
74            FieldDescriptor::new("vlan.inner_ethertype", DataKind::UInt16).set_nullable(true),
75        ]
76    }
77
78    fn child_protocols(&self) -> &[&'static str] {
79        &["ipv4", "ipv6", "arp", "vlan"]
80    }
81
82    fn dependencies(&self) -> &'static [&'static str] {
83        &["ethernet", "vlan"] // Can follow Ethernet or another VLAN (QinQ)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    /// Create a VLAN tag with the given parameters.
92    fn create_vlan_tag(vlan_id: u16, priority: u8, dei: bool, inner_ethertype: u16) -> Vec<u8> {
93        let mut tag = Vec::with_capacity(4);
94
95        // Build TCI
96        let tci = ((priority as u16 & 0x07) << 13) | ((dei as u16) << 12) | (vlan_id & 0x0FFF);
97
98        tag.extend_from_slice(&tci.to_be_bytes());
99        tag.extend_from_slice(&inner_ethertype.to_be_bytes());
100
101        tag
102    }
103
104    #[test]
105    fn test_parse_vlan_basic() {
106        // VLAN ID 100, Priority 0, DEI false, inner ethertype IPv4
107        let tag = create_vlan_tag(100, 0, false, ethertype::IPV4);
108
109        let parser = VlanProtocol;
110        let mut context = ParseContext::new(1);
111        context.insert_hint("ethertype", ethertype::VLAN as u64);
112        context.parent_protocol = Some("ethernet");
113
114        let result = parser.parse(&tag, &context);
115
116        assert!(result.is_ok());
117        assert_eq!(result.get("vlan_id"), Some(&FieldValue::UInt16(100)));
118        assert_eq!(result.get("priority"), Some(&FieldValue::UInt8(0)));
119        assert_eq!(result.get("dei"), Some(&FieldValue::Bool(false)));
120        assert_eq!(
121            result.get("inner_ethertype"),
122            Some(&FieldValue::UInt16(ethertype::IPV4))
123        );
124    }
125
126    #[test]
127    fn test_parse_vlan_with_priority() {
128        // VLAN ID 200, Priority 5, DEI true, inner ethertype IPv6
129        let tag = create_vlan_tag(200, 5, true, ethertype::IPV6);
130
131        let parser = VlanProtocol;
132        let mut context = ParseContext::new(1);
133        context.insert_hint("ethertype", ethertype::VLAN as u64);
134        context.parent_protocol = Some("ethernet");
135
136        let result = parser.parse(&tag, &context);
137
138        assert!(result.is_ok());
139        assert_eq!(result.get("vlan_id"), Some(&FieldValue::UInt16(200)));
140        assert_eq!(result.get("priority"), Some(&FieldValue::UInt8(5)));
141        assert_eq!(result.get("dei"), Some(&FieldValue::Bool(true)));
142        assert_eq!(
143            result.get("inner_ethertype"),
144            Some(&FieldValue::UInt16(ethertype::IPV6))
145        );
146    }
147
148    #[test]
149    fn test_parse_vlan_max_id() {
150        // Max VLAN ID (4095), max priority (7)
151        let tag = create_vlan_tag(4095, 7, true, ethertype::IPV4);
152
153        let parser = VlanProtocol;
154        let mut context = ParseContext::new(1);
155        context.insert_hint("ethertype", ethertype::VLAN as u64);
156
157        let result = parser.parse(&tag, &context);
158
159        assert!(result.is_ok());
160        assert_eq!(result.get("vlan_id"), Some(&FieldValue::UInt16(4095)));
161        assert_eq!(result.get("priority"), Some(&FieldValue::UInt8(7)));
162    }
163
164    #[test]
165    fn test_can_parse_vlan() {
166        let parser = VlanProtocol;
167
168        // Without hint
169        let ctx1 = ParseContext::new(1);
170        assert!(parser.can_parse(&ctx1).is_none());
171
172        // With VLAN ethertype
173        let mut ctx2 = ParseContext::new(1);
174        ctx2.insert_hint("ethertype", ethertype::VLAN as u64);
175        assert!(parser.can_parse(&ctx2).is_some());
176
177        // With QinQ ethertype
178        let mut ctx3 = ParseContext::new(1);
179        ctx3.insert_hint("ethertype", ethertype::QINQ as u64);
180        assert!(parser.can_parse(&ctx3).is_some());
181
182        // With different ethertype
183        let mut ctx4 = ParseContext::new(1);
184        ctx4.insert_hint("ethertype", ethertype::IPV4 as u64);
185        assert!(parser.can_parse(&ctx4).is_none());
186    }
187
188    #[test]
189    fn test_parse_vlan_too_short() {
190        let short_tag = [0x00, 0x64]; // Only 2 bytes
191
192        let parser = VlanProtocol;
193        let mut context = ParseContext::new(1);
194        context.insert_hint("ethertype", ethertype::VLAN as u64);
195
196        let result = parser.parse(&short_tag, &context);
197
198        assert!(!result.is_ok());
199        assert!(result.error.is_some());
200    }
201
202    #[test]
203    fn test_vlan_child_hints() {
204        // VLAN ID 42, inner ethertype IPv4
205        let tag = create_vlan_tag(42, 3, false, ethertype::IPV4);
206
207        let parser = VlanProtocol;
208        let mut context = ParseContext::new(1);
209        context.insert_hint("ethertype", ethertype::VLAN as u64);
210
211        let result = parser.parse(&tag, &context);
212
213        assert!(result.is_ok());
214        assert_eq!(result.hint("ethertype"), Some(ethertype::IPV4 as u64));
215        assert_eq!(result.hint("vlan_id"), Some(42u64));
216    }
217
218    #[test]
219    fn test_vlan_with_payload() {
220        let mut data = create_vlan_tag(100, 0, false, ethertype::IPV4);
221        // Add some payload (IPv4 header start)
222        data.extend_from_slice(&[0x45, 0x00, 0x00, 0x28]);
223
224        let parser = VlanProtocol;
225        let mut context = ParseContext::new(1);
226        context.insert_hint("ethertype", ethertype::VLAN as u64);
227
228        let result = parser.parse(&data, &context);
229
230        assert!(result.is_ok());
231        assert_eq!(result.remaining.len(), 4); // IPv4 header bytes
232        assert_eq!(result.remaining[0], 0x45); // IPv4 version/IHL
233    }
234
235    #[test]
236    fn test_vlan_schema_fields() {
237        let parser = VlanProtocol;
238        let fields = parser.schema_fields();
239
240        assert!(!fields.is_empty());
241
242        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
243        assert!(field_names.contains(&"vlan.vlan_id"));
244        assert!(field_names.contains(&"vlan.priority"));
245        assert!(field_names.contains(&"vlan.dei"));
246        assert!(field_names.contains(&"vlan.inner_ethertype"));
247    }
248}