use crate::base::generated::steam_language::EUdpPacketType;
use crate::error::SteamError;
pub const MAX_PAYLOAD: usize = 0x4DC;
fn read_u32(data: &[u8], cursor: &mut usize) -> Result<u32, SteamError> {
let bytes = read_bytes(data, cursor, 4)?;
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
fn read_u16(data: &[u8], cursor: &mut usize) -> Result<u16, SteamError> {
let bytes = read_bytes(data, cursor, 2)?;
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
}
fn read_u8(data: &[u8], cursor: &mut usize) -> Result<u8, SteamError> {
Ok(read_bytes(data, cursor, 1)?[0])
}
fn read_bytes<'a>(
data: &'a [u8],
cursor: &mut usize,
len: usize,
) -> Result<&'a [u8], SteamError> {
let end = cursor
.checked_add(len)
.ok_or_else(|| SteamError::Malformed("UDP header length overflow".to_string()))?;
if end > data.len() {
return Err(SteamError::Malformed(
"UDP packet is too short to contain a header".to_string(),
));
}
let slice = &data[*cursor..end];
*cursor = end;
Ok(slice)
}
fn packet_type_from_u8(value: u8) -> EUdpPacketType {
match value {
0 => EUdpPacketType::Invalid,
1 => EUdpPacketType::ChallengeReq,
2 => EUdpPacketType::Challenge,
3 => EUdpPacketType::Connect,
4 => EUdpPacketType::Accept,
5 => EUdpPacketType::Disconnect,
6 => EUdpPacketType::Data,
7 => EUdpPacketType::Datagram,
8 => EUdpPacketType::Max,
_ => EUdpPacketType::Invalid,
}
}
fn packet_type_to_u8(value: EUdpPacketType) -> u8 {
match value {
EUdpPacketType::Invalid => 0,
EUdpPacketType::ChallengeReq => 1,
EUdpPacketType::Challenge => 2,
EUdpPacketType::Connect => 3,
EUdpPacketType::Accept => 4,
EUdpPacketType::Disconnect => 5,
EUdpPacketType::Data => 6,
EUdpPacketType::Datagram => 7,
EUdpPacketType::Max => 8,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UdpHeader {
pub magic: u32,
pub payload_size: u16,
pub packet_type: EUdpPacketType,
pub flags: u8,
pub source_conn_id: u32,
pub dest_conn_id: u32,
pub seq_this: u32,
pub seq_ack: u32,
pub packets_in_msg: u32,
pub msg_start_seq: u32,
pub msg_size: u32,
}
impl UdpHeader {
pub const MAGIC: u32 = 0x31305356;
pub const SERIALIZED_SIZE: usize = 36;
pub fn new() -> Self {
Self {
magic: UdpHeader::MAGIC,
payload_size: 0,
packet_type: EUdpPacketType::Invalid,
flags: 0,
source_conn_id: 512,
dest_conn_id: 0,
seq_this: 0,
seq_ack: 0,
packets_in_msg: 0,
msg_start_seq: 0,
msg_size: 0,
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(UdpHeader::SERIALIZED_SIZE);
out.extend_from_slice(&self.magic.to_le_bytes());
out.extend_from_slice(&self.payload_size.to_le_bytes());
out.push(packet_type_to_u8(self.packet_type));
out.push(self.flags);
out.extend_from_slice(&self.source_conn_id.to_le_bytes());
out.extend_from_slice(&self.dest_conn_id.to_le_bytes());
out.extend_from_slice(&self.seq_this.to_le_bytes());
out.extend_from_slice(&self.seq_ack.to_le_bytes());
out.extend_from_slice(&self.packets_in_msg.to_le_bytes());
out.extend_from_slice(&self.msg_start_seq.to_le_bytes());
out.extend_from_slice(&self.msg_size.to_le_bytes());
out
}
pub fn deserialize(&mut self, data: &[u8]) -> Result<(), SteamError> {
let mut cursor = 0usize;
self.magic = read_u32(data, &mut cursor)?;
self.payload_size = read_u16(data, &mut cursor)?;
let packet_type = read_u8(data, &mut cursor)?;
self.packet_type = packet_type_from_u8(packet_type);
self.flags = read_u8(data, &mut cursor)?;
self.source_conn_id = read_u32(data, &mut cursor)?;
self.dest_conn_id = read_u32(data, &mut cursor)?;
self.seq_this = read_u32(data, &mut cursor)?;
self.seq_ack = read_u32(data, &mut cursor)?;
self.packets_in_msg = read_u32(data, &mut cursor)?;
self.msg_start_seq = read_u32(data, &mut cursor)?;
self.msg_size = read_u32(data, &mut cursor)?;
Ok(())
}
}
impl Default for UdpHeader {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChallengeData {
pub challenge_value: u32,
pub server_load: u32,
}
impl ChallengeData {
pub const CHALLENGE_MASK: u32 = 0xA426DF2B;
pub fn deserialize(data: &[u8]) -> Result<Self, SteamError> {
if data.len() < 8 {
return Err(SteamError::Malformed(
"UDP challenge payload is shorter than 8 bytes".to_string(),
));
}
Ok(Self {
challenge_value: u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
server_load: u32::from_le_bytes([data[4], data[5], data[6], data[7]]),
})
}
pub fn serialize(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(8);
out.extend_from_slice(&self.challenge_value.to_le_bytes());
out.extend_from_slice(&self.server_load.to_le_bytes());
out
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ConnectData {
pub challenge_value: u32,
}
impl ConnectData {
pub const CHALLENGE_MASK: u32 = ChallengeData::CHALLENGE_MASK;
pub fn deserialize(data: &[u8]) -> Result<Self, SteamError> {
if data.len() < 4 {
return Err(SteamError::Malformed(
"UDP connect payload is shorter than 4 bytes".to_string(),
));
}
Ok(Self {
challenge_value: u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
})
}
pub fn serialize(&self) -> Vec<u8> {
self.challenge_value.to_le_bytes().to_vec()
}
}
#[derive(Clone, Debug)]
pub struct UdpPacket {
pub header: UdpHeader,
payload: Option<Vec<u8>>,
}
impl UdpPacket {
pub fn new(packet_type: EUdpPacketType) -> Self {
let mut header = UdpHeader::new();
header.packet_type = packet_type;
Self {
header,
payload: Some(Vec::new()),
}
}
pub fn with_payload(
packet_type: EUdpPacketType,
payload: Vec<u8>,
) -> Result<Self, SteamError> {
let mut packet = Self::new(packet_type);
packet.set_payload(payload)?;
Ok(packet)
}
pub fn with_payload_slice(
packet_type: EUdpPacketType,
source: &[u8],
offset: usize,
length: usize,
) -> Result<Self, SteamError> {
let mut packet = Self::new(packet_type);
packet.set_payload_slice(source, offset, length)?;
Ok(packet)
}
pub fn from_bytes(data: &[u8]) -> Self {
let mut header = UdpHeader::new();
if header.deserialize(data).is_err() {
return Self {
header,
payload: None,
};
}
if header.magic != UdpHeader::MAGIC {
return Self {
header,
payload: None,
};
}
let length = header.payload_size as usize;
if length > MAX_PAYLOAD {
return Self {
header,
payload: None,
};
}
let mut payload = vec![0u8; length];
let available = data
.len()
.saturating_sub(UdpHeader::SERIALIZED_SIZE)
.min(length);
payload[..available].copy_from_slice(
&data[UdpHeader::SERIALIZED_SIZE..UdpHeader::SERIALIZED_SIZE + available],
);
header.payload_size = length as u16;
header.msg_size = length as u32;
Self {
header,
payload: Some(payload),
}
}
pub fn is_valid(&self) -> bool {
self.header.magic == UdpHeader::MAGIC
&& self.header.payload_size as usize <= MAX_PAYLOAD
&& self.payload.is_some()
}
pub fn payload(&self) -> &[u8] {
self.payload.as_deref().unwrap_or(&[])
}
pub fn set_payload(&mut self, payload: Vec<u8>) -> Result<(), SteamError> {
if payload.len() > MAX_PAYLOAD {
return Err(SteamError::InvalidArgument(
"Payload length exceeds 0x4DC maximum".to_string(),
));
}
self.header.payload_size = payload.len() as u16;
self.header.msg_size = payload.len() as u32;
self.payload = Some(payload);
Ok(())
}
pub fn set_payload_slice(
&mut self,
source: &[u8],
offset: usize,
length: usize,
) -> Result<(), SteamError> {
if length > MAX_PAYLOAD {
return Err(SteamError::InvalidArgument(
"Payload length exceeds 0x4DC maximum".to_string(),
));
}
let end = offset.saturating_add(length).min(source.len());
let mut payload = vec![0u8; length];
if end > offset {
payload[..end - offset].copy_from_slice(&source[offset..end]);
}
self.header.payload_size = length as u16;
self.header.msg_size = length as u32;
self.payload = Some(payload);
Ok(())
}
pub fn get_data(&self) -> Vec<u8> {
let mut out = self.header.serialize();
out.extend_from_slice(self.payload());
out
}
}