pcapsql_core/protocol/
context.rs

1//! Parse context and result types.
2
3use smallvec::SmallVec;
4
5use super::FieldValue;
6
7/// Field entry for parse results: (field_name, value).
8/// Field names are always static strings (protocol-defined).
9/// The lifetime parameter ties the value to the packet/buffer data.
10pub type FieldEntry<'data> = (&'static str, FieldValue<'data>);
11
12/// Hint entry for child protocol detection: (hint_name, value).
13pub type HintEntry = (&'static str, u64);
14
15/// Type of encapsulating tunnel protocol.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17#[repr(u8)]
18pub enum TunnelType {
19    /// No tunnel encapsulation (outer layer).
20    #[default]
21    None = 0,
22    /// VXLAN encapsulation.
23    Vxlan = 1,
24    /// GRE encapsulation.
25    Gre = 2,
26    /// GTP (GPRS Tunneling Protocol) encapsulation.
27    Gtp = 3,
28    /// MPLS encapsulation.
29    Mpls = 4,
30    /// IPv4-in-IPv4 encapsulation (IP protocol 4 in IPv4).
31    IpInIp = 5,
32    /// IPv6-in-IPv4 encapsulation (IP protocol 41 in IPv4).
33    Ip6InIp = 6,
34    /// IPsec encapsulation.
35    Ipsec = 7,
36    /// IPv4-in-IPv6 encapsulation (next header 4 in IPv6).
37    Ip4InIp6 = 8,
38    /// IPv6-in-IPv6 encapsulation (next header 41 in IPv6).
39    Ip6InIp6 = 9,
40}
41
42impl TunnelType {
43    /// Convert a u64 value (from hints) to TunnelType.
44    pub fn from_u64(value: u64) -> Self {
45        match value {
46            0 => TunnelType::None,
47            1 => TunnelType::Vxlan,
48            2 => TunnelType::Gre,
49            3 => TunnelType::Gtp,
50            4 => TunnelType::Mpls,
51            5 => TunnelType::IpInIp,
52            6 => TunnelType::Ip6InIp,
53            7 => TunnelType::Ipsec,
54            8 => TunnelType::Ip4InIp6,
55            9 => TunnelType::Ip6InIp6,
56            _ => TunnelType::None,
57        }
58    }
59
60    /// Convert TunnelType to a string representation.
61    pub fn as_str(&self) -> Option<&'static str> {
62        match self {
63            TunnelType::None => None,
64            TunnelType::Vxlan => Some("vxlan"),
65            TunnelType::Gre => Some("gre"),
66            TunnelType::Gtp => Some("gtp"),
67            TunnelType::Mpls => Some("mpls"),
68            TunnelType::IpInIp => Some("ipinip"),
69            TunnelType::Ip6InIp => Some("ip6inip"),
70            TunnelType::Ipsec => Some("ipsec"),
71            TunnelType::Ip4InIp6 => Some("ip4inip6"),
72            TunnelType::Ip6InIp6 => Some("ip6inip6"),
73        }
74    }
75}
76
77/// Information about a single tunnel encapsulation layer.
78#[derive(Debug, Clone, Copy, Default)]
79pub struct TunnelLayer {
80    /// Type of tunnel at this layer.
81    pub tunnel_type: TunnelType,
82    /// Tunnel identifier (VNI, GRE key, TEID, MPLS label, etc.).
83    /// None if the tunnel type doesn't have an identifier.
84    pub tunnel_id: Option<u64>,
85    /// Byte offset where this tunnel started in the packet.
86    pub offset: usize,
87}
88
89/// Context passed through the parsing chain.
90#[derive(Debug, Clone)]
91pub struct ParseContext {
92    /// Link type from PCAP header (e.g., 1 = Ethernet).
93    pub link_type: u16,
94
95    /// Parent protocol that identified this protocol.
96    pub parent_protocol: Option<&'static str>,
97
98    /// Protocol-specific hints (e.g., ethertype, IP protocol number).
99    /// Uses SmallVec for consistency with ParseResult. Typically 2-4 entries.
100    pub hints: SmallVec<[HintEntry; 4]>,
101
102    /// Offset into the original packet where this protocol's data starts.
103    pub offset: usize,
104
105    /// Current encapsulation depth (0 = outer/no tunnel, 1+ = inside tunnel).
106    pub encap_depth: u8,
107
108    /// Stack of enclosing tunnel layers (innermost last).
109    /// Typical tunneled traffic has 1-2 layers; SmallVec<4> handles deeper nesting.
110    pub tunnel_stack: SmallVec<[TunnelLayer; 4]>,
111}
112
113impl ParseContext {
114    /// Create a new parse context for a packet with the given link type.
115    pub fn new(link_type: u16) -> Self {
116        Self {
117            link_type,
118            parent_protocol: None,
119            hints: SmallVec::new(),
120            offset: 0,
121            encap_depth: 0,
122            tunnel_stack: SmallVec::new(),
123        }
124    }
125
126    /// Push a new tunnel layer onto the stack and increment encap_depth.
127    pub fn push_tunnel(&mut self, tunnel_type: TunnelType, tunnel_id: Option<u64>) {
128        self.tunnel_stack.push(TunnelLayer {
129            tunnel_type,
130            tunnel_id,
131            offset: self.offset,
132        });
133        self.encap_depth = self.encap_depth.saturating_add(1);
134    }
135
136    /// Get the innermost (current) tunnel layer, if any.
137    pub fn current_tunnel(&self) -> Option<&TunnelLayer> {
138        self.tunnel_stack.last()
139    }
140
141    /// Get the innermost tunnel type, if inside a tunnel.
142    pub fn current_tunnel_type(&self) -> TunnelType {
143        self.tunnel_stack
144            .last()
145            .map(|t| t.tunnel_type)
146            .unwrap_or(TunnelType::None)
147    }
148
149    /// Get the innermost tunnel ID, if inside a tunnel with an ID.
150    pub fn current_tunnel_id(&self) -> Option<u64> {
151        self.tunnel_stack.last().and_then(|t| t.tunnel_id)
152    }
153
154    /// Get a hint value by key (linear search, but N is small).
155    #[inline]
156    pub fn hint(&self, key: &str) -> Option<u64> {
157        self.hints.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
158    }
159
160    /// Insert a hint value (appends, may create duplicates).
161    /// Use `set_hint()` if you need to update an existing hint.
162    #[inline]
163    pub fn insert_hint(&mut self, key: &'static str, value: u64) {
164        self.hints.push((key, value));
165    }
166
167    /// Set a hint value (updates existing or appends).
168    #[inline]
169    pub fn set_hint(&mut self, key: &'static str, value: u64) {
170        if let Some(entry) = self.hints.iter_mut().find(|(k, _)| *k == key) {
171            entry.1 = value;
172        } else {
173            self.hints.push((key, value));
174        }
175    }
176
177    /// Clear all hints (for context reuse).
178    #[inline]
179    pub fn clear_hints(&mut self) {
180        self.hints.clear();
181    }
182
183    /// Check if we're at the start of the packet (no parent protocol).
184    pub fn is_root(&self) -> bool {
185        self.parent_protocol.is_none()
186    }
187}
188
189/// Result of parsing a protocol layer.
190///
191/// Uses SmallVec for inline storage to avoid HashMap allocation overhead.
192/// Most protocols have <16 fields and <4 child hints, so these fit inline.
193///
194/// The lifetime parameter `'data` ties the result to the packet/buffer data,
195/// allowing zero-copy parsing where field values reference the packet directly.
196#[derive(Debug, Clone)]
197pub struct ParseResult<'data> {
198    /// Extracted field values. Most protocols have <16 fields.
199    /// Field values may reference the packet data (zero-copy).
200    pub fields: SmallVec<[FieldEntry<'data>; 16]>,
201
202    /// Remaining unparsed bytes (payload for next layer).
203    pub remaining: &'data [u8],
204
205    /// Hints for child protocol identification. Typically 2-4 entries.
206    pub child_hints: SmallVec<[HintEntry; 4]>,
207
208    /// Parse error if partial parsing occurred.
209    pub error: Option<String>,
210
211    /// Encapsulation depth when this protocol was parsed (0 = outer layer).
212    pub encap_depth: u8,
213
214    /// Type of the innermost enclosing tunnel (if inside a tunnel).
215    pub tunnel_type: TunnelType,
216
217    /// Identifier of the innermost enclosing tunnel (VNI, GRE key, TEID, etc.).
218    pub tunnel_id: Option<u64>,
219}
220
221impl<'data> ParseResult<'data> {
222    /// Create a successful parse result.
223    ///
224    /// Note: `encap_depth`, `tunnel_type`, and `tunnel_id` default to 0/None.
225    /// These are populated by the parse loop from the ParseContext after parsing.
226    pub fn success(
227        fields: SmallVec<[FieldEntry<'data>; 16]>,
228        remaining: &'data [u8],
229        child_hints: SmallVec<[HintEntry; 4]>,
230    ) -> Self {
231        Self {
232            fields,
233            remaining,
234            child_hints,
235            error: None,
236            encap_depth: 0,
237            tunnel_type: TunnelType::None,
238            tunnel_id: None,
239        }
240    }
241
242    /// Create an error parse result.
243    pub fn error(error: String, remaining: &'data [u8]) -> Self {
244        Self {
245            fields: SmallVec::new(),
246            remaining,
247            child_hints: SmallVec::new(),
248            error: Some(error),
249            encap_depth: 0,
250            tunnel_type: TunnelType::None,
251            tunnel_id: None,
252        }
253    }
254
255    /// Create a result with partial fields and an error.
256    pub fn partial(
257        fields: SmallVec<[FieldEntry<'data>; 16]>,
258        remaining: &'data [u8],
259        error: String,
260    ) -> Self {
261        Self {
262            fields,
263            remaining,
264            child_hints: SmallVec::new(),
265            error: Some(error),
266            encap_depth: 0,
267            tunnel_type: TunnelType::None,
268            tunnel_id: None,
269        }
270    }
271
272    /// Set encapsulation context from a ParseContext.
273    pub fn set_encap_context(&mut self, ctx: &ParseContext) {
274        self.encap_depth = ctx.encap_depth;
275        self.tunnel_type = ctx.current_tunnel_type();
276        self.tunnel_id = ctx.current_tunnel_id();
277    }
278
279    /// Get a field value by name (linear search, but N is small).
280    pub fn get(&self, name: &str) -> Option<&FieldValue<'data>> {
281        self.fields.iter().find(|(k, _)| *k == name).map(|(_, v)| v)
282    }
283
284    /// Get a child hint value by name.
285    pub fn hint(&self, name: &str) -> Option<u64> {
286        self.child_hints
287            .iter()
288            .find(|(k, _)| *k == name)
289            .map(|(_, v)| *v)
290    }
291
292    /// Check if parsing was successful.
293    pub fn is_ok(&self) -> bool {
294        self.error.is_none()
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::protocol::ethernet::ethertype;
302
303    #[test]
304    fn test_tunnel_type_conversions() {
305        assert_eq!(TunnelType::from_u64(0), TunnelType::None);
306        assert_eq!(TunnelType::from_u64(1), TunnelType::Vxlan);
307        assert_eq!(TunnelType::from_u64(2), TunnelType::Gre);
308        assert_eq!(TunnelType::from_u64(3), TunnelType::Gtp);
309        assert_eq!(TunnelType::from_u64(4), TunnelType::Mpls);
310        assert_eq!(TunnelType::from_u64(5), TunnelType::IpInIp);
311        assert_eq!(TunnelType::from_u64(6), TunnelType::Ip6InIp);
312        assert_eq!(TunnelType::from_u64(7), TunnelType::Ipsec);
313        assert_eq!(TunnelType::from_u64(99), TunnelType::None); // Unknown
314
315        assert_eq!(TunnelType::None.as_str(), None);
316        assert_eq!(TunnelType::Vxlan.as_str(), Some("vxlan"));
317        assert_eq!(TunnelType::Gre.as_str(), Some("gre"));
318        assert_eq!(TunnelType::Gtp.as_str(), Some("gtp"));
319        assert_eq!(TunnelType::IpInIp.as_str(), Some("ipinip"));
320    }
321
322    #[test]
323    fn test_context_encap_tracking() {
324        let mut ctx = ParseContext::new(1);
325        assert_eq!(ctx.encap_depth, 0);
326        assert_eq!(ctx.current_tunnel_type(), TunnelType::None);
327        assert_eq!(ctx.current_tunnel_id(), None);
328
329        // Push a VXLAN tunnel
330        ctx.push_tunnel(TunnelType::Vxlan, Some(100));
331        assert_eq!(ctx.encap_depth, 1);
332        assert_eq!(ctx.current_tunnel_type(), TunnelType::Vxlan);
333        assert_eq!(ctx.current_tunnel_id(), Some(100));
334
335        // Push an IP-in-IP tunnel (nested)
336        ctx.push_tunnel(TunnelType::IpInIp, None);
337        assert_eq!(ctx.encap_depth, 2);
338        assert_eq!(ctx.current_tunnel_type(), TunnelType::IpInIp);
339        assert_eq!(ctx.current_tunnel_id(), None);
340
341        // Verify tunnel stack
342        assert_eq!(ctx.tunnel_stack.len(), 2);
343        assert_eq!(ctx.tunnel_stack[0].tunnel_type, TunnelType::Vxlan);
344        assert_eq!(ctx.tunnel_stack[1].tunnel_type, TunnelType::IpInIp);
345    }
346
347    #[test]
348    fn test_parse_result_encap_context() {
349        let mut ctx = ParseContext::new(1);
350        ctx.push_tunnel(TunnelType::Gtp, Some(0x12345678));
351
352        let mut result = ParseResult::success(SmallVec::new(), &[], SmallVec::new());
353        assert_eq!(result.encap_depth, 0);
354        assert_eq!(result.tunnel_type, TunnelType::None);
355        assert_eq!(result.tunnel_id, None);
356
357        result.set_encap_context(&ctx);
358        assert_eq!(result.encap_depth, 1);
359        assert_eq!(result.tunnel_type, TunnelType::Gtp);
360        assert_eq!(result.tunnel_id, Some(0x12345678));
361    }
362
363    #[test]
364    fn test_context_hint_access() {
365        let mut ctx = ParseContext::new(1);
366        ctx.insert_hint("ip_protocol", 6);
367        ctx.insert_hint("dst_port", 80);
368
369        assert_eq!(ctx.hint("ip_protocol"), Some(6));
370        assert_eq!(ctx.hint("dst_port"), Some(80));
371        assert_eq!(ctx.hint("nonexistent"), None);
372    }
373
374    #[test]
375    fn test_context_set_hint_update() {
376        let mut ctx = ParseContext::new(1);
377        ctx.set_hint("ip_protocol", 6);
378        ctx.set_hint("ip_protocol", 17); // Update existing
379
380        assert_eq!(ctx.hint("ip_protocol"), Some(17));
381        assert_eq!(ctx.hints.len(), 1); // No duplicates
382    }
383
384    #[test]
385    fn test_context_set_hint_insert() {
386        let mut ctx = ParseContext::new(1);
387        ctx.set_hint("ip_protocol", 6);
388        ctx.set_hint("dst_port", 80); // Insert new
389
390        assert_eq!(ctx.hint("ip_protocol"), Some(6));
391        assert_eq!(ctx.hint("dst_port"), Some(80));
392        assert_eq!(ctx.hints.len(), 2);
393    }
394
395    #[test]
396    fn test_context_clear_hints() {
397        let mut ctx = ParseContext::new(1);
398        ctx.insert_hint("ip_protocol", 6);
399        ctx.insert_hint("dst_port", 80);
400
401        ctx.clear_hints();
402
403        assert_eq!(ctx.hints.len(), 0);
404        assert_eq!(ctx.hint("ip_protocol"), None);
405    }
406
407    #[test]
408    fn test_hint_count_stays_inline() {
409        let mut ctx = ParseContext::new(1);
410        ctx.insert_hint("ethertype", ethertype::IPV4 as u64);
411        ctx.insert_hint("ip_protocol", 6);
412        ctx.insert_hint("src_port", 12345);
413        ctx.insert_hint("dst_port", 80);
414
415        // Should stay inline (no heap allocation) with 4 entries
416        assert!(!ctx.hints.spilled());
417    }
418
419    #[test]
420    fn test_parse_result_success() {
421        let mut fields = SmallVec::new();
422        fields.push(("src_port", FieldValue::UInt16(80)));
423
424        let mut hints = SmallVec::new();
425        hints.push(("transport", 6u64));
426
427        let result = ParseResult::success(fields, &[], hints);
428
429        assert!(result.is_ok());
430        assert_eq!(result.get("src_port"), Some(&FieldValue::UInt16(80)));
431        assert_eq!(result.hint("transport"), Some(6));
432    }
433
434    #[test]
435    fn test_parse_result_error() {
436        let result = ParseResult::error("test error".to_string(), &[1, 2, 3]);
437
438        assert!(!result.is_ok());
439        assert_eq!(result.error, Some("test error".to_string()));
440        assert_eq!(result.remaining, &[1, 2, 3]);
441    }
442}