pcapsql_core/protocol/
gtp.rs

1//! GTP (GPRS Tunneling Protocol) parser.
2//!
3//! GTP is a group of IP-based communications protocols used to carry
4//! general packet radio service (GPRS) within GSM, UMTS, LTE, and 5G networks.
5//!
6//! 3GPP TS 29.060: GPRS Tunnelling Protocol (GTP) across the Gn and Gp interface
7//! 3GPP TS 29.281: GPRS Tunnelling Protocol User Plane (GTPv1-U)
8//! 3GPP TS 29.274: GPRS Tunnelling Protocol version 2 (GTPv2-C)
9
10use compact_str::CompactString;
11use smallvec::SmallVec;
12
13use super::ethernet::ethertype;
14use super::{FieldValue, ParseContext, ParseResult, Protocol, TunnelType};
15use crate::schema::{DataKind, FieldDescriptor};
16
17/// GTP-U (User Plane) UDP port.
18pub const GTP_U_PORT: u16 = 2152;
19
20/// GTP-C (Control Plane) UDP port.
21pub const GTP_C_PORT: u16 = 2123;
22
23/// Maximum extension headers to parse (safety limit).
24const MAX_EXTENSION_HEADERS: usize = 16;
25
26/// GTP message types.
27#[allow(dead_code)]
28pub mod message_type {
29    pub const ECHO_REQUEST: u8 = 1;
30    pub const ECHO_RESPONSE: u8 = 2;
31    pub const ERROR_INDICATION: u8 = 26;
32    pub const SUPPORTED_EXTENSION_HEADERS_NOTIFICATION: u8 = 31;
33    pub const END_MARKER: u8 = 254;
34    pub const G_PDU: u8 = 255;
35}
36
37/// GTP extension header types (3GPP TS 29.281).
38pub mod extension_header_type {
39    pub const NO_MORE: u8 = 0x00;
40    pub const MBMS_SUPPORT_INDICATION: u8 = 0x01;
41    pub const MS_INFO_CHANGE_REPORTING: u8 = 0x02;
42    pub const SERVICE_CLASS_INDICATOR: u8 = 0x20;
43    pub const UDP_PORT: u8 = 0x40;
44    pub const RAN_CONTAINER: u8 = 0x81;
45    pub const LONG_PDCP_PDU_NUMBER: u8 = 0x82;
46    pub const XW_RAN_CONTAINER: u8 = 0x83;
47    pub const NR_RAN_CONTAINER: u8 = 0x84;
48    pub const PDU_SESSION_CONTAINER: u8 = 0x85;
49    pub const PDCP_PDU_NUMBER: u8 = 0xC0;
50}
51
52/// Get the name of a GTP extension header type.
53fn extension_header_type_name(ext_type: u8) -> &'static str {
54    match ext_type {
55        extension_header_type::NO_MORE => "No More",
56        extension_header_type::MBMS_SUPPORT_INDICATION => "MBMS Support Indication",
57        extension_header_type::MS_INFO_CHANGE_REPORTING => "MS Info Change Reporting",
58        extension_header_type::SERVICE_CLASS_INDICATOR => "Service Class Indicator",
59        extension_header_type::UDP_PORT => "UDP Port",
60        extension_header_type::RAN_CONTAINER => "RAN Container",
61        extension_header_type::LONG_PDCP_PDU_NUMBER => "Long PDCP PDU Number",
62        extension_header_type::XW_RAN_CONTAINER => "Xw RAN Container",
63        extension_header_type::NR_RAN_CONTAINER => "NR RAN Container",
64        extension_header_type::PDU_SESSION_CONTAINER => "PDU Session Container",
65        extension_header_type::PDCP_PDU_NUMBER => "PDCP PDU Number",
66        _ => "Unknown",
67    }
68}
69
70/// GTP protocol parser.
71#[derive(Debug, Clone, Copy)]
72pub struct GtpProtocol;
73
74impl Protocol for GtpProtocol {
75    fn name(&self) -> &'static str {
76        "gtp"
77    }
78
79    fn display_name(&self) -> &'static str {
80        "GTP"
81    }
82
83    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
84        // Match when UDP dst_port hint equals GTP-U or GTP-C port
85        match context.hint("dst_port") {
86            Some(port) if port == GTP_U_PORT as u64 => Some(100),
87            Some(port) if port == GTP_C_PORT as u64 => Some(100),
88            _ => None,
89        }
90    }
91
92    fn parse<'a>(&self, data: &'a [u8], context: &ParseContext) -> ParseResult<'a> {
93        // Minimum GTP header is 8 bytes (without optional fields)
94        if data.len() < 8 {
95            return ParseResult::error("GTP header too short".to_string(), data);
96        }
97
98        let mut fields = SmallVec::new();
99
100        // Byte 0: Flags
101        // Bits 7-5: Version (1 for GTPv1, 2 for GTPv2)
102        // Bit 4: PT (Protocol Type: 1 = GTP, 0 = GTP')
103        // Bit 3: Reserved (*) / P flag (Piggyback) for GTPv2
104        // Bit 2: E (Extension Header flag) / T flag (TEID present) for GTPv2
105        // Bit 1: S (Sequence number flag)
106        // Bit 0: PN (N-PDU number flag)
107        let flags = data[0];
108        let version = (flags >> 5) & 0x07;
109        fields.push(("version", FieldValue::UInt8(version)));
110
111        // Handle GTPv2-C differently
112        if version == 2 {
113            return self.parse_gtpv2(data, context);
114        }
115
116        // GTPv1 parsing
117        let protocol_type = (flags >> 4) & 0x01;
118        let extension_header_flag = (flags >> 2) & 0x01 == 1;
119        let sequence_flag = (flags >> 1) & 0x01 == 1;
120        let npdu_flag = flags & 0x01 == 1;
121
122        fields.push(("protocol_type", FieldValue::UInt8(protocol_type)));
123
124        // Byte 1: Message Type
125        let message_type = data[1];
126        fields.push(("message_type", FieldValue::UInt8(message_type)));
127
128        // Bytes 2-3: Length (excludes mandatory header)
129        let length = u16::from_be_bytes([data[2], data[3]]);
130        fields.push(("length", FieldValue::UInt16(length)));
131
132        // Bytes 4-7: TEID (Tunnel Endpoint Identifier)
133        let teid = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
134        fields.push(("teid", FieldValue::UInt32(teid)));
135
136        let mut offset = 8;
137
138        // If any of the E, S, or PN flags are set, there are 4 more bytes
139        if extension_header_flag || sequence_flag || npdu_flag {
140            if data.len() < offset + 4 {
141                return ParseResult::error("GTP: optional header fields missing".to_string(), data);
142            }
143
144            // Bytes 8-9: Sequence Number
145            if sequence_flag {
146                let sequence = u16::from_be_bytes([data[offset], data[offset + 1]]);
147                fields.push(("sequence", FieldValue::UInt16(sequence)));
148            }
149            offset += 2;
150
151            // Byte 10: N-PDU Number
152            if npdu_flag {
153                fields.push(("npdu", FieldValue::UInt8(data[offset])));
154            }
155            offset += 1;
156
157            // Byte 11: Next Extension Header Type
158            let mut next_ext_type = data[offset];
159            offset += 1;
160
161            // Parse extension headers if present
162            if extension_header_flag && next_ext_type != 0 {
163                let mut ext_headers = Vec::new();
164                let mut ext_count = 0u8;
165
166                while next_ext_type != extension_header_type::NO_MORE
167                    && offset < data.len()
168                    && ext_count < MAX_EXTENSION_HEADERS as u8
169                {
170                    // Extension header length (in 4-byte units, including length and next_type fields)
171                    if offset >= data.len() {
172                        break;
173                    }
174                    let ext_len_units = data[offset] as usize;
175                    let ext_len = ext_len_units * 4;
176
177                    if ext_len == 0 || offset + ext_len > data.len() {
178                        // Invalid extension header, stop parsing
179                        break;
180                    }
181
182                    // Store the extension header type name
183                    ext_headers.push(format!(
184                        "{}(0x{:02X})",
185                        extension_header_type_name(next_ext_type),
186                        next_ext_type
187                    ));
188
189                    // Next extension header type is at the last byte of this extension
190                    next_ext_type = data[offset + ext_len - 1];
191                    offset += ext_len;
192                    ext_count += 1;
193                }
194
195                if !ext_headers.is_empty() {
196                    fields.push((
197                        "extension_headers",
198                        FieldValue::OwnedString(CompactString::new(ext_headers.join(","))),
199                    ));
200                    fields.push(("extension_header_count", FieldValue::UInt8(ext_count)));
201                }
202            }
203        }
204
205        // Set up child hints
206        let mut child_hints = SmallVec::new();
207
208        // For G-PDU (type 255), the payload is user data (usually IP)
209        if message_type == message_type::G_PDU && offset < data.len() {
210            let first_byte = data[offset];
211            let ip_version = (first_byte >> 4) & 0x0F;
212
213            match ip_version {
214                4 => {
215                    child_hints.push(("ethertype", ethertype::IPV4 as u64));
216                    child_hints.push(("ip_version", 4u64));
217                }
218                6 => {
219                    child_hints.push(("ethertype", ethertype::IPV6 as u64));
220                    child_hints.push(("ip_version", 6u64));
221                }
222                _ => {}
223            }
224        }
225
226        // Also check if this is GTP-U or GTP-C for context
227        if let Some(port) = context.hint("dst_port") {
228            if port == GTP_U_PORT as u64 {
229                child_hints.push(("gtp_plane", 1u64)); // User plane
230            } else if port == GTP_C_PORT as u64 {
231                child_hints.push(("gtp_plane", 0u64)); // Control plane
232            }
233        }
234
235        // Signal tunnel boundary for encapsulation tracking
236        child_hints.push(("tunnel_type", TunnelType::Gtp as u64));
237        child_hints.push(("tunnel_id", teid as u64)); // TEID is the tunnel identifier
238
239        ParseResult::success(fields, &data[offset..], child_hints)
240    }
241
242    fn schema_fields(&self) -> Vec<FieldDescriptor> {
243        vec![
244            // Common fields
245            FieldDescriptor::new("gtp.version", DataKind::UInt8).set_nullable(true),
246            FieldDescriptor::new("gtp.message_type", DataKind::UInt8).set_nullable(true),
247            FieldDescriptor::new("gtp.length", DataKind::UInt16).set_nullable(true),
248            FieldDescriptor::new("gtp.teid", DataKind::UInt32).set_nullable(true),
249            // GTPv1 fields
250            FieldDescriptor::new("gtp.protocol_type", DataKind::UInt8).set_nullable(true),
251            FieldDescriptor::new("gtp.sequence", DataKind::UInt16).set_nullable(true),
252            FieldDescriptor::new("gtp.npdu", DataKind::UInt8).set_nullable(true),
253            FieldDescriptor::new("gtp.extension_headers", DataKind::String).set_nullable(true),
254            FieldDescriptor::new("gtp.extension_header_count", DataKind::UInt8).set_nullable(true),
255            // GTPv2-C fields
256            FieldDescriptor::new("gtp.piggyback", DataKind::Bool).set_nullable(true),
257            FieldDescriptor::new("gtp.teid_present", DataKind::Bool).set_nullable(true),
258        ]
259    }
260
261    fn child_protocols(&self) -> &[&'static str] {
262        // GTP-U encapsulates IP packets
263        &["ipv4", "ipv6"]
264    }
265
266    fn dependencies(&self) -> &'static [&'static str] {
267        &["udp"] // GTP runs over UDP ports 2123, 2152
268    }
269}
270
271impl GtpProtocol {
272    /// Parse GTPv2-C header (3GPP TS 29.274).
273    ///
274    /// GTPv2-C Header Format:
275    /// ```text
276    ///  0                   1                   2                   3
277    ///  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
278    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
279    /// |Version|  P  |T|  Spare  |      Message Type                  |
280    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
281    /// |                         Length                               |
282    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
283    /// |                TEID (if T=1)                                  |
284    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
285    /// |          Sequence Number                      |    Spare     |
286    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
287    /// ```
288    fn parse_gtpv2<'a>(&self, data: &'a [u8], context: &ParseContext) -> ParseResult<'a> {
289        if data.len() < 8 {
290            return ParseResult::error("GTPv2 header too short".to_string(), data);
291        }
292
293        let mut fields = SmallVec::new();
294
295        let flags = data[0];
296        let version = (flags >> 5) & 0x07;
297        let piggyback = (flags >> 4) & 0x01 == 1;
298        let teid_present = (flags >> 3) & 0x01 == 1;
299
300        fields.push(("version", FieldValue::UInt8(version)));
301        fields.push(("piggyback", FieldValue::Bool(piggyback)));
302        fields.push(("teid_present", FieldValue::Bool(teid_present)));
303
304        // Byte 1: Message Type
305        let message_type = data[1];
306        fields.push(("message_type", FieldValue::UInt8(message_type)));
307
308        // Bytes 2-3: Length (includes optional TEID + SeqNo)
309        let length = u16::from_be_bytes([data[2], data[3]]);
310        fields.push(("length", FieldValue::UInt16(length)));
311
312        let mut offset = 4;
313
314        // TEID is present if T flag is set
315        if teid_present {
316            if data.len() < offset + 4 {
317                return ParseResult::error("GTPv2: missing TEID field".to_string(), data);
318            }
319            let teid = u32::from_be_bytes([
320                data[offset],
321                data[offset + 1],
322                data[offset + 2],
323                data[offset + 3],
324            ]);
325            fields.push(("teid", FieldValue::UInt32(teid)));
326            offset += 4;
327        }
328
329        // Sequence Number (3 bytes) + Spare (1 byte)
330        if data.len() < offset + 4 {
331            return ParseResult::error("GTPv2: missing sequence number".to_string(), data);
332        }
333        let sequence = ((data[offset] as u32) << 16)
334            | ((data[offset + 1] as u32) << 8)
335            | (data[offset + 2] as u32);
336        fields.push(("sequence", FieldValue::UInt32(sequence)));
337        offset += 4;
338
339        // Set up child hints for GTP-C
340        let mut child_hints = SmallVec::new();
341        if let Some(port) = context.hint("dst_port") {
342            if port == GTP_C_PORT as u64 {
343                child_hints.push(("gtp_plane", 0u64)); // Control plane
344            }
345        }
346
347        ParseResult::success(fields, &data[offset..], child_hints)
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    /// Create a minimal GTPv1 header.
356    fn create_gtp_header(
357        message_type: u8,
358        teid: u32,
359        payload_len: u16,
360        sequence: Option<u16>,
361    ) -> Vec<u8> {
362        let mut header = Vec::new();
363
364        // Flags: Version 1, PT=1 (GTP), optional S flag
365        let mut flags = 0x30u8; // Version 1, PT=1
366        if sequence.is_some() {
367            flags |= 0x02; // S flag
368        }
369        header.push(flags);
370
371        // Message type
372        header.push(message_type);
373
374        // Length
375        let len = if sequence.is_some() {
376            payload_len + 4 // Add 4 for optional fields
377        } else {
378            payload_len
379        };
380        header.extend_from_slice(&len.to_be_bytes());
381
382        // TEID
383        header.extend_from_slice(&teid.to_be_bytes());
384
385        // Optional fields if S flag is set
386        if let Some(seq) = sequence {
387            header.extend_from_slice(&seq.to_be_bytes());
388            header.push(0); // N-PDU
389            header.push(0); // Next Extension Header Type
390        }
391
392        header
393    }
394
395    // Test 1: can_parse with GTP-U port
396    #[test]
397    fn test_can_parse_with_gtp_u_port() {
398        let parser = GtpProtocol;
399
400        // Without hint
401        let ctx1 = ParseContext::new(1);
402        assert!(parser.can_parse(&ctx1).is_none());
403
404        // With wrong port
405        let mut ctx2 = ParseContext::new(1);
406        ctx2.insert_hint("dst_port", 80);
407        assert!(parser.can_parse(&ctx2).is_none());
408
409        // With GTP-U port
410        let mut ctx3 = ParseContext::new(1);
411        ctx3.insert_hint("dst_port", 2152);
412        assert!(parser.can_parse(&ctx3).is_some());
413        assert_eq!(parser.can_parse(&ctx3), Some(100));
414    }
415
416    // Test 2: can_parse with GTP-C port
417    #[test]
418    fn test_can_parse_with_gtp_c_port() {
419        let parser = GtpProtocol;
420
421        let mut context = ParseContext::new(1);
422        context.insert_hint("dst_port", 2123);
423
424        assert!(parser.can_parse(&context).is_some());
425        assert_eq!(parser.can_parse(&context), Some(100));
426    }
427
428    // Test 3: GTPv1 header parsing
429    #[test]
430    fn test_gtpv1_header_parsing() {
431        let header = create_gtp_header(message_type::G_PDU, 0x12345678, 0, None);
432
433        let parser = GtpProtocol;
434        let mut context = ParseContext::new(1);
435        context.insert_hint("dst_port", 2152);
436
437        let result = parser.parse(&header, &context);
438
439        assert!(result.is_ok());
440        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(1)));
441        assert_eq!(result.get("protocol_type"), Some(&FieldValue::UInt8(1)));
442        assert_eq!(result.get("message_type"), Some(&FieldValue::UInt8(255)));
443    }
444
445    // Test 4: TEID extraction
446    #[test]
447    fn test_teid_extraction() {
448        let parser = GtpProtocol;
449        let mut context = ParseContext::new(1);
450        context.insert_hint("dst_port", 2152);
451
452        // Test various TEID values
453        let test_teids = [0u32, 1, 0x12345678, 0xFFFFFFFF];
454
455        for teid in test_teids {
456            let header = create_gtp_header(message_type::G_PDU, teid, 0, None);
457            let result = parser.parse(&header, &context);
458
459            assert!(result.is_ok());
460            assert_eq!(result.get("teid"), Some(&FieldValue::UInt32(teid)));
461        }
462    }
463
464    // Test 5: Message type parsing
465    #[test]
466    fn test_message_type_parsing() {
467        let parser = GtpProtocol;
468        let mut context = ParseContext::new(1);
469        context.insert_hint("dst_port", 2152);
470
471        let test_types = [
472            message_type::ECHO_REQUEST,
473            message_type::ECHO_RESPONSE,
474            message_type::ERROR_INDICATION,
475            message_type::G_PDU,
476        ];
477
478        for msg_type in test_types {
479            let header = create_gtp_header(msg_type, 0x1234, 0, None);
480            let result = parser.parse(&header, &context);
481
482            assert!(result.is_ok());
483            assert_eq!(
484                result.get("message_type"),
485                Some(&FieldValue::UInt8(msg_type))
486            );
487        }
488    }
489
490    // Test 6: Optional sequence number
491    #[test]
492    fn test_optional_sequence_number() {
493        let parser = GtpProtocol;
494        let mut context = ParseContext::new(1);
495        context.insert_hint("dst_port", 2152);
496
497        // With sequence number
498        let header = create_gtp_header(message_type::G_PDU, 0x1234, 0, Some(0xABCD));
499        let result = parser.parse(&header, &context);
500
501        assert!(result.is_ok());
502        assert_eq!(result.get("sequence"), Some(&FieldValue::UInt16(0xABCD)));
503    }
504
505    // Test 7: G-PDU child protocol detection
506    #[test]
507    fn test_gpdu_child_protocol_detection() {
508        let parser = GtpProtocol;
509        let mut context = ParseContext::new(1);
510        context.insert_hint("dst_port", 2152);
511
512        // G-PDU with IPv4 payload
513        let mut data_ipv4 = create_gtp_header(message_type::G_PDU, 0x1234, 20, None);
514        data_ipv4.extend_from_slice(&[0x45, 0x00, 0x00, 0x14]); // IPv4 header start
515
516        let result_ipv4 = parser.parse(&data_ipv4, &context);
517        assert!(result_ipv4.is_ok());
518        assert_eq!(result_ipv4.hint("ethertype"), Some(0x0800u64));
519        assert_eq!(result_ipv4.hint("ip_version"), Some(4u64));
520
521        // G-PDU with IPv6 payload
522        let mut data_ipv6 = create_gtp_header(message_type::G_PDU, 0x1234, 40, None);
523        data_ipv6.extend_from_slice(&[0x60, 0x00, 0x00, 0x00]); // IPv6 header start
524
525        let result_ipv6 = parser.parse(&data_ipv6, &context);
526        assert!(result_ipv6.is_ok());
527        assert_eq!(result_ipv6.hint("ethertype"), Some(0x86DDu64));
528        assert_eq!(result_ipv6.hint("ip_version"), Some(6u64));
529    }
530
531    // Test 8: Too short header
532    #[test]
533    fn test_gtp_too_short() {
534        let parser = GtpProtocol;
535        let mut context = ParseContext::new(1);
536        context.insert_hint("dst_port", 2152);
537
538        let short_header = [0x30, 0xFF, 0x00, 0x00]; // Only 4 bytes
539        let result = parser.parse(&short_header, &context);
540
541        assert!(!result.is_ok());
542        assert!(result.error.is_some());
543    }
544
545    // Test 9: Length field
546    #[test]
547    fn test_length_field() {
548        let parser = GtpProtocol;
549        let mut context = ParseContext::new(1);
550        context.insert_hint("dst_port", 2152);
551
552        let mut header = create_gtp_header(message_type::G_PDU, 0x1234, 100, None);
553        // Add 100 bytes of payload
554        header.extend(vec![0u8; 100]);
555
556        let result = parser.parse(&header, &context);
557
558        assert!(result.is_ok());
559        assert_eq!(result.get("length"), Some(&FieldValue::UInt16(100)));
560        assert_eq!(result.remaining.len(), 100);
561    }
562
563    // Test 10: Schema fields
564    #[test]
565    fn test_gtp_schema_fields() {
566        let parser = GtpProtocol;
567        let fields = parser.schema_fields();
568
569        assert!(!fields.is_empty());
570        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
571        assert!(field_names.contains(&"gtp.version"));
572        assert!(field_names.contains(&"gtp.protocol_type"));
573        assert!(field_names.contains(&"gtp.message_type"));
574        assert!(field_names.contains(&"gtp.length"));
575        assert!(field_names.contains(&"gtp.teid"));
576        assert!(field_names.contains(&"gtp.sequence"));
577    }
578
579    // Test 11: GTP' (Protocol Type = 0)
580    #[test]
581    fn test_gtp_prime() {
582        let parser = GtpProtocol;
583        let mut context = ParseContext::new(1);
584        context.insert_hint("dst_port", 2152);
585
586        // GTP' header (PT = 0)
587        let header = vec![
588            0x20, // Version 1, PT=0 (GTP')
589            0x01, // Echo Request
590            0x00, 0x04, // Length
591            0x00, 0x00, 0x00, 0x01, // TEID
592        ];
593
594        let result = parser.parse(&header, &context);
595
596        assert!(result.is_ok());
597        assert_eq!(result.get("protocol_type"), Some(&FieldValue::UInt8(0)));
598    }
599
600    // Test 12: Extension header parsing - single extension
601    #[test]
602    fn test_extension_header_single() {
603        let parser = GtpProtocol;
604        let mut context = ParseContext::new(1);
605        context.insert_hint("dst_port", 2152);
606
607        // GTPv1 with E flag set and one extension header
608        let header = vec![
609            0x34, // Version 1, PT=1, E=1
610            message_type::G_PDU,
611            0x00,
612            0x0C, // Length (includes optional fields + ext header)
613            0x00,
614            0x00,
615            0x00,
616            0x01, // TEID
617            0x00,
618            0x00,                                   // Sequence (not present but field exists)
619            0x00,                                   // N-PDU
620            extension_header_type::PDCP_PDU_NUMBER, // Next ext header type
621            // Extension header: PDCP PDU Number
622            0x01, // Length = 1 * 4 = 4 bytes
623            0x12,
624            0x34,                           // PDCP PDU Number data
625            extension_header_type::NO_MORE, // No more extensions
626        ];
627
628        let result = parser.parse(&header, &context);
629
630        assert!(result.is_ok());
631        assert_eq!(
632            result.get("extension_header_count"),
633            Some(&FieldValue::UInt8(1))
634        );
635
636        if let Some(FieldValue::OwnedString(ext)) = result.get("extension_headers") {
637            assert!(ext.contains("PDCP PDU Number"));
638            assert!(ext.contains("0xC0"));
639        } else {
640            panic!("Expected extension_headers field");
641        }
642    }
643
644    // Test 13: Extension header parsing - multiple extensions (chain)
645    #[test]
646    fn test_extension_header_chain() {
647        let parser = GtpProtocol;
648        let mut context = ParseContext::new(1);
649        context.insert_hint("dst_port", 2152);
650
651        // GTPv1 with multiple chained extension headers
652        let header = vec![
653            0x34, // Version 1, PT=1, E=1
654            message_type::G_PDU,
655            0x00,
656            0x14, // Length
657            0x00,
658            0x00,
659            0x00,
660            0x01, // TEID
661            0x00,
662            0x00,                            // Sequence
663            0x00,                            // N-PDU
664            extension_header_type::UDP_PORT, // First ext header type
665            // Extension header 1: UDP Port (type 0x40)
666            0x01, // Length = 4 bytes
667            0x08,
668            0x68,                                         // UDP port 2152
669            extension_header_type::PDU_SESSION_CONTAINER, // Next ext
670            // Extension header 2: PDU Session Container (type 0x85)
671            0x01, // Length = 4 bytes
672            0x01,
673            0x00,                           // PDU session data
674            extension_header_type::NO_MORE, // End of chain
675        ];
676
677        let result = parser.parse(&header, &context);
678
679        assert!(result.is_ok());
680        assert_eq!(
681            result.get("extension_header_count"),
682            Some(&FieldValue::UInt8(2))
683        );
684
685        if let Some(FieldValue::OwnedString(ext)) = result.get("extension_headers") {
686            assert!(ext.contains("UDP Port"));
687            assert!(ext.contains("PDU Session Container"));
688        } else {
689            panic!("Expected extension_headers field");
690        }
691    }
692
693    // Test 14: Extension header limit (max 16)
694    #[test]
695    fn test_extension_header_max_limit() {
696        let parser = GtpProtocol;
697        let mut context = ParseContext::new(1);
698        context.insert_hint("dst_port", 2152);
699
700        // Build a header with 20 extension headers (exceeds limit of 16)
701        let mut header = vec![
702            0x34, // Version 1, PT=1, E=1
703            message_type::G_PDU,
704            0x00,
705            0x60, // Length (large)
706            0x00,
707            0x00,
708            0x00,
709            0x01, // TEID
710            0x00,
711            0x00,                            // Sequence
712            0x00,                            // N-PDU
713            extension_header_type::UDP_PORT, // First ext header type
714        ];
715
716        // Add 20 extension headers
717        for i in 0..20 {
718            header.push(0x01); // Length = 4 bytes
719            header.push((i & 0xFF) as u8); // Data byte 1
720            header.push(((i >> 8) & 0xFF) as u8); // Data byte 2
721            if i < 19 {
722                header.push(extension_header_type::UDP_PORT); // Chain to next
723            } else {
724                header.push(extension_header_type::NO_MORE); // End
725            }
726        }
727
728        let result = parser.parse(&header, &context);
729
730        assert!(result.is_ok());
731        // Should be capped at MAX_EXTENSION_HEADERS (16)
732        if let Some(FieldValue::UInt8(count)) = result.get("extension_header_count") {
733            assert!(
734                *count <= 16,
735                "Extension header count should be capped at 16"
736            );
737        }
738    }
739
740    // Test 15: GTPv2-C basic parsing
741    #[test]
742    fn test_gtpv2_basic_parsing() {
743        let parser = GtpProtocol;
744        let mut context = ParseContext::new(1);
745        context.insert_hint("dst_port", 2123);
746
747        // GTPv2-C header with TEID present
748        let header = vec![
749            0x48, // Version 2, P=0, T=1
750            0x20, // Create Session Request (type 32)
751            0x00, 0x0C, // Length
752            0xAB, 0xCD, 0xEF, 0x12, // TEID
753            0x00, 0x01, 0x23, // Sequence Number (291)
754            0x00, // Spare
755        ];
756
757        let result = parser.parse(&header, &context);
758
759        assert!(result.is_ok());
760        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(2)));
761        assert_eq!(result.get("teid_present"), Some(&FieldValue::Bool(true)));
762        assert_eq!(result.get("teid"), Some(&FieldValue::UInt32(0xABCDEF12)));
763        assert_eq!(result.get("message_type"), Some(&FieldValue::UInt8(0x20)));
764    }
765
766    // Test 16: GTPv2-C without TEID (T=0)
767    #[test]
768    fn test_gtpv2_no_teid() {
769        let parser = GtpProtocol;
770        let mut context = ParseContext::new(1);
771        context.insert_hint("dst_port", 2123);
772
773        // GTPv2-C header without TEID (T=0)
774        let header = vec![
775            0x40, // Version 2, P=0, T=0
776            0x01, // Echo Request
777            0x00, 0x08, // Length
778            0x00, 0x00, 0x01, // Sequence Number
779            0x00, // Spare
780        ];
781
782        let result = parser.parse(&header, &context);
783
784        assert!(result.is_ok());
785        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(2)));
786        assert_eq!(result.get("teid_present"), Some(&FieldValue::Bool(false)));
787        assert!(result.get("teid").is_none()); // No TEID field
788    }
789
790    // Test 17: GTPv2-C with Piggybacked message (P=1)
791    #[test]
792    fn test_gtpv2_piggyback() {
793        let parser = GtpProtocol;
794        let mut context = ParseContext::new(1);
795        context.insert_hint("dst_port", 2123);
796
797        // GTPv2-C header with Piggyback flag
798        let header = vec![
799            0x58, // Version 2, P=1, T=1
800            0x21, // Create Session Response
801            0x00, 0x0C, // Length
802            0x12, 0x34, 0x56, 0x78, // TEID
803            0x00, 0x00, 0x01, // Sequence Number
804            0x00, // Spare
805        ];
806
807        let result = parser.parse(&header, &context);
808
809        assert!(result.is_ok());
810        assert_eq!(result.get("piggyback"), Some(&FieldValue::Bool(true)));
811    }
812
813    // Test 18: GTPv2-C sequence number (24-bit)
814    #[test]
815    fn test_gtpv2_sequence_number() {
816        let parser = GtpProtocol;
817        let mut context = ParseContext::new(1);
818        context.insert_hint("dst_port", 2123);
819
820        // GTPv2-C with specific sequence number
821        let header = vec![
822            0x48, // Version 2, P=0, T=1
823            0x22, // Modify Bearer Request
824            0x00, 0x0C, // Length
825            0x00, 0x00, 0x00, 0x01, // TEID
826            0xAB, 0xCD, 0xEF, // Sequence Number = 0xABCDEF
827            0x00, // Spare
828        ];
829
830        let result = parser.parse(&header, &context);
831
832        assert!(result.is_ok());
833        // Sequence is stored as UInt32 for GTPv2 (24-bit value)
834        assert_eq!(result.get("sequence"), Some(&FieldValue::UInt32(0xABCDEF)));
835    }
836
837    // Test 19: GTPv1 N-PDU number flag
838    #[test]
839    fn test_gtpv1_npdu_flag() {
840        let parser = GtpProtocol;
841        let mut context = ParseContext::new(1);
842        context.insert_hint("dst_port", 2152);
843
844        // GTPv1 with PN flag set
845        let header = vec![
846            0x31, // Version 1, PT=1, PN=1
847            message_type::G_PDU,
848            0x00,
849            0x08, // Length
850            0x00,
851            0x00,
852            0x00,
853            0x01, // TEID
854            0x00,
855            0x00, // Sequence
856            0x42, // N-PDU Number = 66
857            0x00, // Next extension header type
858        ];
859
860        let result = parser.parse(&header, &context);
861
862        assert!(result.is_ok());
863        assert_eq!(result.get("npdu"), Some(&FieldValue::UInt8(0x42)));
864    }
865
866    // Test 20: Extension header type names
867    #[test]
868    fn test_extension_header_type_names() {
869        // Test the extension_header_type_name function via parsing
870        let parser = GtpProtocol;
871        let mut context = ParseContext::new(1);
872        context.insert_hint("dst_port", 2152);
873
874        let test_cases = [
875            (extension_header_type::RAN_CONTAINER, "RAN Container"),
876            (extension_header_type::NR_RAN_CONTAINER, "NR RAN Container"),
877            (
878                extension_header_type::LONG_PDCP_PDU_NUMBER,
879                "Long PDCP PDU Number",
880            ),
881        ];
882
883        for (ext_type, expected_name) in test_cases {
884            let header = vec![
885                0x34, // Version 1, PT=1, E=1
886                message_type::G_PDU,
887                0x00,
888                0x0C, // Length
889                0x00,
890                0x00,
891                0x00,
892                0x01, // TEID
893                0x00,
894                0x00,     // Sequence
895                0x00,     // N-PDU
896                ext_type, // Extension header type
897                0x01,     // Length = 4 bytes
898                0x00,
899                0x00,                           // Data
900                extension_header_type::NO_MORE, // End
901            ];
902
903            let result = parser.parse(&header, &context);
904            assert!(result.is_ok());
905
906            if let Some(FieldValue::OwnedString(ext)) = result.get("extension_headers") {
907                assert!(
908                    ext.contains(expected_name),
909                    "Expected '{}' in '{}'",
910                    expected_name,
911                    ext
912                );
913            } else {
914                panic!("Expected extension_headers field");
915            }
916        }
917    }
918
919    // Test 21: GTPv2-C message types
920    #[test]
921    fn test_gtpv2_message_types() {
922        let parser = GtpProtocol;
923        let mut context = ParseContext::new(1);
924        context.insert_hint("dst_port", 2123);
925
926        // Common GTPv2-C message types
927        let test_types: [(u8, &str); 6] = [
928            (1, "Echo Request"),
929            (2, "Echo Response"),
930            (32, "Create Session Request"),
931            (33, "Create Session Response"),
932            (34, "Modify Bearer Request"),
933            (35, "Modify Bearer Response"),
934        ];
935
936        for (msg_type, _name) in test_types {
937            let header = vec![
938                0x48, // Version 2, P=0, T=1
939                msg_type, 0x00, 0x0C, // Length
940                0x00, 0x00, 0x00, 0x01, // TEID
941                0x00, 0x00, 0x01, // Sequence
942                0x00, // Spare
943            ];
944
945            let result = parser.parse(&header, &context);
946            assert!(result.is_ok());
947            assert_eq!(
948                result.get("message_type"),
949                Some(&FieldValue::UInt8(msg_type))
950            );
951        }
952    }
953
954    // Test 22: GTPv1 with all optional flags
955    #[test]
956    fn test_gtpv1_all_optional_flags() {
957        let parser = GtpProtocol;
958        let mut context = ParseContext::new(1);
959        context.insert_hint("dst_port", 2152);
960
961        // GTPv1 with E, S, and PN flags all set
962        let header = vec![
963            0x37, // Version 1, PT=1, E=1, S=1, PN=1
964            message_type::G_PDU,
965            0x00,
966            0x10, // Length
967            0x12,
968            0x34,
969            0x56,
970            0x78, // TEID
971            0xAB,
972            0xCD,                            // Sequence Number
973            0x42,                            // N-PDU Number
974            extension_header_type::UDP_PORT, // Next extension header
975            0x01,                            // Ext length = 4 bytes
976            0x08,
977            0x68, // UDP port data
978            extension_header_type::NO_MORE,
979        ];
980
981        let result = parser.parse(&header, &context);
982
983        assert!(result.is_ok());
984        assert_eq!(result.get("teid"), Some(&FieldValue::UInt32(0x12345678)));
985        assert_eq!(result.get("sequence"), Some(&FieldValue::UInt16(0xABCD)));
986        assert_eq!(result.get("npdu"), Some(&FieldValue::UInt8(0x42)));
987        assert_eq!(
988            result.get("extension_header_count"),
989            Some(&FieldValue::UInt8(1))
990        );
991    }
992}