You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.5 KiB
189 lines
5.5 KiB
// SPDX-License-Identifier: LGPL-3.0-only |
|
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> |
|
|
|
//! Game client packets. |
|
|
|
use std::fmt; |
|
use std::net::SocketAddr; |
|
|
|
use crate::cursor::{Cursor, CursorMut}; |
|
use crate::filter::Filter; |
|
use crate::server::Region; |
|
use crate::Error; |
|
|
|
/// Request a list of server addresses from master servers. |
|
#[derive(Clone, Debug, PartialEq)] |
|
pub struct QueryServers<T> { |
|
/// Servers must be from the `region`. |
|
pub region: Region, |
|
/// Last received server address __(not used)__. |
|
pub last: SocketAddr, |
|
/// Select only servers that match the `filter`. |
|
pub filter: T, |
|
} |
|
|
|
impl QueryServers<()> { |
|
/// Packet header. |
|
pub const HEADER: &'static [u8] = b"1"; |
|
} |
|
|
|
impl<'a, T: 'a> QueryServers<T> |
|
where |
|
T: TryFrom<&'a [u8], Error = Error>, |
|
{ |
|
/// Decode packet from `src`. |
|
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { |
|
let mut cur = Cursor::new(src); |
|
cur.expect(QueryServers::HEADER)?; |
|
let region = cur.get_u8()?.try_into().map_err(|_| Error::InvalidRegion)?; |
|
let last = cur.get_cstr_as_str()?; |
|
let filter = match cur.get_bytes(cur.remaining())? { |
|
// some clients may have bug and filter will be with zero at the end |
|
[x @ .., 0] => x, |
|
x => x, |
|
}; |
|
Ok(Self { |
|
region, |
|
last: last.parse().map_err(|_| Error::InvalidQueryServersLast)?, |
|
filter: T::try_from(filter)?, |
|
}) |
|
} |
|
} |
|
|
|
impl<'a, T: 'a> QueryServers<T> |
|
where |
|
for<'b> &'b T: fmt::Display, |
|
{ |
|
/// Encode packet to `buf`. |
|
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { |
|
Ok(CursorMut::new(buf) |
|
.put_bytes(QueryServers::HEADER)? |
|
.put_u8(self.region as u8)? |
|
.put_as_str(self.last)? |
|
.put_u8(0)? |
|
.put_as_str(&self.filter)? |
|
.put_u8(0)? |
|
.pos()) |
|
} |
|
} |
|
|
|
/// Request an information from a game server. |
|
#[derive(Clone, Debug, PartialEq)] |
|
pub struct GetServerInfo { |
|
/// Client protocol version. |
|
pub protocol: u8, |
|
} |
|
|
|
impl GetServerInfo { |
|
/// Packet header. |
|
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffinfo "; |
|
|
|
/// Creates a new `GetServerInfo`. |
|
pub fn new(protocol: u8) -> Self { |
|
Self { protocol } |
|
} |
|
|
|
/// Decode packet from `src`. |
|
pub fn decode(src: &[u8]) -> Result<Self, Error> { |
|
let mut cur = Cursor::new(src); |
|
cur.expect(Self::HEADER)?; |
|
let protocol = cur |
|
.get_str(cur.remaining())? |
|
.parse() |
|
.map_err(|_| Error::InvalidPacket)?; |
|
Ok(Self { protocol }) |
|
} |
|
|
|
/// Encode packet to `buf`. |
|
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { |
|
Ok(CursorMut::new(buf) |
|
.put_bytes(Self::HEADER)? |
|
.put_as_str(self.protocol)? |
|
.pos()) |
|
} |
|
} |
|
|
|
/// Game client packets. |
|
#[derive(Clone, Debug, PartialEq)] |
|
pub enum Packet<'a> { |
|
/// Request a list of server addresses from master servers. |
|
QueryServers(QueryServers<Filter<'a>>), |
|
/// Request an information from a game server. |
|
GetServerInfo(GetServerInfo), |
|
} |
|
|
|
impl<'a> Packet<'a> { |
|
/// Decode packet from `src`. |
|
pub fn decode(src: &'a [u8]) -> Result<Option<Self>, Error> { |
|
if src.starts_with(QueryServers::HEADER) { |
|
QueryServers::decode(src).map(Self::QueryServers) |
|
} else if src.starts_with(GetServerInfo::HEADER) { |
|
GetServerInfo::decode(src).map(Self::GetServerInfo) |
|
} else { |
|
return Ok(None); |
|
} |
|
.map(Some) |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use super::*; |
|
use crate::filter::{FilterFlags, Version}; |
|
use crate::wrappers::Str; |
|
use std::net::{IpAddr, Ipv4Addr}; |
|
|
|
#[test] |
|
fn query_servers() { |
|
let p = QueryServers { |
|
region: Region::RestOfTheWorld, |
|
last: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), |
|
filter: Filter { |
|
gamedir: Some(Str(&b"valve"[..])), |
|
map: Some(Str(&b"crossfire"[..])), |
|
key: Some(0xdeadbeef), |
|
protocol: Some(49), |
|
clver: Some(Version::new(0, 20)), |
|
flags: FilterFlags::all(), |
|
flags_mask: FilterFlags::all(), |
|
}, |
|
}; |
|
let mut buf = [0; 512]; |
|
let n = p.encode(&mut buf).unwrap(); |
|
assert_eq!(Packet::decode(&buf[..n]), Ok(Some(Packet::QueryServers(p)))); |
|
} |
|
|
|
#[test] |
|
fn query_servers_filter_bug() { |
|
let p = QueryServers { |
|
region: Region::RestOfTheWorld, |
|
last: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), |
|
filter: Filter { |
|
gamedir: None, |
|
protocol: Some(48), |
|
map: None, |
|
key: None, |
|
clver: Some(Version::new(0, 20)), |
|
flags: FilterFlags::empty(), |
|
flags_mask: FilterFlags::NAT, |
|
}, |
|
}; |
|
|
|
let s = b"1\xff0.0.0.0:0\x00\\protocol\\48\\clver\\0.20\\nat\\0\0"; |
|
assert_eq!(Packet::decode(s), Ok(Some(Packet::QueryServers(p.clone())))); |
|
|
|
let s = b"1\xff0.0.0.0:0\x00\\protocol\\48\\clver\\0.20\\nat\\0"; |
|
assert_eq!(Packet::decode(s), Ok(Some(Packet::QueryServers(p)))); |
|
} |
|
|
|
#[test] |
|
fn get_server_info() { |
|
let p = GetServerInfo::new(49); |
|
let mut buf = [0; 512]; |
|
let n = p.encode(&mut buf).unwrap(); |
|
assert_eq!( |
|
Packet::decode(&buf[..n]), |
|
Ok(Some(Packet::GetServerInfo(p))) |
|
); |
|
} |
|
}
|
|
|