use core::fmt;
pub const HEADER_LEN: usize = 8;
pub const PROTOCOL_VERSION: u8 = 0x01;
pub const SDP_PORT: u16 = 15118;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum PayloadType {
ExiEncodedV2gMessage,
Part20Main,
Part20Ac,
Part20Dc,
Part20Acdp,
Part20Wpt,
SdpRequest,
SdpResponse,
SdpWirelessRequest,
SdpWirelessResponse,
ManufacturerSpecific(u16),
}
impl PayloadType {
#[must_use]
pub const fn as_u16(self) -> u16 {
match self {
Self::ExiEncodedV2gMessage => 0x8001,
Self::Part20Main => 0x8002,
Self::Part20Ac => 0x8003,
Self::Part20Dc => 0x8004,
Self::Part20Acdp => 0x8005,
Self::Part20Wpt => 0x8006,
Self::SdpRequest => 0x9000,
Self::SdpResponse => 0x9001,
Self::SdpWirelessRequest => 0x9002,
Self::SdpWirelessResponse => 0x9003,
Self::ManufacturerSpecific(v) => v,
}
}
pub const fn from_u16(value: u16) -> Result<Self, V2gtpError> {
Ok(match value {
0x8001 => Self::ExiEncodedV2gMessage,
0x8002 => Self::Part20Main,
0x8003 => Self::Part20Ac,
0x8004 => Self::Part20Dc,
0x8005 => Self::Part20Acdp,
0x8006 => Self::Part20Wpt,
0x9000 => Self::SdpRequest,
0x9001 => Self::SdpResponse,
0x9002 => Self::SdpWirelessRequest,
0x9003 => Self::SdpWirelessResponse,
0xA000..=0xFFFF => Self::ManufacturerSpecific(value),
_ => return Err(V2gtpError::UnknownPayloadType(value)),
})
}
#[must_use]
pub const fn is_exi(self) -> bool {
matches!(
self,
Self::ExiEncodedV2gMessage
| Self::Part20Main
| Self::Part20Ac
| Self::Part20Dc
| Self::Part20Acdp
| Self::Part20Wpt
)
}
#[must_use]
pub const fn is_sdp_request(self) -> bool {
matches!(self, Self::SdpRequest | Self::SdpWirelessRequest)
}
#[must_use]
pub const fn is_sdp_response(self) -> bool {
matches!(self, Self::SdpResponse | Self::SdpWirelessResponse)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Header {
pub payload_type: PayloadType,
pub payload_len: u32,
}
impl Header {
#[must_use]
pub const fn new(payload_type: PayloadType, payload_len: u32) -> Self {
Self { payload_type, payload_len }
}
#[must_use]
pub const fn to_bytes(self) -> [u8; HEADER_LEN] {
let ty = self.payload_type.as_u16().to_be_bytes();
let len = self.payload_len.to_be_bytes();
[PROTOCOL_VERSION, !PROTOCOL_VERSION, ty[0], ty[1], len[0], len[1], len[2], len[3]]
}
pub fn write_to(self, out: &mut [u8]) -> Result<(), V2gtpError> {
let bytes = self.to_bytes();
out.get_mut(..HEADER_LEN).ok_or(V2gtpError::BufferTooSmall)?.copy_from_slice(&bytes);
Ok(())
}
pub fn decode(input: &[u8]) -> Result<Self, V2gtpError> {
let head: &[u8; HEADER_LEN] =
input.get(..HEADER_LEN).ok_or(V2gtpError::Incomplete)?.try_into().unwrap();
if head[0] != PROTOCOL_VERSION {
return Err(V2gtpError::UnsupportedVersion(head[0]));
}
if head[1] != !PROTOCOL_VERSION {
return Err(V2gtpError::VersionMismatch { version: head[0], inverse: head[1] });
}
let payload_type = PayloadType::from_u16(u16::from_be_bytes([head[2], head[3]]))?;
let payload_len = u32::from_be_bytes([head[4], head[5], head[6], head[7]]);
Ok(Self { payload_type, payload_len })
}
}
pub fn split_frame(
input: &[u8],
max_payload_len: usize,
) -> Result<(Header, &[u8], &[u8]), V2gtpError> {
let header = Header::decode(input)?;
let len = usize::try_from(header.payload_len).map_err(|_| V2gtpError::PayloadTooLarge {
declared: header.payload_len,
limit: max_payload_len,
})?;
if len > max_payload_len {
return Err(V2gtpError::PayloadTooLarge {
declared: header.payload_len,
limit: max_payload_len,
});
}
let end = HEADER_LEN.checked_add(len).ok_or(V2gtpError::Incomplete)?;
if input.len() < end {
return Err(V2gtpError::Incomplete);
}
Ok((header, &input[HEADER_LEN..end], &input[end..]))
}
pub fn write_frame(
payload_type: PayloadType,
payload: &[u8],
out: &mut [u8],
) -> Result<usize, V2gtpError> {
let len = u32::try_from(payload.len()).map_err(|_| V2gtpError::PayloadTooLarge {
declared: u32::MAX,
limit: u32::MAX as usize,
})?;
let total = HEADER_LEN + payload.len();
if out.len() < total {
return Err(V2gtpError::BufferTooSmall);
}
Header::new(payload_type, len).write_to(out)?;
out[HEADER_LEN..total].copy_from_slice(payload);
Ok(total)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum V2gtpError {
Incomplete,
BufferTooSmall,
UnsupportedVersion(u8),
VersionMismatch {
version: u8,
inverse: u8,
},
UnknownPayloadType(u16),
PayloadTooLarge {
declared: u32,
limit: usize,
},
}
impl fmt::Display for V2gtpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Incomplete => f.write_str("incomplete V2GTP frame"),
Self::BufferTooSmall => f.write_str("output buffer too small for V2GTP frame"),
Self::UnsupportedVersion(v) => write!(f, "unsupported V2GTP protocol version {v:#04x}"),
Self::VersionMismatch { version, inverse } => {
write!(f, "V2GTP version {version:#04x} does not match its inverse {inverse:#04x}")
}
Self::UnknownPayloadType(t) => write!(f, "unknown V2GTP payload type {t:#06x}"),
Self::PayloadTooLarge { declared, limit } => {
write!(f, "V2GTP payload of {declared} bytes exceeds the {limit} byte limit")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for V2gtpError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_bytes_match_the_spec_layout() {
let h = Header::new(PayloadType::ExiEncodedV2gMessage, 0x1234);
assert_eq!(h.to_bytes(), [0x01, 0xFE, 0x80, 0x01, 0x00, 0x00, 0x12, 0x34]);
}
#[test]
fn header_roundtrips() {
for ty in [
PayloadType::ExiEncodedV2gMessage,
PayloadType::Part20Main,
PayloadType::Part20Ac,
PayloadType::Part20Dc,
PayloadType::Part20Acdp,
PayloadType::Part20Wpt,
PayloadType::SdpRequest,
PayloadType::SdpResponse,
PayloadType::SdpWirelessRequest,
PayloadType::SdpWirelessResponse,
PayloadType::ManufacturerSpecific(0xA123),
] {
let h = Header::new(ty, 42);
assert_eq!(Header::decode(&h.to_bytes()).unwrap(), h);
assert_eq!(PayloadType::from_u16(ty.as_u16()).unwrap(), ty);
}
}
#[test]
fn a_bad_inverse_version_is_rejected() {
let bytes = [0x01, 0x00, 0x80, 0x01, 0, 0, 0, 0];
assert_eq!(
Header::decode(&bytes),
Err(V2gtpError::VersionMismatch { version: 0x01, inverse: 0x00 })
);
}
#[test]
fn reserved_payload_types_are_rejected() {
assert_eq!(PayloadType::from_u16(0x0000), Err(V2gtpError::UnknownPayloadType(0x0000)));
assert_eq!(PayloadType::from_u16(0x8007), Err(V2gtpError::UnknownPayloadType(0x8007)));
assert_eq!(PayloadType::from_u16(0x9004), Err(V2gtpError::UnknownPayloadType(0x9004)));
}
#[test]
fn a_short_buffer_is_incomplete_not_invalid() {
assert_eq!(Header::decode(&[0x01, 0xFE, 0x80]), Err(V2gtpError::Incomplete));
}
#[test]
fn split_frame_returns_payload_and_remainder() {
let mut buf = [0u8; 32];
let n = write_frame(PayloadType::Part20Main, &[1, 2, 3, 4], &mut buf).unwrap();
assert_eq!(n, HEADER_LEN + 4);
buf[n] = 0xFF;
let (h, payload, rest) = split_frame(&buf[..=n], 1024).unwrap();
assert_eq!(h.payload_type, PayloadType::Part20Main);
assert_eq!(payload, &[1, 2, 3, 4]);
assert_eq!(rest, &[0xFF]);
}
#[test]
fn an_oversized_declared_payload_is_refused_before_reading() {
let bytes = [0x01, 0xFE, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF];
assert_eq!(
split_frame(&bytes, 65536),
Err(V2gtpError::PayloadTooLarge { declared: u32::MAX, limit: 65536 })
);
}
#[test]
fn a_truncated_payload_is_incomplete() {
let bytes = [0x01, 0xFE, 0x80, 0x01, 0x00, 0x00, 0x00, 0x10, 0xAA];
assert_eq!(split_frame(&bytes, 65536), Err(V2gtpError::Incomplete));
}
#[test]
fn payload_type_classification() {
assert!(PayloadType::Part20Dc.is_exi());
assert!(!PayloadType::SdpRequest.is_exi());
assert!(PayloadType::SdpWirelessRequest.is_sdp_request());
assert!(PayloadType::SdpResponse.is_sdp_response());
assert!(!PayloadType::SdpResponse.is_sdp_request());
}
}