use bytes::{Buf, BufMut, BytesMut};
use prost::Message;
use crate::error::SteamError;
#[derive(Debug, Clone)]
pub enum MessageHeader {
Protobuf(ProtobufMessageHeader),
Extended(ExtendedMessageHeader),
Minimal(MinimalMessageHeader),
}
#[derive(Debug, Clone, Default)]
pub struct MinimalMessageHeader {
pub target_job_id: u64,
pub source_job_id: u64,
}
impl MinimalMessageHeader {
pub const SIZE: usize = 16;
pub fn encode(&self, buf: &mut BytesMut) {
buf.put_u64_le(self.target_job_id);
buf.put_u64_le(self.source_job_id);
}
pub fn decode(buf: &mut &[u8]) -> Result<Self, SteamError> {
if buf.len() < Self::SIZE {
return Err(SteamError::ProtocolError("Minimal header too short".into()));
}
let target_job_id = buf.get_u64_le();
let source_job_id = buf.get_u64_le();
Ok(Self { target_job_id, source_job_id })
}
}
#[derive(Debug, Clone, Default)]
pub struct ProtobufMessageHeader {
pub header_length: u32,
pub session_id: i32,
pub steam_id: u64,
pub job_id_source: u64,
pub job_id_target: u64,
pub target_job_name: Option<String>,
pub routing_appid: Option<u32>,
}
#[derive(Clone, PartialEq, prost::Message)]
pub struct CMsgProtoBufHeader {
#[prost(fixed64, optional, tag = "1")]
pub steamid: Option<u64>,
#[prost(int32, optional, tag = "2")]
pub client_sessionid: Option<i32>,
#[prost(uint32, optional, tag = "3")]
pub routing_appid: Option<u32>,
#[prost(fixed64, optional, tag = "10")]
pub jobid_source: Option<u64>,
#[prost(fixed64, optional, tag = "11")]
pub jobid_target: Option<u64>,
#[prost(string, optional, tag = "12")]
pub target_job_name: Option<String>,
#[prost(int32, optional, tag = "13")]
pub eresult: Option<i32>,
#[prost(string, optional, tag = "14")]
pub error_message: Option<String>,
#[prost(uint32, optional, tag = "15")]
pub ip: Option<u32>,
#[prost(uint32, optional, tag = "16")]
pub auth_account_flags: Option<u32>,
#[prost(int32, optional, tag = "17")]
pub transport_error: Option<i32>,
#[prost(uint64, optional, tag = "18")]
pub messageid: Option<u64>,
#[prost(uint32, optional, tag = "19")]
pub publisher_group_id: Option<u32>,
#[prost(uint32, optional, tag = "20")]
pub sysid: Option<u32>,
#[prost(uint64, optional, tag = "21")]
pub trace_tag: Option<u64>,
#[prost(uint32, optional, tag = "22")]
pub token_source: Option<u32>,
#[prost(bool, optional, tag = "23")]
pub admin_spoofing_user: Option<bool>,
#[prost(int32, optional, tag = "24")]
pub seq_num: Option<i32>,
#[prost(uint32, optional, tag = "25")]
pub webapi_key_id: Option<u32>,
#[prost(bool, optional, tag = "26")]
pub is_from_external_source: Option<bool>,
#[prost(uint32, repeated, tag = "27")]
pub forward_to_sysid: Vec<u32>,
#[prost(uint32, optional, tag = "28")]
pub cm_sysid: Option<u32>,
#[prost(bytes = "vec", optional, tag = "29")]
pub ip_v6: Option<Vec<u8>>,
#[prost(uint32, optional, tag = "31")]
pub launcher_type: Option<u32>,
#[prost(uint32, optional, tag = "32")]
pub realm: Option<u32>,
#[prost(int32, optional, tag = "33")]
pub timeout_ms: Option<i32>,
#[prost(string, optional, tag = "34")]
pub debug_source: Option<String>,
#[prost(uint32, optional, tag = "35")]
pub debug_source_string_index: Option<u32>,
#[prost(uint64, optional, tag = "36")]
pub token_id: Option<u64>,
}
impl ProtobufMessageHeader {
pub fn encode(&self, buf: &mut BytesMut) {
let proto_header = CMsgProtoBufHeader {
steamid: if self.steam_id != 0 { Some(self.steam_id) } else { None },
client_sessionid: if self.session_id != 0 { Some(self.session_id) } else { None },
jobid_source: if self.job_id_source != u64::MAX { Some(self.job_id_source) } else { None },
jobid_target: if self.job_id_target != u64::MAX { Some(self.job_id_target) } else { None },
target_job_name: self.target_job_name.clone(),
routing_appid: self.routing_appid,
..Default::default()
};
let header_bytes = proto_header.encode_to_vec();
buf.put_u32_le(header_bytes.len() as u32);
buf.extend_from_slice(&header_bytes);
}
pub fn decode(buf: &mut &[u8]) -> Result<Self, SteamError> {
if buf.len() < 4 {
return Err(SteamError::ProtocolError("Header too short".into()));
}
let header_length = buf.get_u32_le();
if buf.len() < header_length as usize {
return Err(SteamError::ProtocolError("Header length mismatch".into()));
}
let header_bytes = &buf[..header_length as usize];
*buf = &buf[header_length as usize..];
let proto_header = CMsgProtoBufHeader::decode(header_bytes).map_err(|e| SteamError::ProtocolError(format!("Failed to decode header: {}", e)))?;
Ok(Self {
header_length,
session_id: proto_header.client_sessionid.unwrap_or(0),
steam_id: proto_header.steamid.unwrap_or(0),
job_id_source: proto_header.jobid_source.unwrap_or(u64::MAX),
job_id_target: proto_header.jobid_target.unwrap_or(u64::MAX),
target_job_name: proto_header.target_job_name,
routing_appid: proto_header.routing_appid,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct ExtendedMessageHeader {
pub header_size: u8,
pub header_version: u16,
pub target_job_id: u64,
pub source_job_id: u64,
pub header_canary: u8,
pub steam_id: u64,
pub session_id: i32,
}
impl ExtendedMessageHeader {
pub const SIZE: usize = 36;
pub fn encode(&self, buf: &mut BytesMut) {
buf.put_u8(Self::SIZE as u8);
buf.put_u16_le(2); buf.put_u64_le(self.target_job_id);
buf.put_u64_le(self.source_job_id);
buf.put_u8(239); buf.put_u64_le(self.steam_id);
buf.put_i32_le(self.session_id);
}
pub fn decode(buf: &mut &[u8]) -> Result<Self, SteamError> {
if buf.len() < Self::SIZE {
return Err(SteamError::ProtocolError("Extended header too short".into()));
}
let header_size = buf.get_u8();
let header_version = buf.get_u16_le();
let target_job_id = buf.get_u64_le();
let source_job_id = buf.get_u64_le();
let header_canary = buf.get_u8();
let steam_id = buf.get_u64_le();
let session_id = buf.get_i32_le();
Ok(Self { header_size, header_version, target_job_id, source_job_id, header_canary, steam_id, session_id })
}
}