use std::fmt::{self, Formatter};
use zerocopy::byteorder::{BigEndian, U16, U32};
use zerocopy::{FromBytes, IntoBytes, Unaligned};
use crate::packet::{HeaderParser, PacketHeader};
pub const GTPV1_C_PORT: u16 = 2123;
pub const GTPV1_U_PORT: u16 = 2152;
#[inline]
pub fn is_gtpv1_c_port(port: u16) -> bool {
port == GTPV1_C_PORT
}
#[inline]
pub fn is_gtpv1_u_port(port: u16) -> bool {
port == GTPV1_U_PORT
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Gtpv1MessageType {
EchoRequest = 1,
EchoResponse = 2,
VersionNotSupported = 3,
CreatePdpContextRequest = 16,
CreatePdpContextResponse = 17,
UpdatePdpContextRequest = 18,
UpdatePdpContextResponse = 19,
DeletePdpContextRequest = 20,
DeletePdpContextResponse = 21,
SgsnContextRequest = 50,
SgsnContextResponse = 51,
SgsnContextAcknowledge = 52,
SendRoutingInfoRequest = 32,
SendRoutingInfoResponse = 33,
MbmsNotificationRequest = 96,
MbmsNotificationResponse = 97,
MbmsNotificationRejectRequest = 98,
MbmsNotificationRejectResponse = 99,
ErrorIndication = 26,
SupportedExtensionHeadersNotification = 31,
EndMarker = 254,
GPdu = 255,
Unknown = 0,
}
impl From<u8> for Gtpv1MessageType {
fn from(value: u8) -> Self {
match value {
1 => Gtpv1MessageType::EchoRequest,
2 => Gtpv1MessageType::EchoResponse,
3 => Gtpv1MessageType::VersionNotSupported,
16 => Gtpv1MessageType::CreatePdpContextRequest,
17 => Gtpv1MessageType::CreatePdpContextResponse,
18 => Gtpv1MessageType::UpdatePdpContextRequest,
19 => Gtpv1MessageType::UpdatePdpContextResponse,
20 => Gtpv1MessageType::DeletePdpContextRequest,
21 => Gtpv1MessageType::DeletePdpContextResponse,
26 => Gtpv1MessageType::ErrorIndication,
31 => Gtpv1MessageType::SupportedExtensionHeadersNotification,
32 => Gtpv1MessageType::SendRoutingInfoRequest,
33 => Gtpv1MessageType::SendRoutingInfoResponse,
50 => Gtpv1MessageType::SgsnContextRequest,
51 => Gtpv1MessageType::SgsnContextResponse,
52 => Gtpv1MessageType::SgsnContextAcknowledge,
96 => Gtpv1MessageType::MbmsNotificationRequest,
97 => Gtpv1MessageType::MbmsNotificationResponse,
98 => Gtpv1MessageType::MbmsNotificationRejectRequest,
99 => Gtpv1MessageType::MbmsNotificationRejectResponse,
254 => Gtpv1MessageType::EndMarker,
255 => Gtpv1MessageType::GPdu,
_ => Gtpv1MessageType::Unknown,
}
}
}
impl fmt::Display for Gtpv1MessageType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Gtpv1MessageType::EchoRequest => write!(f, "Echo Request"),
Gtpv1MessageType::EchoResponse => write!(f, "Echo Response"),
Gtpv1MessageType::VersionNotSupported => write!(f, "Version Not Supported"),
Gtpv1MessageType::CreatePdpContextRequest => write!(f, "Create PDP Context Request"),
Gtpv1MessageType::CreatePdpContextResponse => write!(f, "Create PDP Context Response"),
Gtpv1MessageType::UpdatePdpContextRequest => write!(f, "Update PDP Context Request"),
Gtpv1MessageType::UpdatePdpContextResponse => write!(f, "Update PDP Context Response"),
Gtpv1MessageType::DeletePdpContextRequest => write!(f, "Delete PDP Context Request"),
Gtpv1MessageType::DeletePdpContextResponse => write!(f, "Delete PDP Context Response"),
Gtpv1MessageType::ErrorIndication => write!(f, "Error Indication"),
Gtpv1MessageType::SupportedExtensionHeadersNotification => {
write!(f, "Supported Extension Headers Notification")
}
Gtpv1MessageType::SendRoutingInfoRequest => write!(f, "Send Routing Info Request"),
Gtpv1MessageType::SendRoutingInfoResponse => write!(f, "Send Routing Info Response"),
Gtpv1MessageType::SgsnContextRequest => write!(f, "SGSN Context Request"),
Gtpv1MessageType::SgsnContextResponse => write!(f, "SGSN Context Response"),
Gtpv1MessageType::SgsnContextAcknowledge => write!(f, "SGSN Context Acknowledge"),
Gtpv1MessageType::MbmsNotificationRequest => write!(f, "MBMS Notification Request"),
Gtpv1MessageType::MbmsNotificationResponse => write!(f, "MBMS Notification Response"),
Gtpv1MessageType::MbmsNotificationRejectRequest => {
write!(f, "MBMS Notification Reject Request")
}
Gtpv1MessageType::MbmsNotificationRejectResponse => {
write!(f, "MBMS Notification Reject Response")
}
Gtpv1MessageType::EndMarker => write!(f, "End Marker"),
Gtpv1MessageType::GPdu => write!(f, "G-PDU"),
Gtpv1MessageType::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Gtpv1ExtensionType {
NoMoreExtensions = 0x00,
MbmsSupportIndication = 0x01,
MsInfoChangeReporting = 0x02,
LongPdcpPduNumber = 0x03,
ServiceClassIndicator = 0x20,
UdpPort = 0x40,
RanContainer = 0x81,
LongPdcpPduNumberExt = 0x82,
XwRanContainer = 0x83,
NrRanContainer = 0x84,
PduSessionContainer = 0x85,
PdcpPduNumber = 0xC0,
Unknown = 0xFF,
}
impl From<u8> for Gtpv1ExtensionType {
fn from(value: u8) -> Self {
match value {
0x00 => Gtpv1ExtensionType::NoMoreExtensions,
0x01 => Gtpv1ExtensionType::MbmsSupportIndication,
0x02 => Gtpv1ExtensionType::MsInfoChangeReporting,
0x03 => Gtpv1ExtensionType::LongPdcpPduNumber,
0x20 => Gtpv1ExtensionType::ServiceClassIndicator,
0x40 => Gtpv1ExtensionType::UdpPort,
0x81 => Gtpv1ExtensionType::RanContainer,
0x82 => Gtpv1ExtensionType::LongPdcpPduNumberExt,
0x83 => Gtpv1ExtensionType::XwRanContainer,
0x84 => Gtpv1ExtensionType::NrRanContainer,
0x85 => Gtpv1ExtensionType::PduSessionContainer,
0xC0 => Gtpv1ExtensionType::PdcpPduNumber,
_ => Gtpv1ExtensionType::Unknown,
}
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct Gtpv1Header {
flags: u8,
message_type: u8,
length: U16<BigEndian>,
teid: U32<BigEndian>,
}
impl Gtpv1Header {
pub const VERSION_MASK: u8 = 0xE0;
pub const VERSION_SHIFT: u8 = 5;
pub const FLAG_PT: u8 = 0x10;
pub const FLAG_RESERVED: u8 = 0x08;
pub const FLAG_E: u8 = 0x04;
pub const FLAG_S: u8 = 0x02;
pub const FLAG_PN: u8 = 0x01;
pub const VERSION_1: u8 = 1;
pub const MIN_HEADER_LEN: usize = 8;
pub const OPTIONAL_HEADER_LEN: usize = 12;
#[allow(unused)]
const NAME: &'static str = "Gtpv1Header";
#[inline]
pub fn flags(&self) -> u8 {
self.flags
}
#[inline]
pub fn version(&self) -> u8 {
(self.flags & Self::VERSION_MASK) >> Self::VERSION_SHIFT
}
#[inline]
pub fn is_gtp(&self) -> bool {
self.flags & Self::FLAG_PT != 0
}
#[inline]
pub fn is_gtp_prime(&self) -> bool {
!self.is_gtp()
}
#[inline]
pub fn has_extension(&self) -> bool {
self.flags & Self::FLAG_E != 0
}
#[inline]
pub fn has_sequence(&self) -> bool {
self.flags & Self::FLAG_S != 0
}
#[inline]
pub fn has_npdu(&self) -> bool {
self.flags & Self::FLAG_PN != 0
}
#[inline]
pub fn has_optional_fields(&self) -> bool {
self.flags & (Self::FLAG_E | Self::FLAG_S | Self::FLAG_PN) != 0
}
#[inline]
pub fn message_type(&self) -> u8 {
self.message_type
}
#[inline]
pub fn message_type_enum(&self) -> Gtpv1MessageType {
self.message_type.into()
}
#[inline]
pub fn is_gpdu(&self) -> bool {
self.message_type == 0xFF
}
#[inline]
pub fn is_echo_request(&self) -> bool {
self.message_type == 1
}
#[inline]
pub fn is_echo_response(&self) -> bool {
self.message_type == 2
}
#[inline]
pub fn is_control_plane(&self) -> bool {
!self.is_gpdu()
}
#[inline]
pub fn is_user_plane(&self) -> bool {
self.is_gpdu()
}
#[inline]
pub fn length(&self) -> u16 {
self.length.get()
}
#[inline]
pub fn teid(&self) -> u32 {
self.teid.get()
}
#[inline]
pub fn header_length(&self) -> usize {
if self.has_optional_fields() {
Self::OPTIONAL_HEADER_LEN
} else {
Self::MIN_HEADER_LEN
}
}
#[inline]
fn is_valid(&self) -> bool {
if self.version() != Self::VERSION_1 {
return false;
}
if self.flags & Self::FLAG_RESERVED != 0 {
return false;
}
true
}
pub fn flags_string(&self) -> String {
let mut flags = Vec::new();
if self.is_gtp() {
flags.push("PT");
}
if self.has_extension() {
flags.push("E");
}
if self.has_sequence() {
flags.push("S");
}
if self.has_npdu() {
flags.push("PN");
}
if flags.is_empty() {
"none".to_string()
} else {
flags.join(",")
}
}
}
#[derive(Debug, Clone)]
pub struct Gtpv1HeaderOpt<'a> {
pub header: &'a Gtpv1Header,
pub raw_options: &'a [u8],
}
impl<'a> Gtpv1HeaderOpt<'a> {
pub fn sequence_number(&self) -> Option<u16> {
if !self.header.has_optional_fields() {
return None;
}
if self.raw_options.len() < 2 {
return None;
}
Some(u16::from_be_bytes([
self.raw_options[0],
self.raw_options[1],
]))
}
pub fn npdu_number(&self) -> Option<u8> {
if !self.header.has_optional_fields() {
return None;
}
if self.raw_options.len() < 3 {
return None;
}
Some(self.raw_options[2])
}
pub fn next_extension_type(&self) -> Option<u8> {
if !self.header.has_optional_fields() {
return None;
}
if self.raw_options.len() < 4 {
return None;
}
Some(self.raw_options[3])
}
pub fn next_extension_type_enum(&self) -> Option<Gtpv1ExtensionType> {
self.next_extension_type().map(|t| t.into())
}
pub fn has_extension_headers(&self) -> bool {
if !self.header.has_extension() {
return false;
}
self.next_extension_type().map(|t| t != 0).unwrap_or(false)
}
pub fn extension_headers(&self) -> Gtpv1ExtensionIter<'a> {
let ext_data = if self.header.has_optional_fields() && self.raw_options.len() >= 4 {
if self.has_extension_headers() {
&[] as &[u8]
} else {
&[] as &[u8]
}
} else {
&[] as &[u8]
};
Gtpv1ExtensionIter {
data: ext_data,
next_type: self.next_extension_type().unwrap_or(0),
}
}
}
impl std::ops::Deref for Gtpv1HeaderOpt<'_> {
type Target = Gtpv1Header;
#[inline]
fn deref(&self) -> &Self::Target {
self.header
}
}
#[derive(Debug, Clone)]
pub struct Gtpv1Extension<'a> {
pub extension_type: Gtpv1ExtensionType,
pub content: &'a [u8],
pub next_type: u8,
}
impl<'a> Gtpv1Extension<'a> {
pub fn total_length(&self) -> usize {
self.content.len() + 2 }
}
pub struct Gtpv1ExtensionIter<'a> {
data: &'a [u8],
next_type: u8,
}
impl<'a> Iterator for Gtpv1ExtensionIter<'a> {
type Item = Gtpv1Extension<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.next_type == 0 || self.data.is_empty() {
return None;
}
if self.data.is_empty() {
return None;
}
let length_units = self.data[0] as usize;
if length_units == 0 {
return None;
}
let total_len = length_units * 4;
if self.data.len() < total_len {
return None;
}
let extension_type: Gtpv1ExtensionType = self.next_type.into();
let content = &self.data[1..total_len - 1];
let next_type = self.data[total_len - 1];
let extension = Gtpv1Extension {
extension_type,
content,
next_type,
};
self.data = &self.data[total_len..];
self.next_type = next_type;
Some(extension)
}
}
impl PacketHeader for Gtpv1Header {
const NAME: &'static str = "Gtpv1Header";
type InnerType = u8;
#[inline]
fn inner_type(&self) -> Self::InnerType {
self.message_type
}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
self.header_length()
}
#[inline]
fn is_valid(&self) -> bool {
self.is_valid()
}
}
impl HeaderParser for Gtpv1Header {
type Output<'a> = Gtpv1HeaderOpt<'a>;
#[inline]
fn into_view<'a>(header: &'a Self, raw_options: &'a [u8]) -> Self::Output<'a> {
Gtpv1HeaderOpt {
header,
raw_options,
}
}
}
impl fmt::Display for Gtpv1Header {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"GTPv1 {} msg={} len={} teid=0x{:08x} flags=[{}]",
if self.is_gtp() { "GTP" } else { "GTP'" },
self.message_type_enum(),
self.length(),
self.teid(),
self.flags_string()
)
}
}
impl fmt::Display for Gtpv1HeaderOpt<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"GTPv1 {} msg={} len={} teid=0x{:08x}",
if self.is_gtp() { "GTP" } else { "GTP'" },
self.message_type_enum(),
self.length(),
self.teid()
)?;
if let Some(seq) = self.sequence_number() {
write!(f, " seq={}", seq)?;
}
if let Some(npdu) = self.npdu_number() {
write!(f, " npdu={}", npdu)?;
}
if let Some(next_ext) = self.next_extension_type() {
if next_ext != 0 {
write!(f, " next_ext=0x{:02x}", next_ext)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gtpv1_header_size() {
assert_eq!(std::mem::size_of::<Gtpv1Header>(), 8);
assert_eq!(Gtpv1Header::FIXED_LEN, 8);
}
#[test]
fn test_gtpv1_basic_header() {
let packet = vec![
0x30, 0xFF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x45, 0x00, 0x00, 0x00,
];
let (header, payload) = Gtpv1Header::from_bytes(&packet).unwrap();
assert_eq!(header.version(), 1);
assert!(header.is_gtp());
assert!(!header.is_gtp_prime());
assert_eq!(header.message_type(), 0xFF);
assert!(header.is_gpdu());
assert!(header.is_user_plane());
assert!(!header.is_control_plane());
assert_eq!(header.length(), 4);
assert_eq!(header.teid(), 1);
assert!(!header.has_optional_fields());
assert_eq!(header.header_length(), 8);
assert_eq!(payload.len(), 4);
}
#[test]
fn test_gtpv1_with_sequence() {
let packet = vec![
0x32, 0xFF, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x42, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
];
let (header, payload) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.has_sequence());
assert!(header.has_optional_fields());
assert_eq!(header.header_length(), 12);
assert_eq!(header.sequence_number(), Some(0x0042));
assert_eq!(header.npdu_number(), Some(0));
assert_eq!(header.next_extension_type(), Some(0));
assert_eq!(payload.len(), 4);
}
#[test]
fn test_gtpv1_with_extension() {
let packet = vec![
0x34, 0xFF, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x85, 0x45, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.has_extension());
assert!(header.has_optional_fields());
assert_eq!(header.next_extension_type(), Some(0x85));
assert!(header.has_extension_headers());
}
#[test]
fn test_gtpv1_with_npdu() {
let packet = vec![
0x31, 0xFF, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x42, 0x00, 0x45, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.has_npdu());
assert!(header.has_optional_fields());
assert_eq!(header.npdu_number(), Some(0x42));
}
#[test]
fn test_gtpv1_echo_request() {
let packet = vec![
0x32, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, ];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.is_echo_request());
assert!(!header.is_gpdu());
assert!(header.is_control_plane());
assert_eq!(header.message_type_enum(), Gtpv1MessageType::EchoRequest);
}
#[test]
fn test_gtpv1_echo_response() {
let packet = vec![
0x32, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0E, 0x01, ];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.is_echo_response());
assert_eq!(header.message_type_enum(), Gtpv1MessageType::EchoResponse);
}
#[test]
fn test_gtpv1_control_plane_message() {
let packet = vec![
0x32, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, ];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.is_control_plane());
assert!(!header.is_user_plane());
assert_eq!(
header.message_type_enum(),
Gtpv1MessageType::CreatePdpContextRequest
);
}
#[test]
fn test_gtpv1_gtp_prime() {
let packet = vec![
0x20, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(!header.is_gtp());
assert!(header.is_gtp_prime());
}
#[test]
fn test_gtpv1_invalid_version() {
let packet = vec![
0x00, 0xFF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
];
let result = Gtpv1Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_gtpv1_invalid_version_2() {
let packet = vec![
0x48, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
];
let result = Gtpv1Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_gtpv1_parsing_too_small() {
let packet = vec![0x30, 0xFF, 0x00, 0x04]; let result = Gtpv1Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_gtpv1_flags_string() {
let packet = vec![
0x37, 0xFF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
let flags = header.flags_string();
assert!(flags.contains("PT"));
assert!(flags.contains("E"));
assert!(flags.contains("S"));
assert!(flags.contains("PN"));
}
#[test]
fn test_gtpv1_display() {
let packet = vec![
0x30, 0xFF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x45, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
let display = format!("{}", *header);
assert!(display.contains("GTPv1"));
assert!(display.contains("G-PDU"));
}
#[test]
fn test_gtpv1_message_types() {
assert_eq!(Gtpv1MessageType::from(1), Gtpv1MessageType::EchoRequest);
assert_eq!(Gtpv1MessageType::from(2), Gtpv1MessageType::EchoResponse);
assert_eq!(Gtpv1MessageType::from(255), Gtpv1MessageType::GPdu);
assert_eq!(Gtpv1MessageType::from(254), Gtpv1MessageType::EndMarker);
assert_eq!(Gtpv1MessageType::from(200), Gtpv1MessageType::Unknown);
}
#[test]
fn test_gtpv1_extension_types() {
assert_eq!(
Gtpv1ExtensionType::from(0x00),
Gtpv1ExtensionType::NoMoreExtensions
);
assert_eq!(
Gtpv1ExtensionType::from(0x85),
Gtpv1ExtensionType::PduSessionContainer
);
assert_eq!(
Gtpv1ExtensionType::from(0xC0),
Gtpv1ExtensionType::PdcpPduNumber
);
assert_eq!(Gtpv1ExtensionType::from(0xAA), Gtpv1ExtensionType::Unknown);
}
#[test]
fn test_gtpv1_ports() {
assert!(is_gtpv1_c_port(2123));
assert!(is_gtpv1_u_port(2152));
assert!(!is_gtpv1_c_port(2152));
assert!(!is_gtpv1_u_port(2123));
}
#[test]
fn test_gtpv1_large_teid() {
let packet = vec![
0x30, 0xFF, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert_eq!(header.teid(), 0xFFFFFFFF);
}
#[test]
fn test_gtpv1_end_marker() {
let packet = vec![
0x30, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
let (header, payload) = Gtpv1Header::from_bytes(&packet).unwrap();
assert_eq!(header.message_type(), 254);
assert_eq!(header.message_type_enum(), Gtpv1MessageType::EndMarker);
assert!(payload.is_empty());
}
#[test]
fn test_gtpv1_error_indication() {
let packet = vec![
0x32, 0x1A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert_eq!(
header.message_type_enum(),
Gtpv1MessageType::ErrorIndication
);
}
#[test]
fn test_gtpv1_header_opt_display() {
let packet = vec![
0x32, 0xFF, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x42, 0x05, 0x85, 0x00, 0x00, 0x00, 0x00,
];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("seq=66"));
assert!(display.contains("npdu=5"));
assert!(display.contains("next_ext=0x85"));
}
#[test]
fn test_gtpv1_all_flags_set() {
let packet = vec![
0x37, 0xFF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x85, ];
let (header, _) = Gtpv1Header::from_bytes(&packet).unwrap();
assert!(header.is_gtp());
assert!(header.has_extension());
assert!(header.has_sequence());
assert!(header.has_npdu());
assert!(header.has_optional_fields());
assert_eq!(header.header_length(), 12);
}
}