Skip to main content

netget/server/gtp/
codec.rs

1//! GTP codec — GTPv1-C/GTPv1-U (3GPP TS 29.060) and GTPv2-C (3GPP TS 29.274).
2//!
3//! Pure: no I/O, no state, no LLM. `mod.rs` owns the sockets and `actions.rs` owns the
4//! model's vocabulary; everything that turns bytes into structure and back lives here so the
5//! literal-byte tests in `tests/server/gtp/codec_test.rs` can pin it without a running server.
6//!
7//! # The two rules this file exists to get right
8//!
9//! 1. **The GTPv1 E/S/PN flags are all-or-nothing.** If *any* of the extension-header,
10//!    sequence-number or N-PDU-number flags is set, **all four optional octets are present**
11//!    (2 sequence, 1 N-PDU, 1 next-extension-header) and each field is only *meaningful* when
12//!    its own flag is set. Implementations routinely assume only the flagged field appears,
13//!    which desynchronises every subsequent byte. See [`GtpV1Header::optional_present`].
14//! 2. **GTPv1 information elements below type 128 are fixed-length (TV), 128 and above are
15//!    variable-length (TLV) with a two-octet length.** A decoder that assumes TLV throughout
16//!    misreads a Cause IE as a length prefix. See [`v1_fixed_ie_len`].
17//!
18//! GTPv2-C has neither problem: its header length is decided by one flag (T) and every IE is
19//! TLIV. It is implemented here because both versions share UDP 2123 and a node has to tell
20//! them apart from the first three bits anyway.
21
22use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
23
24// ===========================================================================
25// Ports
26// ===========================================================================
27
28/// GTP-C signalling port (TS 29.060 §4.1, TS 29.274 §4.1). Above 1023.
29pub const GTPC_PORT: u16 = 2123;
30/// GTP-U user-plane port (TS 29.060 §4.1). Above 1023.
31pub const GTPU_PORT: u16 = 2152;
32
33/// Largest sequence number a GTPv1 header can carry: the field is 16 bits (TS 29.060 §6).
34pub const V1_MAX_SEQUENCE: u32 = 0xFFFF;
35
36/// Largest sequence number a GTPv2 header can carry: the field is 24 bits (TS 29.274 §5.1).
37pub const V2_MAX_SEQUENCE: u32 = 0x00FF_FFFF;
38
39/// Largest T-PDU this server will put inside a G-PDU, in octets.
40///
41/// Two limits apply and the tighter one wins. The GTP-U Length field is 16 bits and covers
42/// the payload plus the four optional octets, so the wire format alone would allow 65531.
43/// But GTP-U rides on UDP, whose payload tops out at 65507, and a G-PDU header carrying a
44/// sequence number is 12 octets — so 65495 is the largest T-PDU that can actually be sent.
45/// Past it `send_to` fails with `EMSGSIZE` and the peer hears nothing at all.
46pub const MAX_GPDU_PAYLOAD_LEN: usize = 65_495;
47
48/// Longest DNS label an APN may carry, in octets (TS 23.003 §9.1, RFC 1035 §2.3.4).
49///
50/// The length prefix is one octet and only its low six bits are a length, so this is a hard
51/// format limit rather than a policy: see [`encode_apn`] for why it is a refusal and not a cut.
52pub const MAX_APN_LABEL_LEN: usize = 63;
53
54// ===========================================================================
55// Message types
56// ===========================================================================
57
58pub const V1_ECHO_REQUEST: u8 = 1;
59pub const V1_ECHO_RESPONSE: u8 = 2;
60pub const V1_VERSION_NOT_SUPPORTED: u8 = 3;
61pub const V1_CREATE_PDP_CONTEXT_REQUEST: u8 = 16;
62pub const V1_CREATE_PDP_CONTEXT_RESPONSE: u8 = 17;
63pub const V1_UPDATE_PDP_CONTEXT_REQUEST: u8 = 18;
64pub const V1_UPDATE_PDP_CONTEXT_RESPONSE: u8 = 19;
65pub const V1_DELETE_PDP_CONTEXT_REQUEST: u8 = 20;
66pub const V1_DELETE_PDP_CONTEXT_RESPONSE: u8 = 21;
67pub const V1_ERROR_INDICATION: u8 = 26;
68pub const V1_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION: u8 = 31;
69pub const V1_END_MARKER: u8 = 254;
70pub const V1_G_PDU: u8 = 255;
71
72pub const V2_ECHO_REQUEST: u8 = 1;
73pub const V2_ECHO_RESPONSE: u8 = 2;
74pub const V2_CREATE_SESSION_REQUEST: u8 = 32;
75pub const V2_CREATE_SESSION_RESPONSE: u8 = 33;
76pub const V2_MODIFY_BEARER_REQUEST: u8 = 34;
77pub const V2_MODIFY_BEARER_RESPONSE: u8 = 35;
78pub const V2_DELETE_SESSION_REQUEST: u8 = 36;
79pub const V2_DELETE_SESSION_RESPONSE: u8 = 37;
80
81/// Human name for a GTPv1 message type, for logs and event data.
82pub fn v1_message_name(t: u8) -> &'static str {
83    match t {
84        V1_ECHO_REQUEST => "Echo Request",
85        V1_ECHO_RESPONSE => "Echo Response",
86        V1_VERSION_NOT_SUPPORTED => "Version Not Supported",
87        V1_CREATE_PDP_CONTEXT_REQUEST => "Create PDP Context Request",
88        V1_CREATE_PDP_CONTEXT_RESPONSE => "Create PDP Context Response",
89        V1_UPDATE_PDP_CONTEXT_REQUEST => "Update PDP Context Request",
90        V1_UPDATE_PDP_CONTEXT_RESPONSE => "Update PDP Context Response",
91        V1_DELETE_PDP_CONTEXT_REQUEST => "Delete PDP Context Request",
92        V1_DELETE_PDP_CONTEXT_RESPONSE => "Delete PDP Context Response",
93        V1_ERROR_INDICATION => "Error Indication",
94        V1_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION => "Supported Extension Headers Notification",
95        V1_END_MARKER => "End Marker",
96        V1_G_PDU => "G-PDU",
97        _ => "Unknown",
98    }
99}
100
101/// Human name for a GTPv2-C message type.
102pub fn v2_message_name(t: u8) -> &'static str {
103    match t {
104        V2_ECHO_REQUEST => "Echo Request",
105        V2_ECHO_RESPONSE => "Echo Response",
106        V2_CREATE_SESSION_REQUEST => "Create Session Request",
107        V2_CREATE_SESSION_RESPONSE => "Create Session Response",
108        V2_MODIFY_BEARER_REQUEST => "Modify Bearer Request",
109        V2_MODIFY_BEARER_RESPONSE => "Modify Bearer Response",
110        V2_DELETE_SESSION_REQUEST => "Delete Session Request",
111        V2_DELETE_SESSION_RESPONSE => "Delete Session Response",
112        _ => "Unknown",
113    }
114}
115
116// ===========================================================================
117// GTPv1 information element types (TS 29.060 §7.7)
118// ===========================================================================
119
120pub const V1_IE_CAUSE: u8 = 1;
121pub const V1_IE_IMSI: u8 = 2;
122pub const V1_IE_REORDERING_REQUIRED: u8 = 8;
123pub const V1_IE_RECOVERY: u8 = 14;
124pub const V1_IE_SELECTION_MODE: u8 = 15;
125pub const V1_IE_TEID_DATA_I: u8 = 16;
126pub const V1_IE_TEID_CONTROL_PLANE: u8 = 17;
127pub const V1_IE_TEARDOWN_IND: u8 = 19;
128pub const V1_IE_NSAPI: u8 = 20;
129pub const V1_IE_CHARGING_ID: u8 = 127;
130pub const V1_IE_END_USER_ADDRESS: u8 = 128;
131pub const V1_IE_ACCESS_POINT_NAME: u8 = 131;
132pub const V1_IE_PROTOCOL_CONFIG_OPTIONS: u8 = 132;
133pub const V1_IE_GSN_ADDRESS: u8 = 133;
134pub const V1_IE_MSISDN: u8 = 134;
135pub const V1_IE_QOS_PROFILE: u8 = 135;
136pub const V1_IE_RAT_TYPE: u8 = 151;
137pub const V1_IE_USER_LOCATION_INFO: u8 = 152;
138pub const V1_IE_MS_TIME_ZONE: u8 = 153;
139pub const V1_IE_IMEISV: u8 = 154;
140
141/// Length in octets of the *value* of a GTPv1 fixed-length (TV) information element.
142///
143/// Types 1..=127 are fixed-length and carry **no length field on the wire**, so a decoder
144/// that does not know the type cannot skip it — [`parse_v1_ies`] therefore stops with
145/// [`DecodeError::UnknownFixedIe`] rather than guessing, which is the only safe answer.
146///
147/// Source: TS 29.060 Table 37 ("Information Elements") and §7.7.
148pub fn v1_fixed_ie_len(ie_type: u8) -> Option<usize> {
149    Some(match ie_type {
150        1 => 1,   // Cause
151        2 => 8,   // IMSI (TBCD)
152        3 => 6,   // Routeing Area Identity
153        4 => 4,   // TLLI
154        5 => 4,   // P-TMSI
155        8 => 1,   // Reordering Required
156        9 => 28,  // Authentication Triplet
157        11 => 1,  // MAP Cause
158        12 => 3,  // P-TMSI Signature
159        13 => 1,  // MS Validated
160        14 => 1,  // Recovery
161        15 => 1,  // Selection Mode
162        16 => 4,  // TEID Data I
163        17 => 4,  // TEID Control Plane
164        18 => 5,  // TEID Data II
165        19 => 1,  // Teardown Ind
166        20 => 1,  // NSAPI
167        21 => 1,  // RANAP Cause
168        22 => 9,  // RAB Context
169        23 => 1,  // Radio Priority SMS
170        24 => 1,  // Radio Priority
171        25 => 2,  // Packet Flow Id
172        26 => 2,  // Charging Characteristics
173        27 => 2,  // Trace Reference
174        28 => 2,  // Trace Type
175        29 => 1,  // MS Not Reachable Reason
176        127 => 4, // Charging ID
177        _ => return None,
178    })
179}
180
181/// Human name for the GTPv1 IEs this server surfaces. Unknown types are reported by number.
182pub fn v1_ie_name(ie_type: u8) -> &'static str {
183    match ie_type {
184        1 => "Cause",
185        2 => "IMSI",
186        3 => "Routeing Area Identity",
187        8 => "Reordering Required",
188        14 => "Recovery",
189        15 => "Selection Mode",
190        16 => "TEID Data I",
191        17 => "TEID Control Plane",
192        19 => "Teardown Ind",
193        20 => "NSAPI",
194        127 => "Charging ID",
195        128 => "End User Address",
196        129 => "MM Context",
197        130 => "PDP Context",
198        131 => "Access Point Name",
199        132 => "Protocol Configuration Options",
200        133 => "GSN Address",
201        134 => "MSISDN",
202        135 => "QoS Profile",
203        148 => "Common Flags",
204        149 => "APN Restriction",
205        151 => "RAT Type",
206        152 => "User Location Information",
207        153 => "MS Time Zone",
208        154 => "IMEI(SV)",
209        251 => "Charging Gateway Address",
210        255 => "Private Extension",
211        _ => "Unknown",
212    }
213}
214
215// ===========================================================================
216// GTPv2-C information element types (TS 29.274 §8)
217// ===========================================================================
218
219pub const V2_IE_IMSI: u8 = 1;
220pub const V2_IE_CAUSE: u8 = 2;
221pub const V2_IE_RECOVERY: u8 = 3;
222pub const V2_IE_APN: u8 = 71;
223pub const V2_IE_EBI: u8 = 73;
224pub const V2_IE_MSISDN: u8 = 76;
225pub const V2_IE_PCO: u8 = 78;
226pub const V2_IE_PAA: u8 = 79;
227pub const V2_IE_RAT_TYPE: u8 = 82;
228pub const V2_IE_FTEID: u8 = 87;
229pub const V2_IE_BEARER_CONTEXT: u8 = 93;
230pub const V2_IE_ULI: u8 = 86;
231pub const V2_IE_APN_RESTRICTION: u8 = 127;
232
233/// Human name for the GTPv2-C IEs this server surfaces.
234pub fn v2_ie_name(ie_type: u8) -> &'static str {
235    match ie_type {
236        1 => "IMSI",
237        2 => "Cause",
238        3 => "Recovery",
239        71 => "APN",
240        72 => "AMBR",
241        73 => "EPS Bearer ID",
242        74 => "IPv4 Configuration Parameters",
243        75 => "MEI",
244        76 => "MSISDN",
245        77 => "Indication",
246        78 => "Protocol Configuration Options",
247        79 => "PDN Address Allocation",
248        80 => "Bearer QoS",
249        82 => "RAT Type",
250        83 => "Serving Network",
251        86 => "User Location Information",
252        87 => "Fully Qualified TEID",
253        93 => "Bearer Context",
254        94 => "Charging ID",
255        99 => "PDN Type",
256        127 => "APN Restriction",
257        255 => "Private Extension",
258        _ => "Unknown",
259    }
260}
261
262// ===========================================================================
263// Decode errors
264// ===========================================================================
265
266#[derive(Clone, Debug, PartialEq, Eq)]
267pub enum DecodeError {
268    /// Fewer octets than the mandatory header needs.
269    TooShort { len: usize, needed: usize },
270    /// The three version bits named a version this server does not speak.
271    BadVersion { version: u8 },
272    /// The header's length field disagrees with the datagram.
273    LengthMismatch { declared: usize, available: usize },
274    /// An extension header's length octet was zero, or ran past the datagram.
275    BadExtensionHeader { ext_type: u8 },
276    /// An information element ran past the end of the message body.
277    TruncatedIe { ie_type: u8 },
278    /// A GTPv1 IE below 128 whose fixed length is not in [`v1_fixed_ie_len`]. It carries no
279    /// length on the wire, so there is no way to skip it; guessing would misread the rest.
280    UnknownFixedIe { ie_type: u8 },
281}
282
283impl std::fmt::Display for DecodeError {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        match self {
286            DecodeError::TooShort { len, needed } => {
287                write!(f, "datagram is {len} octets, needs at least {needed}")
288            }
289            DecodeError::BadVersion { version } => {
290                write!(
291                    f,
292                    "GTP version {version} is not supported (expected 1 or 2)"
293                )
294            }
295            DecodeError::LengthMismatch {
296                declared,
297                available,
298            } => write!(
299                f,
300                "header declares {declared} octets of payload but {available} are present"
301            ),
302            DecodeError::BadExtensionHeader { ext_type } => {
303                write!(f, "malformed extension header of type {ext_type}")
304            }
305            DecodeError::TruncatedIe { ie_type } => {
306                write!(f, "information element {ie_type} runs past the message")
307            }
308            DecodeError::UnknownFixedIe { ie_type } => write!(
309                f,
310                "information element {ie_type} is below 128 (fixed length) but its length is \
311                 not known, so the rest of the message cannot be parsed"
312            ),
313        }
314    }
315}
316
317impl std::error::Error for DecodeError {}
318
319// ===========================================================================
320// Encode errors
321// ===========================================================================
322
323/// A value the encoder refuses to put on the wire.
324///
325/// The encode side faces the model rather than a hostile peer, and the rule is the same one
326/// [`encode_apn`] documents: refuse, never truncate. A shortened value is still a *valid*
327/// value, so nothing downstream can tell that it is not the one that was meant.
328#[derive(Clone, Debug, PartialEq, Eq)]
329pub enum EncodeError {
330    /// A DNS label inside an APN was longer than the one-octet length prefix can describe.
331    ApnLabelTooLong { label: String, len: usize },
332}
333
334impl std::fmt::Display for EncodeError {
335    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336        match self {
337            EncodeError::ApnLabelTooLong { label, len } => write!(
338                f,
339                "APN label '{label}' is {len} octets, over the {MAX_APN_LABEL_LEN}-octet DNS \
340                 label limit (TS 23.003 §9.1)"
341            ),
342        }
343    }
344}
345
346impl std::error::Error for EncodeError {}
347
348/// Which GTP version a datagram announced in its first three bits.
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350pub enum GtpVersion {
351    V1,
352    V2,
353}
354
355impl GtpVersion {
356    pub fn as_number(self) -> u8 {
357        match self {
358            GtpVersion::V1 => 1,
359            GtpVersion::V2 => 2,
360        }
361    }
362
363    /// Width of this version's header sequence-number field, in bits.
364    pub fn sequence_bits(self) -> u8 {
365        match self {
366            GtpVersion::V1 => 16,
367            GtpVersion::V2 => 24,
368        }
369    }
370
371    /// Largest sequence number this version's header can carry.
372    pub fn max_sequence(self) -> u32 {
373        match self {
374            GtpVersion::V1 => V1_MAX_SEQUENCE,
375            GtpVersion::V2 => V2_MAX_SEQUENCE,
376        }
377    }
378}
379
380/// The version bits of a datagram, without decoding anything else.
381///
382/// Returns `None` for an empty datagram. A value other than 1 or 2 is returned as-is so the
383/// caller can answer "Version Not Supported" rather than dropping silently.
384pub fn peek_version(data: &[u8]) -> Option<u8> {
385    data.first().map(|b| b >> 5)
386}
387
388// ===========================================================================
389// GTPv1 header
390// ===========================================================================
391
392/// One GTPv1 extension header (TS 29.060 §6.1).
393///
394/// On the wire an extension header is `length | content | next-type`, where `length` counts
395/// **4-octet units** and covers all three parts. `content` here is those middle octets, so
396/// `content.len() + 2` must be a multiple of 4.
397#[derive(Clone, Debug, PartialEq, Eq)]
398pub struct ExtensionHeader {
399    pub ext_type: u8,
400    pub content: Vec<u8>,
401}
402
403/// GTPv1 header (TS 29.060 §6).
404#[derive(Clone, Debug, PartialEq, Eq)]
405pub struct GtpV1Header {
406    /// PT bit: `true` = GTP (this specification), `false` = GTP' (charging, TS 32.295).
407    pub protocol_type: bool,
408    pub message_type: u8,
409    pub teid: u32,
410    /// Present exactly when the S flag is set.
411    pub sequence: Option<u16>,
412    /// Present exactly when the PN flag is set.
413    pub npdu: Option<u8>,
414    /// Non-empty exactly when the E flag is set.
415    pub extension_headers: Vec<ExtensionHeader>,
416}
417
418impl GtpV1Header {
419    /// A minimal header: no sequence, no N-PDU number, no extension headers.
420    pub fn new(message_type: u8, teid: u32) -> Self {
421        Self {
422            protocol_type: true,
423            message_type,
424            teid,
425            sequence: None,
426            npdu: None,
427            extension_headers: Vec::new(),
428        }
429    }
430
431    /// The same, with a sequence number — which every GTP-C signalling message carries.
432    pub fn with_sequence(message_type: u8, teid: u32, sequence: u16) -> Self {
433        Self {
434            sequence: Some(sequence),
435            ..Self::new(message_type, teid)
436        }
437    }
438
439    /// **The all-or-nothing rule.** The four optional octets are present whenever *any* of
440    /// E, S or PN is set — not only the flagged one.
441    pub fn optional_present(&self) -> bool {
442        self.sequence.is_some() || self.npdu.is_some() || !self.extension_headers.is_empty()
443    }
444
445    /// The flags octet: `version(3) | PT(1) | reserved(1) | E(1) | S(1) | PN(1)`.
446    pub fn flags(&self) -> u8 {
447        let mut flags = 0b0010_0000; // version 1
448        if self.protocol_type {
449            flags |= 0b0001_0000;
450        }
451        if !self.extension_headers.is_empty() {
452            flags |= 0b0000_0100;
453        }
454        if self.sequence.is_some() {
455            flags |= 0b0000_0010;
456        }
457        if self.npdu.is_some() {
458            flags |= 0b0000_0001;
459        }
460        flags
461    }
462}
463
464/// A decoded GTPv1 datagram.
465///
466/// `body` is everything after the header and its extension headers: the information elements
467/// of a control-plane message, or the encapsulated user IP packet of a G-PDU.
468#[derive(Clone, Debug, PartialEq, Eq)]
469pub struct GtpV1Message {
470    pub header: GtpV1Header,
471    pub body: Vec<u8>,
472}
473
474impl GtpV1Message {
475    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
476        if data.len() < 8 {
477            return Err(DecodeError::TooShort {
478                len: data.len(),
479                needed: 8,
480            });
481        }
482        let flags = data[0];
483        let version = flags >> 5;
484        if version != 1 {
485            return Err(DecodeError::BadVersion { version });
486        }
487        let protocol_type = flags & 0b0001_0000 != 0;
488        let e = flags & 0b0000_0100 != 0;
489        let s = flags & 0b0000_0010 != 0;
490        let pn = flags & 0b0000_0001 != 0;
491
492        let message_type = data[1];
493        let declared = u16::from_be_bytes([data[2], data[3]]) as usize;
494        let teid = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
495
496        // The length field counts everything after the mandatory 8 octets, so the optional
497        // block and every extension header are inside it.
498        let available = data.len() - 8;
499        if declared > available {
500            return Err(DecodeError::LengthMismatch {
501                declared,
502                available,
503            });
504        }
505        // Trailing octets past the declared length are not ours (UDP padding, a coalesced
506        // read); cut them off rather than feeding them to the IE parser.
507        let payload = &data[8..8 + declared];
508
509        let mut pos = 0usize;
510        let (sequence, npdu, mut next_ext) = if e || s || pn {
511            // *** The all-or-nothing rule: four octets, whatever the individual flags say. ***
512            if payload.len() < 4 {
513                return Err(DecodeError::TooShort {
514                    len: data.len(),
515                    needed: 12,
516                });
517            }
518            let seq = u16::from_be_bytes([payload[0], payload[1]]);
519            let np = payload[2];
520            let nx = payload[3];
521            pos = 4;
522            (
523                if s { Some(seq) } else { None },
524                if pn { Some(np) } else { None },
525                if e { nx } else { 0 },
526            )
527        } else {
528            (None, None, 0)
529        };
530
531        let mut extension_headers = Vec::new();
532        while next_ext != 0 {
533            if pos >= payload.len() {
534                return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
535            }
536            let units = payload[pos] as usize;
537            if units == 0 {
538                return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
539            }
540            let total = units * 4;
541            if pos + total > payload.len() {
542                return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
543            }
544            let content = payload[pos + 1..pos + total - 1].to_vec();
545            extension_headers.push(ExtensionHeader {
546                ext_type: next_ext,
547                content,
548            });
549            next_ext = payload[pos + total - 1];
550            pos += total;
551        }
552
553        Ok(GtpV1Message {
554            header: GtpV1Header {
555                protocol_type,
556                message_type,
557                teid,
558                sequence,
559                npdu,
560                extension_headers,
561            },
562            body: payload[pos..].to_vec(),
563        })
564    }
565
566    pub fn encode(&self) -> Vec<u8> {
567        let h = &self.header;
568        let mut tail = Vec::new();
569
570        if h.optional_present() {
571            // *** The all-or-nothing rule again, on the encode side. A field whose flag is
572            // clear is still written, as zero. ***
573            tail.extend_from_slice(&h.sequence.unwrap_or(0).to_be_bytes());
574            tail.push(h.npdu.unwrap_or(0));
575            tail.push(h.extension_headers.first().map(|x| x.ext_type).unwrap_or(0));
576        }
577
578        for (i, ext) in h.extension_headers.iter().enumerate() {
579            let total = ext.content.len() + 2;
580            debug_assert_eq!(total % 4, 0, "extension header must be a multiple of 4");
581            tail.push((total / 4) as u8);
582            tail.extend_from_slice(&ext.content);
583            tail.push(
584                h.extension_headers
585                    .get(i + 1)
586                    .map(|x| x.ext_type)
587                    .unwrap_or(0),
588            );
589        }
590
591        tail.extend_from_slice(&self.body);
592
593        let mut out = Vec::with_capacity(8 + tail.len());
594        out.push(h.flags());
595        out.push(h.message_type);
596        out.extend_from_slice(&(tail.len() as u16).to_be_bytes());
597        out.extend_from_slice(&h.teid.to_be_bytes());
598        out.extend_from_slice(&tail);
599        out
600    }
601}
602
603// ===========================================================================
604// GTPv1 information elements
605// ===========================================================================
606
607#[derive(Clone, Debug, PartialEq, Eq)]
608pub struct GtpV1Ie {
609    pub ie_type: u8,
610    pub value: Vec<u8>,
611}
612
613/// Parse a GTPv1-C message body into information elements.
614///
615/// **The fixed-vs-TLV split is the whole point.** Types 1..=127 carry their value immediately
616/// after the type octet with a length known only from the specification; types 128..=255 carry
617/// a two-octet big-endian length.
618pub fn parse_v1_ies(body: &[u8]) -> Result<Vec<GtpV1Ie>, DecodeError> {
619    let mut out = Vec::new();
620    let mut pos = 0usize;
621    while pos < body.len() {
622        let ie_type = body[pos];
623        if ie_type < 128 {
624            let len = v1_fixed_ie_len(ie_type).ok_or(DecodeError::UnknownFixedIe { ie_type })?;
625            if pos + 1 + len > body.len() {
626                return Err(DecodeError::TruncatedIe { ie_type });
627            }
628            out.push(GtpV1Ie {
629                ie_type,
630                value: body[pos + 1..pos + 1 + len].to_vec(),
631            });
632            pos += 1 + len;
633        } else {
634            if pos + 3 > body.len() {
635                return Err(DecodeError::TruncatedIe { ie_type });
636            }
637            let len = u16::from_be_bytes([body[pos + 1], body[pos + 2]]) as usize;
638            if pos + 3 + len > body.len() {
639                return Err(DecodeError::TruncatedIe { ie_type });
640            }
641            out.push(GtpV1Ie {
642                ie_type,
643                value: body[pos + 3..pos + 3 + len].to_vec(),
644            });
645            pos += 3 + len;
646        }
647    }
648    Ok(out)
649}
650
651/// Serialise information elements. Fixed types emit no length; 128+ emit a two-octet length.
652pub fn encode_v1_ies(ies: &[GtpV1Ie]) -> Vec<u8> {
653    let mut out = Vec::new();
654    for ie in ies {
655        out.push(ie.ie_type);
656        if ie.ie_type >= 128 {
657            out.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
658        }
659        out.extend_from_slice(&ie.value);
660    }
661    out
662}
663
664/// First IE of a type, if present.
665pub fn find_v1(ies: &[GtpV1Ie], ie_type: u8) -> Option<&[u8]> {
666    ies.iter()
667        .find(|ie| ie.ie_type == ie_type)
668        .map(|ie| ie.value.as_slice())
669}
670
671/// Every IE of a type, in order.
672pub fn find_all_v1(ies: &[GtpV1Ie], ie_type: u8) -> Vec<&[u8]> {
673    ies.iter()
674        .filter(|ie| ie.ie_type == ie_type)
675        .map(|ie| ie.value.as_slice())
676        .collect()
677}
678
679// ===========================================================================
680// GTPv2-C
681// ===========================================================================
682
683/// GTPv2-C header (TS 29.274 §5.1).
684///
685/// The differences from v1 that matter: version bits are `010`, the TEID is **conditional**
686/// on the T flag rather than always present, and the sequence number is 24 bits.
687#[derive(Clone, Debug, PartialEq, Eq)]
688pub struct GtpV2Header {
689    pub piggyback: bool,
690    pub message_priority: Option<u8>,
691    pub message_type: u8,
692    /// Present exactly when the T flag is set.
693    pub teid: Option<u32>,
694    /// 24-bit sequence number.
695    pub sequence: u32,
696}
697
698impl GtpV2Header {
699    pub fn new(message_type: u8, teid: u32, sequence: u32) -> Self {
700        Self {
701            piggyback: false,
702            message_priority: None,
703            message_type,
704            teid: Some(teid),
705            sequence: sequence & 0x00FF_FFFF,
706        }
707    }
708
709    /// Echo Request/Response never carry a TEID (TS 29.274 §5.1: the T flag is 0).
710    pub fn without_teid(message_type: u8, sequence: u32) -> Self {
711        Self {
712            teid: None,
713            ..Self::new(message_type, 0, sequence)
714        }
715    }
716
717    pub fn flags(&self) -> u8 {
718        let mut flags = 0b0100_0000; // version 2
719        if self.piggyback {
720            flags |= 0b0001_0000;
721        }
722        if self.teid.is_some() {
723            flags |= 0b0000_1000;
724        }
725        if self.message_priority.is_some() {
726            flags |= 0b0000_0100;
727        }
728        flags
729    }
730}
731
732#[derive(Clone, Debug, PartialEq, Eq)]
733pub struct GtpV2Ie {
734    pub ie_type: u8,
735    /// Which occurrence of the type this is, within its grouping (TS 29.274 §6.1.3).
736    pub instance: u8,
737    pub value: Vec<u8>,
738}
739
740impl GtpV2Ie {
741    pub fn new(ie_type: u8, instance: u8, value: Vec<u8>) -> Self {
742        Self {
743            ie_type,
744            instance,
745            value,
746        }
747    }
748}
749
750#[derive(Clone, Debug, PartialEq, Eq)]
751pub struct GtpV2Message {
752    pub header: GtpV2Header,
753    pub ies: Vec<GtpV2Ie>,
754}
755
756impl GtpV2Message {
757    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
758        if data.len() < 4 {
759            return Err(DecodeError::TooShort {
760                len: data.len(),
761                needed: 4,
762            });
763        }
764        let flags = data[0];
765        let version = flags >> 5;
766        if version != 2 {
767            return Err(DecodeError::BadVersion { version });
768        }
769        let piggyback = flags & 0b0001_0000 != 0;
770        let t = flags & 0b0000_1000 != 0;
771        let mp = flags & 0b0000_0100 != 0;
772        let message_type = data[1];
773        let declared = u16::from_be_bytes([data[2], data[3]]) as usize;
774
775        let available = data.len() - 4;
776        if declared > available {
777            return Err(DecodeError::LengthMismatch {
778                declared,
779                available,
780            });
781        }
782        let rest = &data[4..4 + declared];
783
784        // TEID (4, conditional) + sequence (3) + spare/message-priority (1).
785        let fixed = if t { 8 } else { 4 };
786        if rest.len() < fixed {
787            return Err(DecodeError::TooShort {
788                len: data.len(),
789                needed: 4 + fixed,
790            });
791        }
792        let mut pos = 0usize;
793        let teid = if t {
794            let v = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
795            pos = 4;
796            Some(v)
797        } else {
798            None
799        };
800        let sequence =
801            u32::from_be_bytes([0, rest[pos], rest[pos + 1], rest[pos + 2]]) & 0x00FF_FFFF;
802        let last = rest[pos + 3];
803        pos += 4;
804
805        let mut ies = Vec::new();
806        while pos < rest.len() {
807            if pos + 4 > rest.len() {
808                return Err(DecodeError::TruncatedIe { ie_type: rest[pos] });
809            }
810            let ie_type = rest[pos];
811            let len = u16::from_be_bytes([rest[pos + 1], rest[pos + 2]]) as usize;
812            let instance = rest[pos + 3] & 0x0F;
813            if pos + 4 + len > rest.len() {
814                return Err(DecodeError::TruncatedIe { ie_type });
815            }
816            ies.push(GtpV2Ie {
817                ie_type,
818                instance,
819                value: rest[pos + 4..pos + 4 + len].to_vec(),
820            });
821            pos += 4 + len;
822        }
823
824        Ok(GtpV2Message {
825            header: GtpV2Header {
826                piggyback,
827                message_priority: if mp { Some(last >> 4) } else { None },
828                message_type,
829                teid,
830                sequence,
831            },
832            ies,
833        })
834    }
835
836    pub fn encode(&self) -> Vec<u8> {
837        let mut rest = Vec::new();
838        if let Some(teid) = self.header.teid {
839            rest.extend_from_slice(&teid.to_be_bytes());
840        }
841        let seq = self.header.sequence.to_be_bytes();
842        rest.extend_from_slice(&seq[1..4]);
843        rest.push(self.header.message_priority.map(|p| p << 4).unwrap_or(0));
844
845        for ie in &self.ies {
846            rest.push(ie.ie_type);
847            rest.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
848            rest.push(ie.instance & 0x0F);
849            rest.extend_from_slice(&ie.value);
850        }
851
852        let mut out = Vec::with_capacity(4 + rest.len());
853        out.push(self.header.flags());
854        out.push(self.header.message_type);
855        out.extend_from_slice(&(rest.len() as u16).to_be_bytes());
856        out.extend_from_slice(&rest);
857        out
858    }
859
860    pub fn find(&self, ie_type: u8) -> Option<&[u8]> {
861        self.ies
862            .iter()
863            .find(|ie| ie.ie_type == ie_type)
864            .map(|ie| ie.value.as_slice())
865    }
866
867    pub fn find_instance(&self, ie_type: u8, instance: u8) -> Option<&[u8]> {
868        self.ies
869            .iter()
870            .find(|ie| ie.ie_type == ie_type && ie.instance == instance)
871            .map(|ie| ie.value.as_slice())
872    }
873}
874
875/// Parse the inner IEs of a grouped GTPv2 IE (Bearer Context and friends).
876pub fn parse_v2_grouped(value: &[u8]) -> Result<Vec<GtpV2Ie>, DecodeError> {
877    let mut out = Vec::new();
878    let mut pos = 0usize;
879    while pos < value.len() {
880        if pos + 4 > value.len() {
881            return Err(DecodeError::TruncatedIe {
882                ie_type: value[pos],
883            });
884        }
885        let ie_type = value[pos];
886        let len = u16::from_be_bytes([value[pos + 1], value[pos + 2]]) as usize;
887        let instance = value[pos + 3] & 0x0F;
888        if pos + 4 + len > value.len() {
889            return Err(DecodeError::TruncatedIe { ie_type });
890        }
891        out.push(GtpV2Ie {
892            ie_type,
893            instance,
894            value: value[pos + 4..pos + 4 + len].to_vec(),
895        });
896        pos += 4 + len;
897    }
898    Ok(out)
899}
900
901/// Serialise IEs for use as the value of a grouped GTPv2 IE.
902pub fn encode_v2_grouped(ies: &[GtpV2Ie]) -> Vec<u8> {
903    let mut out = Vec::new();
904    for ie in ies {
905        out.push(ie.ie_type);
906        out.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
907        out.push(ie.instance & 0x0F);
908        out.extend_from_slice(&ie.value);
909    }
910    out
911}
912
913// ===========================================================================
914// Subscriber identifiers, APNs and addresses
915// ===========================================================================
916
917/// Encode decimal digits as TBCD (telephony BCD): two digits per octet, **low nibble first**,
918/// odd counts padded with the filler `0xF`. Used for IMSI, MSISDN and IMEI.
919///
920/// Non-digit characters are dropped rather than encoded — an IMSI is digits by definition.
921pub fn encode_tbcd(digits: &str) -> Vec<u8> {
922    let nibbles: Vec<u8> = digits
923        .chars()
924        .filter(|c| c.is_ascii_digit())
925        .map(|c| c as u8 - b'0')
926        .collect();
927    let mut out = Vec::with_capacity(nibbles.len().div_ceil(2));
928    for pair in nibbles.chunks(2) {
929        let low = pair[0];
930        let high = pair.get(1).copied().unwrap_or(0x0F);
931        out.push((high << 4) | low);
932    }
933    out
934}
935
936/// Decode TBCD back to digits, stopping at the `0xF` filler.
937pub fn decode_tbcd(bytes: &[u8]) -> String {
938    let mut out = String::with_capacity(bytes.len() * 2);
939    for b in bytes {
940        for nibble in [b & 0x0F, b >> 4] {
941            if nibble == 0x0F {
942                return out;
943            }
944            if nibble > 9 {
945                // Not a decimal digit; the field is not TBCD after all.
946                return out;
947            }
948            out.push((b'0' + nibble) as char);
949        }
950    }
951    out
952}
953
954/// Encode an APN as DNS-style labels: one length octet per label, no trailing root label
955/// (TS 23.003 §9.1, TS 29.060 §7.7.30).
956///
957/// Refuses a label past [`MAX_APN_LABEL_LEN`] rather than shortening it. Truncating here would
958/// be worse than useless: `a…a` cut to 63 octets is a perfectly well-formed label naming a
959/// *different* access point, so nothing downstream — not the decoder, not the peer, not the
960/// operator reading a log — could tell that the APN it resolved was not the one asked for.
961/// The length prefix is one octet and the limit is a hard format rule, so there is no
962/// downstream recovery either.
963pub fn encode_apn(apn: &str) -> Result<Vec<u8>, EncodeError> {
964    let mut out = Vec::with_capacity(apn.len() + 4);
965    for label in apn.split('.').filter(|l| !l.is_empty()) {
966        let bytes = label.as_bytes();
967        if bytes.len() > MAX_APN_LABEL_LEN {
968            return Err(EncodeError::ApnLabelTooLong {
969                label: crate::utils::truncate_for_log(label, 32),
970                len: bytes.len(),
971            });
972        }
973        out.push(bytes.len() as u8);
974        out.extend_from_slice(bytes);
975    }
976    Ok(out)
977}
978
979/// Decode a labelled APN back to dotted form. Returns `None` if the labels do not tile the
980/// buffer exactly, which means it was not an APN.
981pub fn decode_apn(value: &[u8]) -> Option<String> {
982    let mut parts: Vec<String> = Vec::new();
983    let mut pos = 0usize;
984    while pos < value.len() {
985        let len = value[pos] as usize;
986        if len == 0 || pos + 1 + len > value.len() {
987            return None;
988        }
989        parts.push(String::from_utf8_lossy(&value[pos + 1..pos + 1 + len]).to_string());
990        pos += 1 + len;
991    }
992    if parts.is_empty() {
993        None
994    } else {
995        Some(parts.join("."))
996    }
997}
998
999/// GTPv1 End User Address (IE 128) for an IETF-allocated PDP address.
1000///
1001/// Octet 1 is `1111 0001` — four spare bits set to 1, then PDP Type Organisation 1 (IETF);
1002/// octet 2 is the PDP Type Number (0x21 IPv4, 0x57 IPv6, 0x8D IPv4v6); the address follows.
1003pub fn encode_end_user_address(addr: Option<IpAddr>) -> Vec<u8> {
1004    match addr {
1005        Some(IpAddr::V4(v4)) => {
1006            let mut out = vec![0xF1, 0x21];
1007            out.extend_from_slice(&v4.octets());
1008            out
1009        }
1010        Some(IpAddr::V6(v6)) => {
1011            let mut out = vec![0xF1, 0x57];
1012            out.extend_from_slice(&v6.octets());
1013            out
1014        }
1015        // A dynamic address the network has not chosen yet: type only, no address.
1016        None => vec![0xF1, 0x21],
1017    }
1018}
1019
1020/// Decode a GTPv1 End User Address. Returns `(pdp_type, address)`; the address is `None` for
1021/// the "give me a dynamic one" form a UE sends in a Create PDP Context Request.
1022pub fn decode_end_user_address(value: &[u8]) -> (&'static str, Option<IpAddr>) {
1023    if value.len() < 2 {
1024        return ("unknown", None);
1025    }
1026    let (name, expected) = match value[1] {
1027        0x21 => ("IPv4", 4usize),
1028        0x57 => ("IPv6", 16),
1029        0x8D => ("IPv4v6", 4),
1030        _ => ("unknown", 0),
1031    };
1032    let rest = &value[2..];
1033    if expected == 4 && rest.len() >= 4 {
1034        (
1035            name,
1036            Some(IpAddr::V4(Ipv4Addr::new(
1037                rest[0], rest[1], rest[2], rest[3],
1038            ))),
1039        )
1040    } else if expected == 16 && rest.len() >= 16 {
1041        let mut octets = [0u8; 16];
1042        octets.copy_from_slice(&rest[..16]);
1043        (name, Some(IpAddr::V6(Ipv6Addr::from(octets))))
1044    } else {
1045        (name, None)
1046    }
1047}
1048
1049/// GTPv2-C PDN Address Allocation (IE 79). PDN type 1 = IPv4, 2 = IPv6, 3 = IPv4v6.
1050pub fn encode_paa(addr: IpAddr) -> Vec<u8> {
1051    match addr {
1052        IpAddr::V4(v4) => {
1053            let mut out = vec![1u8];
1054            out.extend_from_slice(&v4.octets());
1055            out
1056        }
1057        IpAddr::V6(v6) => {
1058            // TS 29.274 §8.14: an IPv6 PAA is a prefix length followed by the prefix.
1059            let mut out = vec![2u8, 64];
1060            out.extend_from_slice(&v6.octets());
1061            out
1062        }
1063    }
1064}
1065
1066/// Decode a GTPv2-C PAA. Returns `(pdn_type, address)`.
1067pub fn decode_paa(value: &[u8]) -> (&'static str, Option<IpAddr>) {
1068    match value.first() {
1069        Some(1) if value.len() >= 5 => (
1070            "IPv4",
1071            Some(IpAddr::V4(Ipv4Addr::new(
1072                value[1], value[2], value[3], value[4],
1073            ))),
1074        ),
1075        Some(2) if value.len() >= 18 => {
1076            let mut octets = [0u8; 16];
1077            octets.copy_from_slice(&value[2..18]);
1078            ("IPv6", Some(IpAddr::V6(Ipv6Addr::from(octets))))
1079        }
1080        Some(3) if value.len() >= 22 => {
1081            let mut octets = [0u8; 16];
1082            octets.copy_from_slice(&value[2..18]);
1083            (
1084                "IPv4v6",
1085                Some(IpAddr::V4(Ipv4Addr::new(
1086                    value[18], value[19], value[20], value[21],
1087                ))),
1088            )
1089        }
1090        Some(1) => ("IPv4", None),
1091        Some(2) => ("IPv6", None),
1092        Some(3) => ("IPv4v6", None),
1093        _ => ("unknown", None),
1094    }
1095}
1096
1097/// GTPv2-C Fully Qualified TEID (IE 87), TS 29.274 §8.22.
1098///
1099/// Octet 1 is `V4 | V6 | interface-type(6 bits)`, then the 4-octet TEID/GRE key, then the
1100/// addresses the flags announced.
1101pub fn encode_fteid(interface_type: u8, teid: u32, addr: IpAddr) -> Vec<u8> {
1102    let mut out = Vec::with_capacity(21);
1103    let flag = match addr {
1104        IpAddr::V4(_) => 0x80,
1105        IpAddr::V6(_) => 0x40,
1106    };
1107    out.push(flag | (interface_type & 0x3F));
1108    out.extend_from_slice(&teid.to_be_bytes());
1109    match addr {
1110        IpAddr::V4(v4) => out.extend_from_slice(&v4.octets()),
1111        IpAddr::V6(v6) => out.extend_from_slice(&v6.octets()),
1112    }
1113    out
1114}
1115
1116/// Decode an F-TEID. Returns `(interface_type, teid, address)`.
1117pub fn decode_fteid(value: &[u8]) -> Option<(u8, u32, Option<IpAddr>)> {
1118    if value.len() < 5 {
1119        return None;
1120    }
1121    let interface_type = value[0] & 0x3F;
1122    let teid = u32::from_be_bytes([value[1], value[2], value[3], value[4]]);
1123    let has_v4 = value[0] & 0x80 != 0;
1124    let has_v6 = value[0] & 0x40 != 0;
1125    let addr = if has_v4 && value.len() >= 9 {
1126        Some(IpAddr::V4(Ipv4Addr::new(
1127            value[5], value[6], value[7], value[8],
1128        )))
1129    } else if has_v6 && value.len() >= 21 {
1130        let mut octets = [0u8; 16];
1131        octets.copy_from_slice(&value[5..21]);
1132        Some(IpAddr::V6(Ipv6Addr::from(octets)))
1133    } else {
1134        None
1135    };
1136    Some((interface_type, teid, addr))
1137}
1138
1139/// Protocol Configuration Options carrying DNS server addresses in the network-to-MS
1140/// direction (TS 24.008 §10.5.6.3; GTPv1 IE 132, GTPv2 IE 78).
1141///
1142/// Octet 1 is `1000 0000`: extension bit set, configuration protocol 000 (PPP). Each
1143/// container is a two-octet identifier, a one-octet length and its contents; `0x000D` is
1144/// "DNS Server IPv4 Address" and `0x0003` is "DNS Server IPv6 Address".
1145pub fn encode_pco_dns(servers: &[IpAddr]) -> Vec<u8> {
1146    let mut out = vec![0x80u8];
1147    for server in servers {
1148        match server {
1149            IpAddr::V4(v4) => {
1150                out.extend_from_slice(&[0x00, 0x0D, 0x04]);
1151                out.extend_from_slice(&v4.octets());
1152            }
1153            IpAddr::V6(v6) => {
1154                out.extend_from_slice(&[0x00, 0x03, 0x10]);
1155                out.extend_from_slice(&v6.octets());
1156            }
1157        }
1158    }
1159    out
1160}
1161
1162/// Radio access technology name (TS 29.060 §7.7.50 / TS 29.274 §8.17). The two
1163/// specifications number these differently, so the version has to be passed in.
1164pub fn rat_type_name(version: GtpVersion, value: u8) -> &'static str {
1165    match (version, value) {
1166        (GtpVersion::V1, 1) => "UTRAN",
1167        (GtpVersion::V1, 2) => "GERAN",
1168        (GtpVersion::V1, 3) => "WLAN",
1169        (GtpVersion::V1, 4) => "GAN",
1170        (GtpVersion::V1, 5) => "HSPA Evolution",
1171        (GtpVersion::V1, 6) => "EUTRAN",
1172        (GtpVersion::V2, 1) => "UTRAN",
1173        (GtpVersion::V2, 2) => "GERAN",
1174        (GtpVersion::V2, 3) => "WLAN",
1175        (GtpVersion::V2, 4) => "GAN",
1176        (GtpVersion::V2, 5) => "HSPA Evolution",
1177        (GtpVersion::V2, 6) => "EUTRAN",
1178        (GtpVersion::V2, 8) => "EUTRAN-NB-IoT",
1179        (GtpVersion::V2, 9) => "LTE-M",
1180        (GtpVersion::V2, 10) => "NR",
1181        _ => "unknown",
1182    }
1183}
1184
1185// ===========================================================================
1186// Cause values
1187// ===========================================================================
1188
1189/// A cause the model can name, with its code in each version.
1190///
1191/// Only causes that exist in **both** TS 29.060 Table 38 and TS 29.274 Table 8.4-1 are
1192/// offered, so the same name is answerable whichever version the peer used. The numeric
1193/// escape hatch on the action covers everything else.
1194pub struct CauseName {
1195    pub name: &'static str,
1196    pub v1: u8,
1197    pub v2: u8,
1198    /// True only for the causes that *grant* something. Used by the fail-closed rule, which
1199    /// must never synthesise one of these.
1200    pub accepts: bool,
1201}
1202
1203pub const CAUSES: &[CauseName] = &[
1204    CauseName {
1205        name: "request_accepted",
1206        v1: 128,
1207        v2: 16,
1208        accepts: true,
1209    },
1210    CauseName {
1211        name: "new_pdp_type_due_to_network_preference",
1212        v1: 129,
1213        v2: 18,
1214        accepts: true,
1215    },
1216    CauseName {
1217        name: "new_pdp_type_due_to_single_address_bearer_only",
1218        v1: 130,
1219        v2: 19,
1220        accepts: true,
1221    },
1222    CauseName {
1223        name: "invalid_message_format",
1224        v1: 193,
1225        v2: 65,
1226        accepts: false,
1227    },
1228    CauseName {
1229        name: "imsi_not_known",
1230        v1: 194,
1231        v2: 95,
1232        accepts: false,
1233    },
1234    CauseName {
1235        name: "version_not_supported",
1236        v1: 198,
1237        v2: 66,
1238        accepts: false,
1239    },
1240    CauseName {
1241        name: "no_resources_available",
1242        v1: 199,
1243        v2: 73,
1244        accepts: false,
1245    },
1246    CauseName {
1247        name: "service_not_supported",
1248        v1: 200,
1249        v2: 68,
1250        accepts: false,
1251    },
1252    CauseName {
1253        name: "mandatory_ie_incorrect",
1254        v1: 201,
1255        v2: 69,
1256        accepts: false,
1257    },
1258    CauseName {
1259        name: "mandatory_ie_missing",
1260        v1: 202,
1261        v2: 70,
1262        accepts: false,
1263    },
1264    CauseName {
1265        name: "system_failure",
1266        v1: 204,
1267        v2: 72,
1268        accepts: false,
1269    },
1270    CauseName {
1271        name: "user_authentication_failed",
1272        v1: 209,
1273        v2: 91,
1274        accepts: false,
1275    },
1276    CauseName {
1277        name: "context_not_found",
1278        v1: 210,
1279        v2: 64,
1280        accepts: false,
1281    },
1282    CauseName {
1283        name: "all_dynamic_addresses_are_occupied",
1284        v1: 211,
1285        v2: 83,
1286        accepts: false,
1287    },
1288    CauseName {
1289        name: "no_memory_available",
1290        v1: 212,
1291        v2: 90,
1292        accepts: false,
1293    },
1294    CauseName {
1295        name: "missing_or_unknown_apn",
1296        v1: 219,
1297        v2: 77,
1298        accepts: false,
1299    },
1300    CauseName {
1301        name: "apn_access_denied_no_subscription",
1302        v1: 222,
1303        v2: 92,
1304        accepts: false,
1305    },
1306];
1307
1308/// Look a cause up by name, tolerating the spellings a model actually produces:
1309/// `"Request accepted"`, `"REQUEST_ACCEPTED"`, `"request-accepted"`.
1310pub fn cause_by_name(name: &str) -> Option<&'static CauseName> {
1311    let normalised: String = name
1312        .chars()
1313        .filter_map(|c| {
1314            if c.is_ascii_alphanumeric() {
1315                Some(c.to_ascii_lowercase())
1316            } else if c == ' ' || c == '-' || c == '_' {
1317                Some('_')
1318            } else {
1319                None
1320            }
1321        })
1322        .collect();
1323    CAUSES.iter().find(|c| c.name == normalised)
1324}
1325
1326/// Every cause name the model may use, for error messages and documentation.
1327pub fn cause_names() -> Vec<&'static str> {
1328    CAUSES.iter().map(|c| c.name).collect()
1329}
1330
1331/// Does this numeric cause value grant something?
1332///
1333/// TS 29.060 §7.7.1 splits the v1 space into request (0-63), accept (128-191) and reject
1334/// (192-255); TS 29.274 §8.4 splits the v2 space into request (0-15), accept (16-63) and
1335/// reject (64-255).
1336pub fn cause_accepts(version: GtpVersion, value: u8) -> bool {
1337    match version {
1338        GtpVersion::V1 => (128..=191).contains(&value),
1339        GtpVersion::V2 => (16..=63).contains(&value),
1340    }
1341}
1342
1343// ===========================================================================
1344// Inner IP header of a G-PDU
1345// ===========================================================================
1346
1347/// The decoded header of the user packet a G-PDU carries.
1348///
1349/// Structured deliberately: the root `CLAUDE.md` rule is that a model cannot reliably parse
1350/// bytes, so it is handed addresses, a protocol name and ports, never a blob to decode.
1351#[derive(Clone, Debug, PartialEq, Eq)]
1352pub struct InnerIp {
1353    pub version: u8,
1354    pub source: IpAddr,
1355    pub destination: IpAddr,
1356    pub protocol: u8,
1357    pub protocol_name: &'static str,
1358    /// TTL for IPv4, Hop Limit for IPv6.
1359    pub ttl: u8,
1360    /// Total length for IPv4 (header included), payload length for IPv6.
1361    pub length: u16,
1362    pub source_port: Option<u16>,
1363    pub destination_port: Option<u16>,
1364    /// Offset of the transport payload within the inner packet, when it could be found.
1365    pub payload_offset: Option<usize>,
1366}
1367
1368pub fn ip_protocol_name(protocol: u8) -> &'static str {
1369    match protocol {
1370        1 => "ICMP",
1371        2 => "IGMP",
1372        6 => "TCP",
1373        17 => "UDP",
1374        41 => "IPv6",
1375        47 => "GRE",
1376        50 => "ESP",
1377        51 => "AH",
1378        58 => "ICMPv6",
1379        89 => "OSPF",
1380        132 => "SCTP",
1381        _ => "unknown",
1382    }
1383}
1384
1385/// Decode the header of an encapsulated user IP packet. `None` when it is not IP at all.
1386pub fn decode_inner_ip(packet: &[u8]) -> Option<InnerIp> {
1387    let first = *packet.first()?;
1388    match first >> 4 {
1389        4 => {
1390            if packet.len() < 20 {
1391                return None;
1392            }
1393            let ihl = (first & 0x0F) as usize * 4;
1394            if ihl < 20 {
1395                return None;
1396            }
1397            let protocol = packet[9];
1398            let source = IpAddr::V4(Ipv4Addr::new(
1399                packet[12], packet[13], packet[14], packet[15],
1400            ));
1401            let destination = IpAddr::V4(Ipv4Addr::new(
1402                packet[16], packet[17], packet[18], packet[19],
1403            ));
1404            let (source_port, destination_port, payload_offset) =
1405                transport_ports(packet, ihl, protocol);
1406            Some(InnerIp {
1407                version: 4,
1408                source,
1409                destination,
1410                protocol,
1411                protocol_name: ip_protocol_name(protocol),
1412                ttl: packet[8],
1413                length: u16::from_be_bytes([packet[2], packet[3]]),
1414                source_port,
1415                destination_port,
1416                payload_offset,
1417            })
1418        }
1419        6 => {
1420            if packet.len() < 40 {
1421                return None;
1422            }
1423            let protocol = packet[6];
1424            let mut s = [0u8; 16];
1425            s.copy_from_slice(&packet[8..24]);
1426            let mut d = [0u8; 16];
1427            d.copy_from_slice(&packet[24..40]);
1428            let (source_port, destination_port, payload_offset) =
1429                transport_ports(packet, 40, protocol);
1430            Some(InnerIp {
1431                version: 6,
1432                source: IpAddr::V6(Ipv6Addr::from(s)),
1433                destination: IpAddr::V6(Ipv6Addr::from(d)),
1434                protocol,
1435                protocol_name: ip_protocol_name(protocol),
1436                ttl: packet[7],
1437                length: u16::from_be_bytes([packet[4], packet[5]]),
1438                source_port,
1439                destination_port,
1440                payload_offset,
1441            })
1442        }
1443        _ => None,
1444    }
1445}
1446
1447fn transport_ports(
1448    packet: &[u8],
1449    offset: usize,
1450    protocol: u8,
1451) -> (Option<u16>, Option<u16>, Option<usize>) {
1452    if !matches!(protocol, 6 | 17 | 132) || packet.len() < offset + 4 {
1453        return (None, None, None);
1454    }
1455    let sport = u16::from_be_bytes([packet[offset], packet[offset + 1]]);
1456    let dport = u16::from_be_bytes([packet[offset + 2], packet[offset + 3]]);
1457    let payload_offset = match protocol {
1458        17 => Some(offset + 8),
1459        6 if packet.len() > offset + 12 => Some(offset + ((packet[offset + 12] >> 4) as usize * 4)),
1460        _ => None,
1461    };
1462    (Some(sport), Some(dport), payload_offset)
1463}