pcapsql_core/protocol/
quic.rs

1//! QUIC protocol parser.
2//!
3//! Parses QUIC transport protocol headers. QUIC encrypts most data,
4//! so this parser focuses on what's visible in cleartext: connection IDs,
5//! version, and packet type information.
6
7use compact_str::CompactString;
8use smallvec::SmallVec;
9
10use super::{FieldValue, ParseContext, ParseResult, Protocol};
11use crate::schema::{DataKind, FieldDescriptor};
12
13/// QUIC versions.
14mod version {
15    pub const VERSION_NEGOTIATION: u32 = 0x00000000;
16    pub const QUIC_V1: u32 = 0x00000001;
17    pub const QUIC_V2: u32 = 0x6b3343cf;
18    pub const DRAFT_29: u32 = 0xff00001d;
19    pub const DRAFT_32: u32 = 0xff000020;
20    pub const DRAFT_34: u32 = 0xff000022;
21}
22
23/// Long header packet types.
24mod long_packet_type {
25    pub const INITIAL: u8 = 0x0;
26    pub const ZERO_RTT: u8 = 0x1;
27    pub const HANDSHAKE: u8 = 0x2;
28    pub const RETRY: u8 = 0x3;
29}
30
31/// QUIC protocol parser.
32#[derive(Debug, Clone, Copy)]
33pub struct QuicProtocol;
34
35impl Protocol for QuicProtocol {
36    fn name(&self) -> &'static str {
37        "quic"
38    }
39
40    fn display_name(&self) -> &'static str {
41        "QUIC"
42    }
43
44    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
45        // Must be UDP
46        let parent = context.parent_protocol;
47        if parent != Some("udp") {
48            return None;
49        }
50
51        // Common QUIC ports
52        let src_port = context.hint("src_port");
53        let dst_port = context.hint("dst_port");
54
55        match (src_port, dst_port) {
56            (Some(443), _) | (_, Some(443)) => Some(40),
57            (Some(8443), _) | (_, Some(8443)) => Some(40),
58            _ => None,
59        }
60    }
61
62    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
63        let mut fields = SmallVec::new();
64
65        if data.is_empty() {
66            return ParseResult::error("QUIC packet empty".to_string(), data);
67        }
68
69        let first_byte = data[0];
70
71        // Check header form bit (bit 7)
72        let is_long_header = (first_byte & 0x80) != 0;
73
74        if is_long_header {
75            fields.push(("header_form", FieldValue::Str("long")));
76            parse_long_header(data, &mut fields)
77        } else {
78            fields.push(("header_form", FieldValue::Str("short")));
79            parse_short_header(data, &mut fields)
80        }
81    }
82
83    fn schema_fields(&self) -> Vec<FieldDescriptor> {
84        vec![
85            FieldDescriptor::new("quic.header_form", DataKind::String).set_nullable(true),
86            FieldDescriptor::new("quic.long_packet_type", DataKind::String).set_nullable(true),
87            FieldDescriptor::new("quic.version", DataKind::UInt32).set_nullable(true),
88            FieldDescriptor::new("quic.version_name", DataKind::String).set_nullable(true),
89            FieldDescriptor::new("quic.dcid_length", DataKind::UInt8).set_nullable(true),
90            FieldDescriptor::new("quic.dcid", DataKind::String).set_nullable(true),
91            FieldDescriptor::new("quic.scid_length", DataKind::UInt8).set_nullable(true),
92            FieldDescriptor::new("quic.scid", DataKind::String).set_nullable(true),
93            FieldDescriptor::new("quic.token_length", DataKind::UInt32).set_nullable(true),
94            FieldDescriptor::new("quic.packet_length", DataKind::UInt32).set_nullable(true),
95            FieldDescriptor::new("quic.spin_bit", DataKind::Bool).set_nullable(true),
96            FieldDescriptor::new("quic.key_phase", DataKind::Bool).set_nullable(true),
97            FieldDescriptor::new("quic.sni", DataKind::String).set_nullable(true),
98        ]
99    }
100
101    fn child_protocols(&self) -> &[&'static str] {
102        &[]
103    }
104
105    fn dependencies(&self) -> &'static [&'static str] {
106        &["udp"]
107    }
108}
109
110/// Parse QUIC long header.
111fn parse_long_header<'a>(
112    data: &'a [u8],
113    fields: &mut SmallVec<[(&'static str, FieldValue<'a>); 16]>,
114) -> ParseResult<'a> {
115    // Long Header format:
116    // 0                   1                   2                   3
117    // 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
118    // +-+-+-+-+-+-+-+-+
119    // |1|1|T T|X X X X|
120    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
121    // |                         Version (32)                          |
122    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
123    // |DCID Len|      Destination Connection ID (0..160)            ...
124    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125    // |SCID Len|      Source Connection ID (0..160)                 ...
126    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
127
128    if data.len() < 6 {
129        return ParseResult::error("QUIC long header too short".to_string(), data);
130    }
131
132    let first_byte = data[0];
133
134    // Fixed bit should be 1 (bit 6)
135    let fixed_bit = (first_byte & 0x40) != 0;
136    if !fixed_bit {
137        return ParseResult::error("QUIC fixed bit not set".to_string(), data);
138    }
139
140    // Packet type (bits 4-5)
141    let packet_type = (first_byte >> 4) & 0x03;
142    let packet_type_name = match packet_type {
143        long_packet_type::INITIAL => "Initial",
144        long_packet_type::ZERO_RTT => "0-RTT",
145        long_packet_type::HANDSHAKE => "Handshake",
146        long_packet_type::RETRY => "Retry",
147        _ => "Unknown",
148    };
149    fields.push(("long_packet_type", FieldValue::Str(packet_type_name)));
150
151    // Version (4 bytes)
152    let quic_version = u32::from_be_bytes([data[1], data[2], data[3], data[4]]);
153    fields.push(("version", FieldValue::UInt32(quic_version)));
154    fields.push((
155        "version_name",
156        FieldValue::OwnedString(CompactString::new(format_version(quic_version))),
157    ));
158
159    // DCID Length
160    let dcid_len = data[5] as usize;
161    fields.push(("dcid_length", FieldValue::UInt8(dcid_len as u8)));
162
163    let mut offset = 6;
164
165    // Destination Connection ID
166    if offset + dcid_len > data.len() {
167        return ParseResult::partial(
168            fields.clone(),
169            &data[offset..],
170            "QUIC DCID truncated".to_string(),
171        );
172    }
173    if dcid_len > 0 {
174        let dcid = &data[offset..offset + dcid_len];
175        fields.push((
176            "dcid",
177            FieldValue::OwnedString(CompactString::new(hex_encode(dcid))),
178        ));
179    }
180    offset += dcid_len;
181
182    // SCID Length
183    if offset >= data.len() {
184        return ParseResult::partial(
185            fields.clone(),
186            &data[offset..],
187            "QUIC SCID length missing".to_string(),
188        );
189    }
190    let scid_len = data[offset] as usize;
191    fields.push(("scid_length", FieldValue::UInt8(scid_len as u8)));
192    offset += 1;
193
194    // Source Connection ID
195    if offset + scid_len > data.len() {
196        return ParseResult::partial(
197            fields.clone(),
198            &data[offset..],
199            "QUIC SCID truncated".to_string(),
200        );
201    }
202    if scid_len > 0 {
203        let scid = &data[offset..offset + scid_len];
204        fields.push((
205            "scid",
206            FieldValue::OwnedString(CompactString::new(hex_encode(scid))),
207        ));
208    }
209    offset += scid_len;
210
211    // For Initial packets, try to parse token and extract SNI from CRYPTO frame
212    if packet_type == long_packet_type::INITIAL && offset < data.len() {
213        parse_initial_packet(&data[offset..], fields);
214    }
215
216    ParseResult::success(fields.clone(), &[], SmallVec::new())
217}
218
219/// Parse QUIC short header.
220fn parse_short_header<'a>(
221    data: &'a [u8],
222    fields: &mut SmallVec<[(&'static str, FieldValue<'a>); 16]>,
223) -> ParseResult<'a> {
224    // Short Header format:
225    // 0                   1                   2                   3
226    // 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
227    // +-+-+-+-+-+-+-+-+
228    // |0|1|S|R|R|K|P P|
229    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
230    // |                Destination Connection ID (0..160)           ...
231    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
232
233    if data.is_empty() {
234        return ParseResult::error("QUIC short header empty".to_string(), data);
235    }
236
237    let first_byte = data[0];
238
239    // Fixed bit should be 1 (bit 6)
240    let fixed_bit = (first_byte & 0x40) != 0;
241    if !fixed_bit {
242        return ParseResult::error("QUIC fixed bit not set".to_string(), data);
243    }
244
245    // Spin bit (bit 5)
246    let spin_bit = (first_byte & 0x20) != 0;
247    fields.push(("spin_bit", FieldValue::Bool(spin_bit)));
248
249    // Key phase (bit 2)
250    let key_phase = (first_byte & 0x04) != 0;
251    fields.push(("key_phase", FieldValue::Bool(key_phase)));
252
253    // Note: DCID length is not in the short header - it must be known from context
254    // For now, we can only note that this is a short header packet
255
256    ParseResult::success(fields.clone(), &[], SmallVec::new())
257}
258
259/// Parse Initial packet payload to extract token and potentially SNI.
260fn parse_initial_packet<'a>(
261    data: &[u8],
262    fields: &mut SmallVec<[(&'static str, FieldValue<'a>); 16]>,
263) {
264    // Initial packet has:
265    // - Token Length (variable-length integer)
266    // - Token
267    // - Length (variable-length integer)
268    // - Packet Number (1-4 bytes, encrypted)
269    // - Payload (encrypted, contains CRYPTO frame with TLS ClientHello)
270
271    if data.is_empty() {
272        return;
273    }
274
275    // Parse token length (variable-length integer)
276    let (token_len, consumed) = match parse_varint(data) {
277        Some(v) => v,
278        None => return,
279    };
280
281    fields.push(("token_length", FieldValue::UInt32(token_len as u32)));
282
283    let mut offset = consumed;
284
285    // Skip token
286    if offset + token_len > data.len() {
287        return;
288    }
289    offset += token_len;
290
291    // Parse packet length (variable-length integer)
292    if offset >= data.len() {
293        return;
294    }
295    let (packet_len, consumed) = match parse_varint(&data[offset..]) {
296        Some(v) => v,
297        None => return,
298    };
299
300    fields.push(("packet_length", FieldValue::UInt32(packet_len as u32)));
301    offset += consumed;
302
303    // The rest is encrypted - we'd need to derive keys to parse further
304    // For the Initial packet, the encryption key is derived from the DCID,
305    // but implementing full QUIC decryption is beyond scope here.
306
307    // Try to extract SNI from unencrypted CRYPTO frame in Initial packets
308    // This is a best-effort attempt - may not work for all implementations
309    if offset < data.len() {
310        try_extract_sni(&data[offset..], fields);
311    }
312}
313
314/// Try to extract SNI from potentially unencrypted Initial packet payload.
315/// This is a heuristic that looks for patterns typical of TLS ClientHello.
316fn try_extract_sni<'a>(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue<'a>); 16]>) {
317    // Look for SNI extension pattern in the data
318    // SNI extension has type 0x0000, followed by length, then name type, name length, and name
319
320    // Search for the pattern: 0x00 0x00 (SNI type) followed by reasonable lengths
321    for i in 0..data.len().saturating_sub(10) {
322        // Check for SNI extension type (0x0000)
323        if data[i] == 0x00 && data[i + 1] == 0x00 {
324            // Extension length
325            if i + 4 > data.len() {
326                continue;
327            }
328            let ext_len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
329            if ext_len == 0 || ext_len > 256 || i + 4 + ext_len > data.len() {
330                continue;
331            }
332
333            // Server name list length
334            if i + 6 > data.len() {
335                continue;
336            }
337            let list_len = u16::from_be_bytes([data[i + 4], data[i + 5]]) as usize;
338            if list_len == 0 || list_len > ext_len {
339                continue;
340            }
341
342            // Name type (0 = hostname)
343            if i + 7 > data.len() || data[i + 6] != 0x00 {
344                continue;
345            }
346
347            // Name length
348            if i + 9 > data.len() {
349                continue;
350            }
351            let name_len = u16::from_be_bytes([data[i + 7], data[i + 8]]) as usize;
352            if name_len == 0 || name_len > 255 || i + 9 + name_len > data.len() {
353                continue;
354            }
355
356            // Extract hostname
357            if let Ok(hostname) = std::str::from_utf8(&data[i + 9..i + 9 + name_len]) {
358                // Validate it looks like a hostname
359                if hostname
360                    .chars()
361                    .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
362                    && hostname.contains('.')
363                {
364                    fields.push(("sni", FieldValue::OwnedString(CompactString::new(hostname))));
365                    return;
366                }
367            }
368        }
369    }
370}
371
372/// Parse a QUIC variable-length integer.
373/// Returns (value, bytes_consumed) or None if invalid.
374fn parse_varint(data: &[u8]) -> Option<(usize, usize)> {
375    if data.is_empty() {
376        return None;
377    }
378
379    let first = data[0];
380    let prefix = first >> 6;
381
382    match prefix {
383        0 => {
384            // 1 byte, 6 bits
385            Some(((first & 0x3F) as usize, 1))
386        }
387        1 => {
388            // 2 bytes, 14 bits
389            if data.len() < 2 {
390                return None;
391            }
392            let value = (((first & 0x3F) as usize) << 8) | (data[1] as usize);
393            Some((value, 2))
394        }
395        2 => {
396            // 4 bytes, 30 bits
397            if data.len() < 4 {
398                return None;
399            }
400            let value = (((first & 0x3F) as usize) << 24)
401                | ((data[1] as usize) << 16)
402                | ((data[2] as usize) << 8)
403                | (data[3] as usize);
404            Some((value, 4))
405        }
406        3 => {
407            // 8 bytes, 62 bits
408            if data.len() < 8 {
409                return None;
410            }
411            let value = (((first & 0x3F) as usize) << 56)
412                | ((data[1] as usize) << 48)
413                | ((data[2] as usize) << 40)
414                | ((data[3] as usize) << 32)
415                | ((data[4] as usize) << 24)
416                | ((data[5] as usize) << 16)
417                | ((data[6] as usize) << 8)
418                | (data[7] as usize);
419            Some((value, 8))
420        }
421        _ => None,
422    }
423}
424
425/// Format QUIC version as a readable name.
426fn format_version(ver: u32) -> String {
427    match ver {
428        version::VERSION_NEGOTIATION => "Version Negotiation".to_string(),
429        version::QUIC_V1 => "QUIC v1".to_string(),
430        version::QUIC_V2 => "QUIC v2".to_string(),
431        version::DRAFT_29 => "Draft-29".to_string(),
432        version::DRAFT_32 => "Draft-32".to_string(),
433        version::DRAFT_34 => "Draft-34".to_string(),
434        v if (v & 0xff000000) == 0xff000000 => {
435            format!("Draft-{}", v & 0xff)
436        }
437        _ => format!("0x{ver:08x}"),
438    }
439}
440
441/// Encode bytes as hex string.
442fn hex_encode(data: &[u8]) -> String {
443    data.iter().map(|b| format!("{b:02x}")).collect()
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    /// Create a QUIC Initial packet (long header).
451    fn create_quic_initial(dcid: &[u8], scid: &[u8], version: u32) -> Vec<u8> {
452        let mut packet = Vec::new();
453
454        // First byte: Form=1, Fixed=1, Type=00 (Initial), Reserved + Packet Number Length
455        packet.push(0xC0 | 0x00);
456
457        // Version
458        packet.extend_from_slice(&version.to_be_bytes());
459
460        // DCID Length + DCID
461        packet.push(dcid.len() as u8);
462        packet.extend_from_slice(dcid);
463
464        // SCID Length + SCID
465        packet.push(scid.len() as u8);
466        packet.extend_from_slice(scid);
467
468        // Token Length (0)
469        packet.push(0x00);
470
471        // Packet Length (varint, e.g., 100 bytes)
472        packet.push(0x40); // 2-byte varint prefix
473        packet.push(0x64); // 100
474
475        // Some payload data (would be encrypted in real QUIC)
476        packet.extend(std::iter::repeat(0u8).take(100));
477
478        packet
479    }
480
481    /// Create a QUIC Handshake packet (long header).
482    fn create_quic_handshake(dcid: &[u8], scid: &[u8]) -> Vec<u8> {
483        let mut packet = Vec::new();
484
485        // First byte: Form=1, Fixed=1, Type=10 (Handshake)
486        packet.push(0xC0 | 0x20);
487
488        // Version (QUIC v1)
489        packet.extend_from_slice(&version::QUIC_V1.to_be_bytes());
490
491        // DCID Length + DCID
492        packet.push(dcid.len() as u8);
493        packet.extend_from_slice(dcid);
494
495        // SCID Length + SCID
496        packet.push(scid.len() as u8);
497        packet.extend_from_slice(scid);
498
499        // Packet Length (varint)
500        packet.push(0x40);
501        packet.push(0x32); // 50
502
503        // Payload
504        packet.extend(std::iter::repeat(0u8).take(50));
505
506        packet
507    }
508
509    /// Create a QUIC short header packet.
510    fn create_quic_short(dcid: &[u8], spin: bool, key_phase: bool) -> Vec<u8> {
511        let mut packet = Vec::new();
512
513        // First byte: Form=0, Fixed=1, Spin, Reserved, Key Phase, PN Length
514        let mut first = 0x40;
515        if spin {
516            first |= 0x20;
517        }
518        if key_phase {
519            first |= 0x04;
520        }
521        packet.push(first);
522
523        // DCID (length known from context, we just include it)
524        packet.extend_from_slice(dcid);
525
526        // Some encrypted payload
527        packet.extend(std::iter::repeat(0u8).take(20));
528
529        packet
530    }
531
532    #[test]
533    fn test_can_parse_quic() {
534        let parser = QuicProtocol;
535
536        // Not UDP
537        let ctx1 = ParseContext::new(1);
538        assert!(parser.can_parse(&ctx1).is_none());
539
540        // UDP but wrong port
541        let mut ctx2 = ParseContext::new(1);
542        ctx2.parent_protocol = Some("udp");
543        ctx2.insert_hint("dst_port", 80);
544        assert!(parser.can_parse(&ctx2).is_none());
545
546        // UDP on port 443
547        let mut ctx3 = ParseContext::new(1);
548        ctx3.parent_protocol = Some("udp");
549        ctx3.insert_hint("dst_port", 443);
550        assert!(parser.can_parse(&ctx3).is_some());
551    }
552
553    #[test]
554    fn test_quic_detection_long_header() {
555        let dcid = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
556        let scid = [0x11, 0x12, 0x13, 0x14];
557        let packet = create_quic_initial(&dcid, &scid, version::QUIC_V1);
558
559        let parser = QuicProtocol;
560        let mut context = ParseContext::new(1);
561        context.parent_protocol = Some("udp");
562        context.insert_hint("dst_port", 443);
563
564        let result = parser.parse(&packet, &context);
565
566        assert!(result.is_ok());
567        assert_eq!(result.get("header_form"), Some(&FieldValue::Str("long")));
568    }
569
570    #[test]
571    fn test_quic_detection_short_header() {
572        let dcid = [0x01, 0x02, 0x03, 0x04];
573        let packet = create_quic_short(&dcid, false, false);
574
575        let parser = QuicProtocol;
576        let mut context = ParseContext::new(1);
577        context.parent_protocol = Some("udp");
578        context.insert_hint("dst_port", 443);
579
580        let result = parser.parse(&packet, &context);
581
582        assert!(result.is_ok());
583        assert_eq!(result.get("header_form"), Some(&FieldValue::Str("short")));
584    }
585
586    #[test]
587    fn test_quic_version_parsing() {
588        let dcid = [0x01, 0x02, 0x03, 0x04];
589        let scid = [0x11, 0x12];
590        let packet = create_quic_initial(&dcid, &scid, version::QUIC_V1);
591
592        let parser = QuicProtocol;
593        let context = ParseContext::new(1);
594
595        let result = parser.parse(&packet, &context);
596
597        assert!(result.is_ok());
598        assert_eq!(
599            result.get("version"),
600            Some(&FieldValue::UInt32(version::QUIC_V1))
601        );
602        assert_eq!(
603            result.get("version_name"),
604            Some(&FieldValue::OwnedString(CompactString::new("QUIC v1")))
605        );
606    }
607
608    #[test]
609    fn test_quic_initial_packet_parsing() {
610        let dcid = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
611        let scid = [0x11, 0x12, 0x13, 0x14];
612        let packet = create_quic_initial(&dcid, &scid, version::QUIC_V1);
613
614        let parser = QuicProtocol;
615        let context = ParseContext::new(1);
616
617        let result = parser.parse(&packet, &context);
618
619        assert!(result.is_ok());
620        assert_eq!(
621            result.get("long_packet_type"),
622            Some(&FieldValue::Str("Initial"))
623        );
624    }
625
626    #[test]
627    fn test_quic_handshake_packet_parsing() {
628        let dcid = [0x01, 0x02, 0x03, 0x04];
629        let scid = [0x11, 0x12];
630        let packet = create_quic_handshake(&dcid, &scid);
631
632        let parser = QuicProtocol;
633        let context = ParseContext::new(1);
634
635        let result = parser.parse(&packet, &context);
636
637        assert!(result.is_ok());
638        assert_eq!(
639            result.get("long_packet_type"),
640            Some(&FieldValue::Str("Handshake"))
641        );
642    }
643
644    #[test]
645    fn test_quic_dcid_extraction() {
646        let dcid = [0xaa, 0xbb, 0xcc, 0xdd];
647        let scid = [0x11, 0x22];
648        let packet = create_quic_initial(&dcid, &scid, version::QUIC_V1);
649
650        let parser = QuicProtocol;
651        let context = ParseContext::new(1);
652
653        let result = parser.parse(&packet, &context);
654
655        assert!(result.is_ok());
656        assert_eq!(result.get("dcid_length"), Some(&FieldValue::UInt8(4)));
657        assert_eq!(
658            result.get("dcid"),
659            Some(&FieldValue::OwnedString(CompactString::new("aabbccdd")))
660        );
661    }
662
663    #[test]
664    fn test_quic_scid_extraction() {
665        let dcid = [0x01, 0x02];
666        let scid = [0xee, 0xff, 0x00, 0x11];
667        let packet = create_quic_initial(&dcid, &scid, version::QUIC_V1);
668
669        let parser = QuicProtocol;
670        let context = ParseContext::new(1);
671
672        let result = parser.parse(&packet, &context);
673
674        assert!(result.is_ok());
675        assert_eq!(result.get("scid_length"), Some(&FieldValue::UInt8(4)));
676        assert_eq!(
677            result.get("scid"),
678            Some(&FieldValue::OwnedString(CompactString::new("eeff0011")))
679        );
680    }
681
682    #[test]
683    fn test_quic_version_negotiation_detection() {
684        let dcid = [0x01, 0x02, 0x03, 0x04];
685        let scid = [0x11, 0x12];
686        let packet = create_quic_initial(&dcid, &scid, version::VERSION_NEGOTIATION);
687
688        let parser = QuicProtocol;
689        let context = ParseContext::new(1);
690
691        let result = parser.parse(&packet, &context);
692
693        assert!(result.is_ok());
694        assert_eq!(
695            result.get("version"),
696            Some(&FieldValue::UInt32(version::VERSION_NEGOTIATION))
697        );
698        assert_eq!(
699            result.get("version_name"),
700            Some(&FieldValue::OwnedString(CompactString::new(
701                "Version Negotiation"
702            )))
703        );
704    }
705
706    #[test]
707    fn test_quic_unknown_version_handling() {
708        let dcid = [0x01, 0x02];
709        let scid = [0x11, 0x12];
710        let unknown_version = 0xdeadbeef;
711        let packet = create_quic_initial(&dcid, &scid, unknown_version);
712
713        let parser = QuicProtocol;
714        let context = ParseContext::new(1);
715
716        let result = parser.parse(&packet, &context);
717
718        assert!(result.is_ok());
719        assert_eq!(
720            result.get("version"),
721            Some(&FieldValue::UInt32(unknown_version))
722        );
723        assert_eq!(
724            result.get("version_name"),
725            Some(&FieldValue::OwnedString(CompactString::new("0xdeadbeef")))
726        );
727    }
728
729    #[test]
730    fn test_quic_short_header_spin_bit() {
731        let dcid = [0x01, 0x02, 0x03, 0x04];
732        let packet = create_quic_short(&dcid, true, false);
733
734        let parser = QuicProtocol;
735        let context = ParseContext::new(1);
736
737        let result = parser.parse(&packet, &context);
738
739        assert!(result.is_ok());
740        assert_eq!(result.get("spin_bit"), Some(&FieldValue::Bool(true)));
741        assert_eq!(result.get("key_phase"), Some(&FieldValue::Bool(false)));
742    }
743
744    #[test]
745    fn test_quic_short_header_key_phase() {
746        let dcid = [0x01, 0x02, 0x03, 0x04];
747        let packet = create_quic_short(&dcid, false, true);
748
749        let parser = QuicProtocol;
750        let context = ParseContext::new(1);
751
752        let result = parser.parse(&packet, &context);
753
754        assert!(result.is_ok());
755        assert_eq!(result.get("spin_bit"), Some(&FieldValue::Bool(false)));
756        assert_eq!(result.get("key_phase"), Some(&FieldValue::Bool(true)));
757    }
758
759    #[test]
760    fn test_quic_schema_fields() {
761        let parser = QuicProtocol;
762        let fields = parser.schema_fields();
763
764        assert!(!fields.is_empty());
765
766        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
767        assert!(field_names.contains(&"quic.header_form"));
768        assert!(field_names.contains(&"quic.version"));
769        assert!(field_names.contains(&"quic.dcid"));
770        assert!(field_names.contains(&"quic.scid"));
771        assert!(field_names.contains(&"quic.sni"));
772    }
773
774    #[test]
775    fn test_varint_parsing() {
776        // 1-byte varint
777        assert_eq!(parse_varint(&[0x25]), Some((0x25, 1)));
778        assert_eq!(parse_varint(&[0x00]), Some((0, 1)));
779
780        // 2-byte varint (prefix 01)
781        assert_eq!(parse_varint(&[0x40, 0x19]), Some((0x19, 2)));
782        assert_eq!(parse_varint(&[0x7f, 0xff]), Some((0x3fff, 2)));
783
784        // 4-byte varint (prefix 10)
785        assert_eq!(parse_varint(&[0x80, 0x00, 0x00, 0x01]), Some((0x01, 4)));
786
787        // Empty data
788        assert_eq!(parse_varint(&[]), None);
789    }
790
791    #[test]
792    fn test_version_formatting() {
793        assert_eq!(format_version(version::QUIC_V1), "QUIC v1");
794        assert_eq!(format_version(version::QUIC_V2), "QUIC v2");
795        assert_eq!(
796            format_version(version::VERSION_NEGOTIATION),
797            "Version Negotiation"
798        );
799        assert_eq!(format_version(0xff00001d), "Draft-29");
800        assert_eq!(format_version(0x12345678), "0x12345678");
801    }
802}