use crate::error::SteamError;
use crate::networking::steam3::protocol_type::ProtocolTypes;
use super::server_record::{tcp_udp_protocol_types, ServerRecord};
#[derive(Clone, Debug, Default)]
pub(crate) struct BasicServerListProto {
pub(crate) address: String,
pub(crate) port: u16,
pub(crate) protocols: Option<ProtocolTypes>,
}
impl BasicServerListProto {
pub(crate) fn new(address: String, port: u16, protocols: ProtocolTypes) -> Self {
BasicServerListProto {
address,
port,
protocols: Some(protocols),
}
}
pub(crate) fn address(&self) -> &str {
&self.address
}
pub(crate) fn port(&self) -> u16 {
self.port
}
pub(crate) fn protocols(&self) -> ProtocolTypes {
self.protocols.unwrap_or_else(tcp_udp_protocol_types)
}
pub(crate) fn from_server_record(record: &ServerRecord) -> Self {
BasicServerListProto {
address: record.get_host(),
port: record.get_port(),
protocols: Some(record.protocol_types()),
}
}
pub(crate) async fn to_server_record(&self) -> Result<ServerRecord, SteamError> {
ServerRecord::create_server(&self.address, self.port, self.protocols()).await
}
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
if !self.address.is_empty() {
write_tag(&mut out, 1, WireType::LengthDelimited);
write_varint(&mut out, self.address.len() as u64);
out.extend_from_slice(self.address.as_bytes());
}
if self.port != 0 {
write_tag(&mut out, 2, WireType::Varint);
write_varint(&mut out, u64::from(self.port));
}
if let Some(protocols) = self.protocols {
if protocols.raw() != 0 {
write_tag(&mut out, 3, WireType::Varint);
write_varint(&mut out, u64::from(protocols.raw()));
}
}
out
}
pub(crate) fn decode(data: &[u8]) -> Result<Self, SteamError> {
let mut result = BasicServerListProto::default();
let mut cursor = Cursor::new(data);
while !cursor.is_empty() {
let tag = cursor.read_varint()?;
let field = tag >> 3;
let wire_type = WireType::from_tag(tag)?;
match (field, wire_type) {
(1, WireType::LengthDelimited) => {
let length = cursor.read_varint()? as usize;
let bytes = cursor.read_bytes(length)?;
result.address = String::from_utf8(bytes.to_vec()).map_err(|error| {
SteamError::Malformed(format!("Invalid UTF-8 in server address: {error}"))
})?;
}
(2, WireType::Varint) => {
result.port = cursor.read_varint()? as u16;
}
(3, WireType::Varint) => {
result.protocols = Some(ProtocolTypes(cursor.read_varint()? as u32));
}
_ => cursor.skip(wire_type)?,
}
}
Ok(result)
}
pub(crate) fn encode_list(items: &[BasicServerListProto]) -> Vec<u8> {
let mut out = Vec::new();
for item in items {
let payload = item.encode();
write_tag(&mut out, 1, WireType::LengthDelimited);
write_varint(&mut out, payload.len() as u64);
out.extend_from_slice(&payload);
}
out
}
pub(crate) fn decode_list(data: &[u8]) -> Result<Vec<Self>, SteamError> {
let mut result = Vec::new();
let mut cursor = Cursor::new(data);
while !cursor.is_empty() {
let tag = cursor.read_varint()?;
let field = tag >> 3;
let wire_type = WireType::from_tag(tag)?;
if field == 1 && wire_type == WireType::LengthDelimited {
let length = cursor.read_varint()? as usize;
let bytes = cursor.read_bytes(length)?;
result.push(BasicServerListProto::decode(bytes)?);
} else {
cursor.skip(wire_type)?;
}
}
Ok(result)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum WireType {
Varint,
Fixed64,
LengthDelimited,
StartGroup,
EndGroup,
Fixed32,
}
impl WireType {
fn from_tag(tag: u64) -> Result<Self, SteamError> {
match tag & 0x07 {
0 => Ok(WireType::Varint),
1 => Ok(WireType::Fixed64),
2 => Ok(WireType::LengthDelimited),
3 => Ok(WireType::StartGroup),
4 => Ok(WireType::EndGroup),
5 => Ok(WireType::Fixed32),
other => Err(SteamError::Malformed(format!(
"Unknown protobuf wire type {other}"
))),
}
}
fn skip_size(self) -> Option<usize> {
match self {
WireType::Fixed64 => Some(8),
WireType::Fixed32 => Some(4),
_ => None,
}
}
}
fn write_tag(out: &mut Vec<u8>, field: u64, wire_type: WireType) {
let wire = match wire_type {
WireType::Varint => 0u64,
WireType::Fixed64 => 1,
WireType::LengthDelimited => 2,
WireType::StartGroup => 3,
WireType::EndGroup => 4,
WireType::Fixed32 => 5,
};
write_varint(out, (field << 3) | wire);
}
fn write_varint(out: &mut Vec<u8>, mut value: u64) {
loop {
let byte = (value & 0x7F) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
break;
}
out.push(byte | 0x80);
}
}
struct Cursor<'a> {
data: &'a [u8],
position: usize,
}
impl<'a> Cursor<'a> {
fn new(data: &'a [u8]) -> Self {
Cursor { data, position: 0 }
}
fn is_empty(&self) -> bool {
self.position >= self.data.len()
}
fn read_varint(&mut self) -> Result<u64, SteamError> {
let mut result: u64 = 0;
let mut shift = 0;
loop {
if self.position >= self.data.len() {
return Err(SteamError::Malformed(
"Unexpected end of protobuf data".to_string(),
));
}
let byte = self.data[self.position];
self.position += 1;
result |= u64::from(byte & 0x7F) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift > 63 {
return Err(SteamError::Malformed(
"Protobuf varint is too long".to_string(),
));
}
}
Ok(result)
}
fn read_bytes(&mut self, length: usize) -> Result<&'a [u8], SteamError> {
let end = self.position.checked_add(length).ok_or_else(|| {
SteamError::Malformed("Protobuf length overflow".to_string())
})?;
if end > self.data.len() {
return Err(SteamError::Malformed(
"Unexpected end of protobuf data".to_string(),
));
}
let bytes = &self.data[self.position..end];
self.position = end;
Ok(bytes)
}
fn skip(&mut self, wire_type: WireType) -> Result<(), SteamError> {
match wire_type {
WireType::Varint => {
self.read_varint()?;
}
WireType::LengthDelimited => {
let length = self.read_varint()? as usize;
self.read_bytes(length)?;
}
other => {
let size = other.skip_size().ok_or_else(|| {
SteamError::Malformed(format!(
"Unsupported protobuf wire type in server list: {other:?}"
))
})?;
self.read_bytes(size)?;
}
}
Ok(())
}
}