Compare commits

...

18 Commits

Author SHA1 Message Date
Denis Drakhnia
ecab28dfd4 update README.md 2024-11-14 09:12:28 +02:00
Denis Drakhnia
ccaf62f941 all: update repository links 2024-11-14 09:07:52 +02:00
Denis Drakhnia
ed4591559f protocol: fix build with MSRV 2024-11-13 09:17:59 +02:00
Denis Drakhnia
33d6d19bb4 protocol: set autocfg to 1.3 for probe_raw 2024-11-13 08:42:50 +02:00
Denis Drakhnia
fe763c3e60 protocol: move Os to server_info module 2024-09-26 10:35:52 +03:00
Denis Drakhnia
2350ccbe55 protocol: rename internal cursor modules 2024-09-26 10:31:30 +03:00
Denis Drakhnia
00233d3a10 master: fix compile error 2024-09-26 10:30:01 +03:00
Denis Drakhnia
14aaacc453 protocol: generate cfg docs with nightly 2024-09-26 10:25:28 +03:00
Denis Drakhnia
cfe44a04de protocol: add net feature 2024-09-26 10:03:54 +03:00
Denis Drakhnia
3145e8bfc3 protocol: add std and alloc features 2024-09-26 09:28:25 +03:00
Denis Drakhnia
da06045f18 protocol: remove thiserror 2024-09-26 08:51:48 +03:00
Denis Drakhnia
7c53bc40e3 protocol: rename to CursorError 2024-09-26 08:40:21 +03:00
Denis Drakhnia
a5f2affc49 protocol: implement fmt::Write for CursorMut 2024-09-26 08:31:17 +03:00
Denis Drakhnia
dfb702f58d protocol: rename remaining into available 2024-09-26 08:30:35 +03:00
Denis Drakhnia
df3e1b23be master: v0.2.1 2024-09-24 19:14:36 +03:00
Denis Drakhnia
3e91b49e5d master: increate max packet size for IPv6 2024-09-24 19:13:39 +03:00
Denis Drakhnia
17c75e7759 protocol: update docs 2024-09-08 04:49:33 +03:00
Denis Drakhnia
d7e33170d3 query: compile fix 2024-08-29 17:59:32 +03:00
23 changed files with 943 additions and 777 deletions

4
Cargo.lock generated
View File

@ -697,7 +697,7 @@ dependencies = [
[[package]]
name = "xash3d-master"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"ahash",
"bitflags 2.5.0",
@ -717,9 +717,9 @@ dependencies = [
name = "xash3d-protocol"
version = "0.2.0"
dependencies = [
"autocfg",
"bitflags 2.5.0",
"log",
"thiserror",
]
[[package]]

View File

@ -1,3 +1 @@
# xash3d-master
Master server for Half-Life/Xash3D.
Moved to https://github.com/FWGS/xash3d-master

View File

@ -9,7 +9,7 @@ authors = ["Denis Drakhnia <numas13@gmail.com>"]
edition = "2021"
rust-version = "1.56"
homepage = "https://xash.su"
repository = "https://git.mentality.rip/numas13/xash3d-master"
repository = "https://github.com/FWGS/xash3d-master"
[dependencies]
thiserror = "1.0.49"

View File

@ -1,7 +1,7 @@
[package]
name = "xash3d-master"
description = "Master server for games on Xash3D engine"
version = "0.2.0"
version = "0.2.1"
license = "GPL-3.0-only"
keywords = ["xash3d"]
categories = ["command-line-utilities"]
@ -9,7 +9,7 @@ authors = ["Denis Drakhnia <numas13@gmail.com>"]
edition = "2021"
rust-version = "1.56"
homepage = "https://xash.su"
repository = "https://git.mentality.rip/numas13/xash3d-master"
repository = "https://github.com/FWGS/xash3d-master"
[features]
default = ["logtime"]

View File

@ -27,7 +27,7 @@ use xash3d_protocol::{
server,
server::Region,
wrappers::Str,
Error as ProtocolError, ServerInfo,
Error as ProtocolError,
};
use crate::{
@ -35,12 +35,16 @@ use crate::{
stats::Stats,
};
type ServerInfo = xash3d_protocol::ServerInfo<Box<[u8]>>;
pub trait AddrExt: Sized + Eq + Hash + Display + Copy + ToSocketAddrs + ServerAddress {
type Ip: Eq + Hash + Display + Copy + FromStr;
fn extract(addr: SocketAddr) -> Result<Self, SocketAddr>;
fn ip(&self) -> &Self::Ip;
fn wrap(self) -> SocketAddr;
fn mtu() -> usize;
}
impl AddrExt for SocketAddrV4 {
@ -61,6 +65,11 @@ impl AddrExt for SocketAddrV4 {
fn wrap(self) -> SocketAddr {
SocketAddr::V4(self)
}
#[inline(always)]
fn mtu() -> usize {
512
}
}
impl AddrExt for SocketAddrV6 {
@ -81,10 +90,15 @@ impl AddrExt for SocketAddrV6 {
fn wrap(self) -> SocketAddr {
SocketAddr::V6(self)
}
#[inline(always)]
fn mtu() -> usize {
MAX_PACKET_SIZE
}
}
/// The maximum size of UDP packets.
const MAX_PACKET_SIZE: usize = 512;
const MAX_PACKET_SIZE: usize = 1280;
/// How many cleanup calls should be skipped before removing outdated servers.
const SERVER_CLEANUP_MAX: usize = 100;
@ -132,8 +146,8 @@ impl<T> Entry<T> {
}
impl Entry<ServerInfo> {
fn matches<Addr: AddrExt>(&self, addr: Addr, region: Region, filter: &Filter) -> bool {
self.region == region && filter.matches(addr.wrap(), &self.value)
fn matches(&self, region: Region, filter: &Filter) -> bool {
self.region == region && filter.matches(&self.value)
}
}
@ -333,7 +347,7 @@ impl<Addr: AddrExt> MasterServer<Addr> {
pub fn run(&mut self, sig_flag: &AtomicBool) -> Result<(), Error> {
let mut buf = [0; MAX_PACKET_SIZE];
while !sig_flag.load(Ordering::Relaxed) {
let (n, from) = match self.sock.recv_from(&mut buf) {
let (n, from) = match self.sock.recv_from(&mut buf[..Addr::mtu()]) {
Ok(x) => x,
Err(e) => match e.kind() {
io::ErrorKind::Interrupted => break,
@ -434,9 +448,9 @@ impl<Addr: AddrExt> MasterServer<Addr> {
self.filtered_servers_nat.clear();
self.servers
.iter()
.filter(|(addr, info)| {
.filter(|(_addr, info)| {
info.is_valid(now, self.timeout.server)
&& info.matches(**addr, p.region, &p.filter)
&& info.matches(p.region, &p.filter)
})
.for_each(|(addr, info)| {
self.filtered_servers.push(*addr);
@ -468,7 +482,7 @@ impl<Addr: AddrExt> MasterServer<Addr> {
};
trace!("{}: send {:?}", from, p);
let mut buf = [0; MAX_PACKET_SIZE];
let n = p.encode(&mut buf)?;
let n = p.encode(&mut buf[..Addr::mtu()])?;
self.sock.send_to(&buf[..n], from)?;
}
}
@ -663,7 +677,7 @@ impl<Addr: AddrExt> MasterServer<Addr> {
let mut offset = 0;
let mut list = master::QueryServersResponse::new(key);
while offset < servers.len() {
let (n, c) = list.encode(&mut buf, &servers[offset..])?;
let (n, c) = list.encode(&mut buf[..Addr::mtu()], &servers[offset..])?;
offset += c;
self.sock.send_to(&buf[..n], &to)?;
}

View File

@ -1,5 +1,6 @@
use crate::config::StatConfig;
#[allow(dead_code)]
#[derive(Default)]
struct Counters;

View File

@ -9,9 +9,17 @@ authors = ["Denis Drakhnia <numas13@gmail.com>"]
edition = "2021"
rust-version = "1.56"
homepage = "https://xash.su"
repository = "https://git.mentality.rip/numas13/xash3d-master"
repository = "https://github.com/FWGS/xash3d-master"
[features]
default = ["std", "net"]
std = ["alloc"]
alloc = []
net = ["std"]
[dependencies]
thiserror = "1.0.49"
log = "0.4.18"
bitflags = "2.4"
[build-dependencies]
autocfg = "1.3"

7
protocol/build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
let ac = autocfg::new();
println!("cargo:rustc-check-cfg=cfg(has_doc_auto_cfg)");
if ac.probe_raw("#![feature(doc_auto_cfg)]").is_ok() {
println!("cargo:rustc-cfg=has_doc_auto_cfg");
}
}

View File

@ -3,7 +3,8 @@
//! Color codes for strings.
use std::borrow::Cow;
#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, string::String};
/// Color codes `^digit`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -131,6 +132,7 @@ impl<'a> Iterator for ColorIter<'a> {
/// # use xash3d_protocol::color::trim_color;
/// assert_eq!(trim_color("^1no^7 ^2colors^7"), "no colors");
/// ```
#[cfg(feature = "alloc")]
pub fn trim_color(s: &str) -> Cow<'_, str> {
let (_, s) = trim_start_color(s);
if !s.chars().any(|c| c == '^') {
@ -157,6 +159,7 @@ mod tests {
assert_eq!(trim_start_color("^1^2^3foo^2bar"), ("^3", "foo^2bar"));
}
#[cfg(feature = "alloc")]
#[test]
fn trim_colors() {
assert_eq!(trim_color("foo^2bar"), "foobar");

View File

@ -1,518 +1,67 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
use std::io::{self, Write as _};
use std::{fmt, mem, str};
mod read;
mod write;
use thiserror::Error;
use core::fmt;
use super::color;
use super::wrappers::Str;
pub use read::{Cursor, GetKeyValue};
pub use write::{CursorMut, PutKeyValue};
/// The error type for `Cursor` and `CursorMut`.
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Error {
#[derive(Debug, PartialEq, Eq)]
pub enum CursorError {
/// Invalid number.
#[error("Invalid number")]
InvalidNumber,
/// Invalid string.
#[error("Invalid string")]
InvalidString,
/// Invalid boolean.
#[error("Invalid boolean")]
InvalidBool,
/// Invalid table entry.
#[error("Invalid table key")]
InvalidTableKey,
/// Invalid table entry.
#[error("Invalid table entry")]
InvalidTableValue,
/// Table end found.
#[error("Table end")]
TableEnd,
/// Expected data not found.
#[error("Expected data not found")]
Expect,
/// An unexpected data found.
#[error("Unexpected data")]
ExpectEmpty,
/// Buffer size is no enougth to decode or encode a packet.
#[error("Unexpected end of buffer")]
UnexpectedEnd,
}
pub trait GetKeyValue<'a>: Sized {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error>;
}
impl<'a> GetKeyValue<'a> for &'a [u8] {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
cur.get_key_value_raw()
impl fmt::Display for CursorError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Self::InvalidNumber => "Invalid number",
Self::InvalidString => "Invalid string",
Self::InvalidBool => "Invalid boolean",
Self::InvalidTableKey => "Invalid table key",
Self::InvalidTableValue => "Invalid table entry",
Self::TableEnd => "Table end",
Self::Expect => "Expected data not found",
Self::ExpectEmpty => "Unexpected data",
Self::UnexpectedEnd => "Unexpected end of buffer",
};
s.fmt(fmt)
}
}
impl<'a> GetKeyValue<'a> for Str<&'a [u8]> {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
cur.get_key_value_raw().map(Str)
}
}
#[cfg(feature = "std")]
impl std::error::Error for CursorError {}
impl<'a> GetKeyValue<'a> for &'a str {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw).map_err(|_| Error::InvalidString)
}
}
impl<'a> GetKeyValue<'a> for Box<str> {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw)
.map(|s| s.to_owned().into_boxed_str())
.map_err(|_| Error::InvalidString)
}
}
impl<'a> GetKeyValue<'a> for String {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw)
.map(|s| s.to_owned())
.map_err(|_| Error::InvalidString)
}
}
impl<'a> GetKeyValue<'a> for bool {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
match cur.get_key_value_raw()? {
b"0" => Ok(false),
b"1" => Ok(true),
_ => Err(Error::InvalidBool),
}
}
}
macro_rules! impl_get_value {
($($t:ty),+ $(,)?) => {
$(impl<'a> GetKeyValue<'a> for $t {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error> {
let s = cur.get_key_value::<&str>()?;
// HACK: special case for one asshole
let (_, s) = color::trim_start_color(s);
s.parse().map_err(|_| Error::InvalidNumber)
}
})+
};
}
impl_get_value! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
}
// TODO: impl GetKeyValue for f32 and f64
#[derive(Copy, Clone)]
pub struct Cursor<'a> {
buffer: &'a [u8],
}
macro_rules! impl_get {
($($n:ident: $t:ty = $f:ident),+ $(,)?) => (
$(#[inline]
pub fn $n(&mut self) -> Result<$t, Error> {
const N: usize = mem::size_of::<$t>();
self.get_array::<N>().map(<$t>::$f)
})+
);
}
impl<'a> Cursor<'a> {
pub fn new(buffer: &'a [u8]) -> Self {
Self { buffer }
}
pub fn end(self) -> &'a [u8] {
self.buffer
}
pub fn as_slice(&'a self) -> &'a [u8] {
self.buffer
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.buffer.len()
}
#[inline(always)]
pub fn has_remaining(&self) -> bool {
self.remaining() != 0
}
pub fn get_bytes(&mut self, count: usize) -> Result<&'a [u8], Error> {
if count <= self.remaining() {
let (head, tail) = self.buffer.split_at(count);
self.buffer = tail;
Ok(head)
} else {
Err(Error::UnexpectedEnd)
}
}
pub fn advance(&mut self, count: usize) -> Result<(), Error> {
self.get_bytes(count).map(|_| ())
}
pub fn get_array<const N: usize>(&mut self) -> Result<[u8; N], Error> {
self.get_bytes(N).map(|s| {
let mut array = [0; N];
array.copy_from_slice(s);
array
})
}
pub fn get_str(&mut self, n: usize) -> Result<&'a str, Error> {
let mut cur = *self;
let s = cur
.get_bytes(n)
.and_then(|s| str::from_utf8(s).map_err(|_| Error::InvalidString))?;
*self = cur;
Ok(s)
}
pub fn get_cstr(&mut self) -> Result<Str<&'a [u8]>, Error> {
let pos = self
.buffer
.iter()
.position(|&c| c == b'\0')
.ok_or(Error::UnexpectedEnd)?;
let (head, tail) = self.buffer.split_at(pos);
self.buffer = &tail[1..];
Ok(Str(&head[..pos]))
}
pub fn get_cstr_as_str(&mut self) -> Result<&'a str, Error> {
str::from_utf8(&self.get_cstr()?).map_err(|_| Error::InvalidString)
}
#[inline(always)]
pub fn get_u8(&mut self) -> Result<u8, Error> {
self.get_array::<1>().map(|s| s[0])
}
#[inline(always)]
pub fn get_i8(&mut self) -> Result<i8, Error> {
self.get_array::<1>().map(|s| s[0] as i8)
}
impl_get! {
get_u16_le: u16 = from_le_bytes,
get_u32_le: u32 = from_le_bytes,
get_u64_le: u64 = from_le_bytes,
get_i16_le: i16 = from_le_bytes,
get_i32_le: i32 = from_le_bytes,
get_i64_le: i64 = from_le_bytes,
get_f32_le: f32 = from_le_bytes,
get_f64_le: f64 = from_le_bytes,
get_u16_be: u16 = from_be_bytes,
get_u32_be: u32 = from_be_bytes,
get_u64_be: u64 = from_be_bytes,
get_i16_be: i16 = from_be_bytes,
get_i32_be: i32 = from_be_bytes,
get_i64_be: i64 = from_be_bytes,
get_f32_be: f32 = from_be_bytes,
get_f64_be: f64 = from_be_bytes,
get_u16_ne: u16 = from_ne_bytes,
get_u32_ne: u32 = from_ne_bytes,
get_u64_ne: u64 = from_ne_bytes,
get_i16_ne: i16 = from_ne_bytes,
get_i32_ne: i32 = from_ne_bytes,
get_i64_ne: i64 = from_ne_bytes,
get_f32_ne: f32 = from_ne_bytes,
get_f64_ne: f64 = from_ne_bytes,
}
pub fn expect(&mut self, s: &[u8]) -> Result<(), Error> {
if self.buffer.starts_with(s) {
self.advance(s.len())?;
Ok(())
} else {
Err(Error::Expect)
}
}
pub fn expect_empty(&self) -> Result<(), Error> {
if self.has_remaining() {
Err(Error::ExpectEmpty)
} else {
Ok(())
}
}
pub fn take_while<F>(&mut self, mut cond: F) -> Result<&'a [u8], Error>
where
F: FnMut(u8) -> bool,
{
self.buffer
.iter()
.position(|&i| !cond(i))
.ok_or(Error::UnexpectedEnd)
.and_then(|n| self.get_bytes(n))
}
pub fn take_while_or_all<F>(&mut self, cond: F) -> &'a [u8]
where
F: FnMut(u8) -> bool,
{
self.take_while(cond).unwrap_or_else(|_| {
let (head, tail) = self.buffer.split_at(self.buffer.len());
self.buffer = tail;
head
})
}
pub fn get_key_value_raw(&mut self) -> Result<&'a [u8], Error> {
let mut cur = *self;
match cur.get_u8()? {
b'\\' => {
let value = cur.take_while_or_all(|c| c != b'\\' && c != b'\n');
*self = cur;
Ok(value)
}
_ => Err(Error::InvalidTableValue),
}
}
pub fn get_key_value<T: GetKeyValue<'a>>(&mut self) -> Result<T, Error> {
T::get_key_value(self)
}
pub fn skip_key_value<T: GetKeyValue<'a>>(&mut self) -> Result<(), Error> {
T::get_key_value(self).map(|_| ())
}
pub fn get_key_raw(&mut self) -> Result<&'a [u8], Error> {
let mut cur = *self;
match cur.get_u8() {
Ok(b'\\') => {
let value = cur.take_while(|c| c != b'\\' && c != b'\n')?;
*self = cur;
Ok(value)
}
Ok(b'\n') | Err(Error::UnexpectedEnd) => Err(Error::TableEnd),
_ => Err(Error::InvalidTableKey),
}
}
pub fn get_key<T: GetKeyValue<'a>>(&mut self) -> Result<(&'a [u8], T), Error> {
Ok((self.get_key_raw()?, self.get_key_value()?))
}
}
pub trait PutKeyValue {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, Error>;
}
impl<T> PutKeyValue for &T
where
T: PutKeyValue,
{
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, Error> {
(*self).put_key_value(cur)
}
}
impl PutKeyValue for &str {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, Error> {
cur.put_str(self)
}
}
impl PutKeyValue for bool {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, Error> {
cur.put_u8(if *self { b'1' } else { b'0' })
}
}
macro_rules! impl_put_key_value {
($($t:ty),+ $(,)?) => {
$(impl PutKeyValue for $t {
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>, Error> {
cur.put_as_str(self)
}
})+
};
}
impl_put_key_value! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
f32,
f64,
}
pub struct CursorMut<'a> {
buffer: &'a mut [u8],
pos: usize,
}
macro_rules! impl_put {
($($n:ident: $t:ty = $f:ident),+ $(,)?) => (
$(#[inline]
pub fn $n(&mut self, n: $t) -> Result<&mut Self, Error> {
self.put_array(&n.$f())
})+
);
}
impl<'a> CursorMut<'a> {
pub fn new(buffer: &'a mut [u8]) -> Self {
Self { buffer, pos: 0 }
}
pub fn pos(&mut self) -> usize {
self.pos
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.buffer.len() - self.pos
}
pub fn advance<F>(&mut self, count: usize, mut f: F) -> Result<&mut Self, Error>
where
F: FnMut(&mut [u8]),
{
if count <= self.remaining() {
f(&mut self.buffer[self.pos..self.pos + count]);
self.pos += count;
Ok(self)
} else {
Err(Error::UnexpectedEnd)
}
}
pub fn put_bytes(&mut self, s: &[u8]) -> Result<&mut Self, Error> {
self.advance(s.len(), |i| {
i.copy_from_slice(s);
})
}
pub fn put_array<const N: usize>(&mut self, s: &[u8; N]) -> Result<&mut Self, Error> {
self.advance(N, |i| {
i.copy_from_slice(s);
})
}
pub fn put_str(&mut self, s: &str) -> Result<&mut Self, Error> {
self.put_bytes(s.as_bytes())
}
pub fn put_cstr(&mut self, s: &str) -> Result<&mut Self, Error> {
self.put_str(s)?.put_u8(0)
}
#[inline(always)]
pub fn put_u8(&mut self, n: u8) -> Result<&mut Self, Error> {
self.put_array(&[n])
}
#[inline(always)]
pub fn put_i8(&mut self, n: i8) -> Result<&mut Self, Error> {
self.put_u8(n as u8)
}
impl_put! {
put_u16_le: u16 = to_le_bytes,
put_u32_le: u32 = to_le_bytes,
put_u64_le: u64 = to_le_bytes,
put_i16_le: i16 = to_le_bytes,
put_i32_le: i32 = to_le_bytes,
put_i64_le: i64 = to_le_bytes,
put_f32_le: f32 = to_le_bytes,
put_f64_le: f64 = to_le_bytes,
put_u16_be: u16 = to_be_bytes,
put_u32_be: u32 = to_be_bytes,
put_u64_be: u64 = to_be_bytes,
put_i16_be: i16 = to_be_bytes,
put_i32_be: i32 = to_be_bytes,
put_i64_be: i64 = to_be_bytes,
put_f32_be: f32 = to_be_bytes,
put_f64_be: f64 = to_be_bytes,
put_u16_ne: u16 = to_ne_bytes,
put_u32_ne: u32 = to_ne_bytes,
put_u64_ne: u64 = to_ne_bytes,
put_i16_ne: i16 = to_ne_bytes,
put_i32_ne: i32 = to_ne_bytes,
put_i64_ne: i64 = to_ne_bytes,
put_f32_ne: f32 = to_ne_bytes,
put_f64_ne: f64 = to_ne_bytes,
}
pub fn put_as_str<T: fmt::Display>(&mut self, value: T) -> Result<&mut Self, Error> {
let mut cur = io::Cursor::new(&mut self.buffer[self.pos..]);
write!(&mut cur, "{}", value).map_err(|_| Error::UnexpectedEnd)?;
self.pos += cur.position() as usize;
Ok(self)
}
pub fn put_key_value<T: PutKeyValue>(&mut self, value: T) -> Result<&mut Self, Error> {
value.put_key_value(self)
}
pub fn put_key_raw(&mut self, key: &str, value: &[u8]) -> Result<&mut Self, Error> {
self.put_u8(b'\\')?
.put_str(key)?
.put_u8(b'\\')?
.put_bytes(value)
}
pub fn put_key<T: PutKeyValue>(&mut self, key: &str, value: T) -> Result<&mut Self, Error> {
self.put_u8(b'\\')?
.put_str(key)?
.put_u8(b'\\')?
.put_key_value(value)
}
}
pub type Result<T, E = CursorError> = core::result::Result<T, E>;
#[cfg(test)]
mod tests {
use super::*;
use crate::wrappers::Str;
#[test]
fn cursor() -> Result<(), Error> {
fn cursor() -> Result<()> {
let mut buf = [0; 64];
let n = CursorMut::new(&mut buf)
.put_bytes(b"12345678")?
@ -533,13 +82,13 @@ mod tests {
assert_eq!(cur.get_u8(), Ok(0x7f));
assert_eq!(cur.get_i8(), Ok(-128));
assert_eq!(cur.get_u32_le(), Ok(0x44332211));
assert_eq!(cur.get_u8(), Err(Error::UnexpectedEnd));
assert_eq!(cur.get_u8(), Err(CursorError::UnexpectedEnd));
Ok(())
}
#[test]
fn key() -> Result<(), Error> {
fn key() -> Result<()> {
let mut buf = [0; 512];
let n = CursorMut::new(&mut buf)
.put_key("p", 49)?
@ -566,7 +115,7 @@ mod tests {
assert_eq!(cur.get_key(), Ok((&b"gamedir"[..], "valve")));
assert_eq!(cur.get_key(), Ok((&b"password"[..], false)));
assert_eq!(cur.get_key(), Ok((&b"host"[..], "test")));
assert_eq!(cur.get_key::<&[u8]>(), Err(Error::TableEnd));
assert_eq!(cur.get_key::<&[u8]>(), Err(CursorError::TableEnd));
Ok(())
}

311
protocol/src/cursor/read.rs Normal file
View File

@ -0,0 +1,311 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
#![cfg_attr(not(feature = "net"), allow(dead_code))]
use core::{mem, str};
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, boxed::Box, string::String};
use crate::{color, wrappers::Str};
use super::{CursorError, Result};
pub trait GetKeyValue<'a>: Sized {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self>;
}
impl<'a> GetKeyValue<'a> for &'a [u8] {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
cur.get_key_value_raw()
}
}
impl<'a> GetKeyValue<'a> for Str<&'a [u8]> {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
cur.get_key_value_raw().map(Str)
}
}
impl<'a> GetKeyValue<'a> for &'a str {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw).map_err(|_| CursorError::InvalidString)
}
}
#[cfg(feature = "alloc")]
impl<'a> GetKeyValue<'a> for Box<str> {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw)
.map(|s| s.to_owned().into_boxed_str())
.map_err(|_| CursorError::InvalidString)
}
}
#[cfg(feature = "alloc")]
impl<'a> GetKeyValue<'a> for String {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
let raw = cur.get_key_value_raw()?;
str::from_utf8(raw)
.map(|s| s.to_owned())
.map_err(|_| CursorError::InvalidString)
}
}
impl<'a> GetKeyValue<'a> for bool {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
match cur.get_key_value_raw()? {
b"0" => Ok(false),
b"1" => Ok(true),
_ => Err(CursorError::InvalidBool),
}
}
}
impl GetKeyValue<'_> for crate::server_info::Region {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value::<u8>()?.try_into()
}
}
impl GetKeyValue<'_> for crate::server_info::ServerType {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value_raw()?.try_into()
}
}
impl GetKeyValue<'_> for crate::server_info::Os {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value_raw()?.try_into()
}
}
macro_rules! impl_get_value {
($($t:ty),+ $(,)?) => {
$(impl<'a> GetKeyValue<'a> for $t {
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self> {
let s = cur.get_key_value::<&str>()?;
// HACK: special case for one asshole
let (_, s) = color::trim_start_color(s);
s.parse().map_err(|_| CursorError::InvalidNumber)
}
})+
};
}
impl_get_value! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
}
// TODO: impl GetKeyValue for f32 and f64
#[derive(Copy, Clone)]
pub struct Cursor<'a> {
buffer: &'a [u8],
}
macro_rules! impl_get {
($($n:ident: $t:ty = $f:ident),+ $(,)?) => (
$(#[inline]
pub fn $n(&mut self) -> Result<$t> {
const N: usize = mem::size_of::<$t>();
self.get_array::<N>().map(<$t>::$f)
})+
);
}
impl<'a> Cursor<'a> {
pub fn new(buffer: &'a [u8]) -> Self {
Self { buffer }
}
pub fn end(self) -> &'a [u8] {
self.buffer
}
pub fn as_slice(&'a self) -> &'a [u8] {
self.buffer
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.buffer.len()
}
#[inline(always)]
pub fn has_remaining(&self) -> bool {
self.remaining() != 0
}
pub fn get_bytes(&mut self, count: usize) -> Result<&'a [u8]> {
if count <= self.remaining() {
let (head, tail) = self.buffer.split_at(count);
self.buffer = tail;
Ok(head)
} else {
Err(CursorError::UnexpectedEnd)
}
}
pub fn advance(&mut self, count: usize) -> Result<()> {
self.get_bytes(count).map(|_| ())
}
pub fn get_array<const N: usize>(&mut self) -> Result<[u8; N]> {
self.get_bytes(N).map(|s| {
let mut array = [0; N];
array.copy_from_slice(s);
array
})
}
pub fn get_str(&mut self, n: usize) -> Result<&'a str> {
let mut cur = *self;
let s = cur
.get_bytes(n)
.and_then(|s| str::from_utf8(s).map_err(|_| CursorError::InvalidString))?;
*self = cur;
Ok(s)
}
pub fn get_cstr(&mut self) -> Result<Str<&'a [u8]>> {
let pos = self
.buffer
.iter()
.position(|&c| c == b'\0')
.ok_or(CursorError::UnexpectedEnd)?;
let (head, tail) = self.buffer.split_at(pos);
self.buffer = &tail[1..];
Ok(Str(&head[..pos]))
}
pub fn get_cstr_as_str(&mut self) -> Result<&'a str> {
str::from_utf8(&self.get_cstr()?).map_err(|_| CursorError::InvalidString)
}
#[inline(always)]
pub fn get_u8(&mut self) -> Result<u8> {
self.get_array::<1>().map(|s| s[0])
}
#[inline(always)]
pub fn get_i8(&mut self) -> Result<i8> {
self.get_array::<1>().map(|s| s[0] as i8)
}
impl_get! {
get_u16_le: u16 = from_le_bytes,
get_u32_le: u32 = from_le_bytes,
get_u64_le: u64 = from_le_bytes,
get_i16_le: i16 = from_le_bytes,
get_i32_le: i32 = from_le_bytes,
get_i64_le: i64 = from_le_bytes,
get_f32_le: f32 = from_le_bytes,
get_f64_le: f64 = from_le_bytes,
get_u16_be: u16 = from_be_bytes,
get_u32_be: u32 = from_be_bytes,
get_u64_be: u64 = from_be_bytes,
get_i16_be: i16 = from_be_bytes,
get_i32_be: i32 = from_be_bytes,
get_i64_be: i64 = from_be_bytes,
get_f32_be: f32 = from_be_bytes,
get_f64_be: f64 = from_be_bytes,
get_u16_ne: u16 = from_ne_bytes,
get_u32_ne: u32 = from_ne_bytes,
get_u64_ne: u64 = from_ne_bytes,
get_i16_ne: i16 = from_ne_bytes,
get_i32_ne: i32 = from_ne_bytes,
get_i64_ne: i64 = from_ne_bytes,
get_f32_ne: f32 = from_ne_bytes,
get_f64_ne: f64 = from_ne_bytes,
}
pub fn expect(&mut self, s: &[u8]) -> Result<()> {
if self.buffer.starts_with(s) {
self.advance(s.len())?;
Ok(())
} else {
Err(CursorError::Expect)
}
}
pub fn expect_empty(&self) -> Result<()> {
if self.has_remaining() {
Err(CursorError::ExpectEmpty)
} else {
Ok(())
}
}
pub fn take_while<F>(&mut self, mut cond: F) -> Result<&'a [u8]>
where
F: FnMut(u8) -> bool,
{
self.buffer
.iter()
.position(|&i| !cond(i))
.ok_or(CursorError::UnexpectedEnd)
.and_then(|n| self.get_bytes(n))
}
pub fn take_while_or_all<F>(&mut self, cond: F) -> &'a [u8]
where
F: FnMut(u8) -> bool,
{
self.take_while(cond).unwrap_or_else(|_| {
let (head, tail) = self.buffer.split_at(self.buffer.len());
self.buffer = tail;
head
})
}
pub fn get_key_value_raw(&mut self) -> Result<&'a [u8]> {
let mut cur = *self;
match cur.get_u8()? {
b'\\' => {
let value = cur.take_while_or_all(|c| c != b'\\' && c != b'\n');
*self = cur;
Ok(value)
}
_ => Err(CursorError::InvalidTableValue),
}
}
pub fn get_key_value<T: GetKeyValue<'a>>(&mut self) -> Result<T> {
T::get_key_value(self)
}
pub fn skip_key_value<T: GetKeyValue<'a>>(&mut self) -> Result<()> {
T::get_key_value(self).map(|_| ())
}
pub fn get_key_raw(&mut self) -> Result<&'a [u8]> {
let mut cur = *self;
match cur.get_u8() {
Ok(b'\\') => {
let value = cur.take_while(|c| c != b'\\' && c != b'\n')?;
*self = cur;
Ok(value)
}
Ok(b'\n') | Err(CursorError::UnexpectedEnd) => Err(CursorError::TableEnd),
_ => Err(CursorError::InvalidTableKey),
}
}
pub fn get_key<T: GetKeyValue<'a>>(&mut self) -> Result<(&'a [u8], T)> {
Ok((self.get_key_raw()?, self.get_key_value()?))
}
}

View File

@ -0,0 +1,221 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
#![cfg_attr(not(feature = "net"), allow(dead_code))]
use core::{
fmt::{self, Write},
str,
};
use super::{CursorError, Result};
pub trait PutKeyValue {
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>>;
}
impl<T> PutKeyValue for &T
where
T: PutKeyValue,
{
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>> {
(*self).put_key_value(cur)
}
}
impl PutKeyValue for &str {
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>> {
cur.put_str(self)
}
}
impl PutKeyValue for bool {
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>> {
cur.put_u8(if *self { b'1' } else { b'0' })
}
}
impl PutKeyValue for crate::server_info::ServerType {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, CursorError> {
match self {
Self::Dedicated => cur.put_str("d"),
Self::Local => cur.put_str("l"),
Self::Proxy => cur.put_str("p"),
Self::Unknown => cur.put_str("?"),
}
}
}
impl PutKeyValue for crate::server_info::Os {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, CursorError> {
match self {
Self::Linux => cur.put_str("l"),
Self::Windows => cur.put_str("w"),
Self::Mac => cur.put_str("m"),
Self::Unknown => cur.put_str("?"),
}
}
}
macro_rules! impl_put_key_value {
($($t:ty),+ $(,)?) => {
$(impl PutKeyValue for $t {
fn put_key_value<'a, 'b>(&self, cur: &'b mut CursorMut<'a>) -> Result<&'b mut CursorMut<'a>> {
cur.put_as_str(self)
}
})+
};
}
impl_put_key_value! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
f32,
f64,
}
pub struct CursorMut<'a> {
buffer: &'a mut [u8],
pos: usize,
}
macro_rules! impl_put {
($($n:ident: $t:ty = $f:ident),+ $(,)?) => (
$(#[inline]
pub fn $n(&mut self, n: $t) -> Result<&mut Self> {
self.put_array(&n.$f())
})+
);
}
impl<'a> CursorMut<'a> {
pub fn new(buffer: &'a mut [u8]) -> Self {
Self { buffer, pos: 0 }
}
pub fn pos(&mut self) -> usize {
self.pos
}
#[inline(always)]
pub fn available(&self) -> usize {
self.buffer.len() - self.pos
}
pub fn advance<F>(&mut self, count: usize, mut f: F) -> Result<&mut Self>
where
F: FnMut(&mut [u8]),
{
if count <= self.available() {
f(&mut self.buffer[self.pos..self.pos + count]);
self.pos += count;
Ok(self)
} else {
Err(CursorError::UnexpectedEnd)
}
}
pub fn put_bytes(&mut self, s: &[u8]) -> Result<&mut Self> {
self.advance(s.len(), |i| {
i.copy_from_slice(s);
})
}
pub fn put_array<const N: usize>(&mut self, s: &[u8; N]) -> Result<&mut Self> {
self.advance(N, |i| {
i.copy_from_slice(s);
})
}
pub fn put_str(&mut self, s: &str) -> Result<&mut Self> {
self.put_bytes(s.as_bytes())
}
pub fn put_cstr(&mut self, s: &str) -> Result<&mut Self> {
self.put_str(s)?.put_u8(0)
}
#[inline(always)]
pub fn put_u8(&mut self, n: u8) -> Result<&mut Self> {
self.put_array(&[n])
}
#[inline(always)]
pub fn put_i8(&mut self, n: i8) -> Result<&mut Self> {
self.put_u8(n as u8)
}
impl_put! {
put_u16_le: u16 = to_le_bytes,
put_u32_le: u32 = to_le_bytes,
put_u64_le: u64 = to_le_bytes,
put_i16_le: i16 = to_le_bytes,
put_i32_le: i32 = to_le_bytes,
put_i64_le: i64 = to_le_bytes,
put_f32_le: f32 = to_le_bytes,
put_f64_le: f64 = to_le_bytes,
put_u16_be: u16 = to_be_bytes,
put_u32_be: u32 = to_be_bytes,
put_u64_be: u64 = to_be_bytes,
put_i16_be: i16 = to_be_bytes,
put_i32_be: i32 = to_be_bytes,
put_i64_be: i64 = to_be_bytes,
put_f32_be: f32 = to_be_bytes,
put_f64_be: f64 = to_be_bytes,
put_u16_ne: u16 = to_ne_bytes,
put_u32_ne: u32 = to_ne_bytes,
put_u64_ne: u64 = to_ne_bytes,
put_i16_ne: i16 = to_ne_bytes,
put_i32_ne: i32 = to_ne_bytes,
put_i64_ne: i64 = to_ne_bytes,
put_f32_ne: f32 = to_ne_bytes,
put_f64_ne: f64 = to_ne_bytes,
}
pub fn put_as_str<T: fmt::Display>(&mut self, value: T) -> Result<&mut Self> {
write!(self, "{}", value).map_err(|_| CursorError::UnexpectedEnd)?;
Ok(self)
}
pub fn put_key_value<T: PutKeyValue>(&mut self, value: T) -> Result<&mut Self> {
value.put_key_value(self)
}
pub fn put_key_raw(&mut self, key: &str, value: &[u8]) -> Result<&mut Self> {
self.put_u8(b'\\')?
.put_str(key)?
.put_u8(b'\\')?
.put_bytes(value)
}
pub fn put_key<T: PutKeyValue>(&mut self, key: &str, value: T) -> Result<&mut Self> {
self.put_u8(b'\\')?
.put_str(key)?
.put_u8(b'\\')?
.put_key_value(value)
}
}
impl fmt::Write for CursorMut<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.put_bytes(s.as_bytes())
.map(|_| ())
.map_err(|_| fmt::Error)
}
}

View File

@ -29,16 +29,22 @@
//! * Do not have bots
//! * Is not protected by a password
use std::fmt;
use std::net::SocketAddr;
use std::str::FromStr;
use core::{fmt, str::FromStr};
use bitflags::bitflags;
use crate::cursor::{Cursor, GetKeyValue, PutKeyValue};
use crate::server::{ServerAdd, ServerFlags, ServerType};
use crate::wrappers::Str;
use crate::{CursorError, Error, ServerInfo};
use crate::{
cursor::{Cursor, CursorError, GetKeyValue, PutKeyValue},
server_info::ServerInfo,
wrappers::Str,
Error,
};
#[cfg(feature = "net")]
use crate::{
net::server::ServerAdd,
server_info::{ServerFlags, ServerType},
};
bitflags! {
/// Additional filter flags.
@ -65,6 +71,7 @@ bitflags! {
}
}
#[cfg(feature = "net")]
impl<T> From<&ServerAdd<T>> for FilterFlags {
fn from(info: &ServerAdd<T>) -> Self {
let mut flags = Self::empty();
@ -196,11 +203,13 @@ impl Filter<'_> {
}
/// Returns `true` if a server matches the filter.
pub fn matches(&self, _addr: SocketAddr, info: &ServerInfo) -> bool {
// TODO: match addr
pub fn matches<T>(&self, info: &ServerInfo<T>) -> bool
where
T: AsRef<[u8]>,
{
!((info.flags & self.flags_mask) != self.flags
|| self.gamedir.map_or(false, |s| *s != &*info.gamedir)
|| self.map.map_or(false, |s| *s != &*info.map)
|| self.gamedir.map_or(false, |s| *s != info.gamedir.as_ref())
|| self.map.map_or(false, |s| *s != info.map.as_ref())
|| self.protocol.map_or(false, |s| s != info.protocol))
}
}
@ -307,9 +316,8 @@ impl fmt::Display for &Filter<'_> {
#[cfg(test)]
mod tests {
use super::*;
use crate::cursor::CursorMut;
use crate::wrappers::Str;
use std::net::SocketAddr;
use crate::{cursor::CursorMut, wrappers::Str};
macro_rules! tests {
($($name:ident$(($($predefined_f:ident: $predefined_v:expr),+ $(,)?))? {
@ -448,6 +456,17 @@ mod tests {
.pos();
assert_eq!(&buf[..n], b"0.19.3");
}
}
#[cfg(all(test, feature = "net"))]
mod match_tests {
use std::net::SocketAddr;
use crate::{cursor::CursorMut, wrappers::Str};
use super::*;
type ServerInfo = crate::ServerInfo<Box<[u8]>>;
macro_rules! servers {
($($addr:expr => $info:expr $(=> $func:expr)?)+) => (
@ -478,7 +497,7 @@ mod tests {
let iter = servers
.iter()
.enumerate()
.filter(|(_, (addr, server))| filter.matches(*addr, &server))
.filter(|(_, (_addr, server))| filter.matches(&server))
.map(|(i, _)| i);
assert_eq!(iter.collect::<Vec<_>>(), [$($expected),*])
);

View File

@ -6,24 +6,33 @@
//! Xash3D protocol between clients, servers and masters.
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(all(doc, has_doc_auto_cfg), feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[macro_use]
extern crate log;
mod cursor;
mod server_info;
pub mod admin;
#[cfg(feature = "net")]
pub mod net;
pub mod color;
pub mod filter;
pub mod game;
pub mod master;
pub mod server;
pub mod server_info;
pub mod wrappers;
pub use cursor::Error as CursorError;
pub use server_info::ServerInfo;
#[deprecated(since = "0.2.1", note = "use net module instead")]
#[cfg(feature = "net")]
pub use crate::net::{admin, game, master, server};
use thiserror::Error;
use core::fmt;
pub use cursor::CursorError;
pub use server_info::ServerInfo;
use crate::filter::Version;
@ -33,30 +42,54 @@ pub const PROTOCOL_VERSION: u8 = 49;
pub const CLIENT_VERSION: Version = Version::new(0, 20);
/// The error type for decoding and encoding packets.
#[derive(Error, Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Failed to decode a packet.
#[error("Invalid packet")]
InvalidPacket,
/// Invalid region.
#[error("Invalid region")]
InvalidRegion,
/// Invalid client announce IP.
#[error("Invalid client announce IP")]
InvalidClientAnnounceIp,
/// Invalid last IP.
#[error("Invalid last server IP")]
InvalidQueryServersLast,
/// Server protocol version is not supported.
#[error("Invalid protocol version")]
InvalidProtocolVersion,
/// Cursor error.
#[error("{0}")]
CursorError(#[from] CursorError),
CursorError(CursorError),
/// Invalid value for server add packet.
#[error("Invalid value for server add key `{0}`: {1}")]
InvalidServerValue(&'static str, #[source] CursorError),
InvalidServerValue(&'static str, CursorError),
/// Invalid value for query servers packet.
#[error("Invalid value for filter key `{0}`: {1}")]
InvalidFilterValue(&'static str, #[source] CursorError),
InvalidFilterValue(&'static str, CursorError),
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidPacket => "Invalid packet".fmt(fmt),
Self::InvalidRegion => "Invalid region".fmt(fmt),
Self::InvalidClientAnnounceIp => "Invalid client announce IP".fmt(fmt),
Self::InvalidQueryServersLast => "Invalid last server IP".fmt(fmt),
Self::InvalidProtocolVersion => "Invalid protocol version".fmt(fmt),
Self::CursorError(source) => source.fmt(fmt),
Self::InvalidServerValue(key, source) => {
write!(
fmt,
"Invalid value for server add key `{}`: {}",
key, source
)
}
Self::InvalidFilterValue(key, source) => {
write!(fmt, "Invalid value for filter key `{}`: {}", key, source)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl From<CursorError> for Error {
fn from(source: CursorError) -> Self {
Self::CursorError(source)
}
}

6
protocol/src/net.rs Normal file
View File

@ -0,0 +1,6 @@
//! Network packets decoders and encoders.
pub mod admin;
pub mod game;
pub mod master;
pub mod server;

View File

@ -3,13 +3,14 @@
//! Game client packets.
use std::fmt;
use std::net::SocketAddr;
use std::{fmt, net::SocketAddr};
use crate::cursor::{Cursor, CursorMut};
use crate::filter::Filter;
use crate::server::Region;
use crate::Error;
use crate::{
cursor::{Cursor, CursorMut},
filter::Filter,
net::server::Region,
Error,
};
/// Request a list of server addresses from master servers.
#[derive(Clone, Debug, PartialEq)]
@ -129,10 +130,14 @@ impl<'a> Packet<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::{FilterFlags, Version};
use crate::wrappers::Str;
use std::net::{IpAddr, Ipv4Addr};
use crate::{
filter::{FilterFlags, Version},
wrappers::Str,
};
#[test]
fn query_servers() {
let p = QueryServers {

View File

@ -5,8 +5,10 @@
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use super::cursor::{Cursor, CursorMut};
use super::Error;
use crate::{
cursor::{Cursor, CursorMut},
Error,
};
/// Master server challenge response packet.
#[derive(Clone, Debug, PartialEq)]
@ -143,7 +145,7 @@ impl<'a> QueryServersResponse<&'a [u8]> {
A: ServerAddress,
{
let mut cur = Cursor::new(self.inner);
std::iter::from_fn(move || {
core::iter::from_fn(move || {
if cur.remaining() == A::size() && cur.end().ends_with(&[0; 2]) {
// skip last address with port 0
return None;
@ -166,10 +168,7 @@ impl QueryServersResponse<()> {
/// Encode packet to `buf`.
///
/// If `buf` has not enougth size to hold all addresses the method must be called
/// multiple times until the end flag equals `true`.
///
/// Returns how many bytes was written in `buf` and the end flag.
/// Returns number of bytes written into `buf` and how many items was written.
pub fn encode<A>(&mut self, buf: &mut [u8], list: &[A]) -> Result<(usize, usize), Error>
where
A: ServerAddress,
@ -183,7 +182,7 @@ impl QueryServersResponse<()> {
}
let mut count = 0;
let mut iter = list.iter();
while cur.remaining() >= A::size() * 2 {
while cur.available() >= A::size() * 2 {
if let Some(i) = iter.next() {
i.put(&mut cur)?;
count += 1;

View File

@ -3,14 +3,24 @@
//! Game server packets.
use std::fmt;
use crate::{
cursor::{Cursor, CursorMut, GetKeyValue, PutKeyValue},
filter::Version,
wrappers::Str,
{CursorError, Error},
};
use bitflags::bitflags;
#[deprecated(since = "0.2.1", note = "use server_info::Region instead")]
pub use crate::server_info::Region;
use super::cursor::{Cursor, CursorMut, GetKeyValue, PutKeyValue};
use super::filter::Version;
use super::wrappers::Str;
use super::{CursorError, Error};
#[deprecated(since = "0.2.1", note = "use server_info::ServerType instead")]
pub use crate::server_info::ServerType;
#[deprecated(since = "0.2.1", note = "use server_info::ServerFlags instead")]
pub use crate::server_info::ServerFlags;
#[deprecated(since = "0.2.1", note = "use server_info::Os instead")]
pub use crate::server_info::Os;
/// Sended to a master server before `ServerAdd` packet.
#[derive(Clone, Debug, PartialEq)]
@ -52,211 +62,6 @@ impl Challenge {
}
}
/// The operating system on which the game server runs.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Os {
/// GNU/Linux.
Linux,
/// Microsoft Windows
Windows,
/// Apple macOS, OS X, Mac OS X
Mac,
/// Unknown
Unknown,
}
impl Default for Os {
fn default() -> Os {
Os::Unknown
}
}
impl TryFrom<&[u8]> for Os {
type Error = CursorError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"l" => Ok(Os::Linux),
b"w" => Ok(Os::Windows),
b"m" => Ok(Os::Mac),
_ => Ok(Os::Unknown),
}
}
}
impl GetKeyValue<'_> for Os {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value_raw()?.try_into()
}
}
impl PutKeyValue for Os {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, CursorError> {
match self {
Self::Linux => cur.put_str("l"),
Self::Windows => cur.put_str("w"),
Self::Mac => cur.put_str("m"),
Self::Unknown => cur.put_str("?"),
}
}
}
impl fmt::Display for Os {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Os::Linux => "Linux",
Os::Windows => "Windows",
Os::Mac => "Mac",
Os::Unknown => "Unknown",
};
write!(fmt, "{}", s)
}
}
/// Game server type.
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum ServerType {
/// Dedicated server.
Dedicated,
/// Game client.
Local,
/// Spectator proxy.
Proxy,
/// Unknown.
Unknown,
}
impl Default for ServerType {
fn default() -> Self {
Self::Unknown
}
}
impl TryFrom<&[u8]> for ServerType {
type Error = CursorError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"d" => Ok(Self::Dedicated),
b"l" => Ok(Self::Local),
b"p" => Ok(Self::Proxy),
_ => Ok(Self::Unknown),
}
}
}
impl GetKeyValue<'_> for ServerType {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value_raw()?.try_into()
}
}
impl PutKeyValue for ServerType {
fn put_key_value<'a, 'b>(
&self,
cur: &'b mut CursorMut<'a>,
) -> Result<&'b mut CursorMut<'a>, CursorError> {
match self {
Self::Dedicated => cur.put_str("d"),
Self::Local => cur.put_str("l"),
Self::Proxy => cur.put_str("p"),
Self::Unknown => cur.put_str("?"),
}
}
}
impl fmt::Display for ServerType {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use ServerType as E;
let s = match self {
E::Dedicated => "dedicated",
E::Local => "local",
E::Proxy => "proxy",
E::Unknown => "unknown",
};
write!(fmt, "{}", s)
}
}
/// The region of the world in which the server is located.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Region {
/// US East coast.
USEastCoast = 0x00,
/// US West coast.
USWestCoast = 0x01,
/// South America.
SouthAmerica = 0x02,
/// Europe.
Europe = 0x03,
/// Asia.
Asia = 0x04,
/// Australia.
Australia = 0x05,
/// Middle East.
MiddleEast = 0x06,
/// Africa.
Africa = 0x07,
/// Rest of the world.
RestOfTheWorld = 0xff,
}
impl Default for Region {
fn default() -> Self {
Self::RestOfTheWorld
}
}
impl TryFrom<u8> for Region {
type Error = CursorError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Region::USEastCoast),
0x01 => Ok(Region::USWestCoast),
0x02 => Ok(Region::SouthAmerica),
0x03 => Ok(Region::Europe),
0x04 => Ok(Region::Asia),
0x05 => Ok(Region::Australia),
0x06 => Ok(Region::MiddleEast),
0x07 => Ok(Region::Africa),
0xff => Ok(Region::RestOfTheWorld),
_ => Err(CursorError::InvalidNumber),
}
}
}
impl GetKeyValue<'_> for Region {
fn get_key_value(cur: &mut Cursor) -> Result<Self, CursorError> {
cur.get_key_value::<u8>()?.try_into()
}
}
bitflags! {
/// Additional server flags.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ServerFlags: u8 {
/// Server has bots.
const BOTS = 1 << 0;
/// Server is behind a password.
const PASSWORD = 1 << 1;
/// Server using anti-cheat.
const SECURE = 1 << 2;
/// Server is LAN.
const LAN = 1 << 3;
/// Server behind NAT.
const NAT = 1 << 4;
}
}
/// Add/update game server information on the master server.
#[derive(Clone, Debug, PartialEq, Default)]
pub struct ServerAdd<T> {

View File

@ -1,28 +1,217 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
use super::filter::{FilterFlags, Version};
use super::server::{Region, ServerAdd};
use super::wrappers::Str;
//! Server info structures used in filter.
use core::fmt;
#[cfg(all(feature = "alloc", feature = "net"))]
use alloc::boxed::Box;
use bitflags::bitflags;
use crate::{
cursor::CursorError,
filter::{FilterFlags, Version},
};
#[cfg(feature = "net")]
use crate::{net::server::ServerAdd, wrappers::Str};
/// The region of the world in which the server is located.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Region {
/// US East coast.
USEastCoast = 0x00,
/// US West coast.
USWestCoast = 0x01,
/// South America.
SouthAmerica = 0x02,
/// Europe.
Europe = 0x03,
/// Asia.
Asia = 0x04,
/// Australia.
Australia = 0x05,
/// Middle East.
MiddleEast = 0x06,
/// Africa.
Africa = 0x07,
/// Rest of the world.
RestOfTheWorld = 0xff,
}
impl Default for Region {
fn default() -> Self {
Self::RestOfTheWorld
}
}
impl TryFrom<u8> for Region {
type Error = CursorError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Region::USEastCoast),
0x01 => Ok(Region::USWestCoast),
0x02 => Ok(Region::SouthAmerica),
0x03 => Ok(Region::Europe),
0x04 => Ok(Region::Asia),
0x05 => Ok(Region::Australia),
0x06 => Ok(Region::MiddleEast),
0x07 => Ok(Region::Africa),
0xff => Ok(Region::RestOfTheWorld),
_ => Err(CursorError::InvalidNumber),
}
}
}
/// Game server type.
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum ServerType {
/// Dedicated server.
Dedicated,
/// Game client.
Local,
/// Spectator proxy.
Proxy,
/// Unknown.
Unknown,
}
impl Default for ServerType {
fn default() -> Self {
Self::Unknown
}
}
impl TryFrom<&[u8]> for ServerType {
type Error = CursorError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"d" => Ok(Self::Dedicated),
b"l" => Ok(Self::Local),
b"p" => Ok(Self::Proxy),
_ => Ok(Self::Unknown),
}
}
}
impl fmt::Display for ServerType {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use ServerType as E;
let s = match self {
E::Dedicated => "dedicated",
E::Local => "local",
E::Proxy => "proxy",
E::Unknown => "unknown",
};
write!(fmt, "{}", s)
}
}
bitflags! {
/// Additional server flags.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ServerFlags: u8 {
/// Server has bots.
const BOTS = 1 << 0;
/// Server is behind a password.
const PASSWORD = 1 << 1;
/// Server using anti-cheat.
const SECURE = 1 << 2;
/// Server is LAN.
const LAN = 1 << 3;
/// Server behind NAT.
const NAT = 1 << 4;
}
}
/// The operating system on which the game server runs.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Os {
/// GNU/Linux.
Linux,
/// Microsoft Windows
Windows,
/// Apple macOS, OS X, Mac OS X
Mac,
/// Unknown
Unknown,
}
impl Default for Os {
fn default() -> Os {
Os::Unknown
}
}
impl TryFrom<&[u8]> for Os {
type Error = CursorError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"l" => Ok(Os::Linux),
b"w" => Ok(Os::Windows),
b"m" => Ok(Os::Mac),
_ => Ok(Os::Unknown),
}
}
}
impl fmt::Display for Os {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Os::Linux => "Linux",
Os::Windows => "Windows",
Os::Mac => "Mac",
Os::Unknown => "Unknown",
};
write!(fmt, "{}", s)
}
}
/// Game server information.
#[derive(Clone, Debug)]
pub struct ServerInfo {
pub struct ServerInfo<T> {
/// Server version.
pub version: Version,
/// Server protocol version.
pub protocol: u8,
/// Server midification.
pub gamedir: Box<[u8]>,
pub gamedir: T,
/// Server map.
pub map: Box<[u8]>,
pub map: T,
/// Server additional filter flags.
pub flags: FilterFlags,
/// Server region.
pub region: Region,
}
impl ServerInfo {
#[cfg(feature = "net")]
impl<'a> ServerInfo<&'a [u8]> {
/// Creates a new `ServerInfo`.
pub fn new(info: &ServerAdd<Str<&'a [u8]>>) -> Self {
Self {
version: info.version,
protocol: info.protocol,
gamedir: &info.gamedir,
map: &info.map,
flags: FilterFlags::from(info),
region: info.region,
}
}
}
#[cfg(feature = "net")]
#[cfg(any(feature = "alloc", test))]
impl ServerInfo<Box<[u8]>> {
/// Creates a new `ServerInfo`.
pub fn new(info: &ServerAdd<Str<&[u8]>>) -> Self {
Self {

View File

@ -3,11 +3,9 @@
//! Wrappers for byte slices with pretty-printers.
use std::fmt;
use std::ops::Deref;
use core::{fmt, ops::Deref};
use crate::cursor::{CursorMut, PutKeyValue};
use crate::CursorError;
use crate::cursor::{CursorError, CursorMut, PutKeyValue};
/// Wrapper for slice of bytes with printing the bytes as a string.
///

View File

@ -9,7 +9,7 @@ authors = ["Denis Drakhnia <numas13@gmail.com>"]
edition = "2021"
rust-version = "1.56"
homepage = "https://xash.su"
repository = "https://git.mentality.rip/numas13/xash3d-master"
repository = "https://github.com/FWGS/xash3d-master"
[features]
default = ["color"]

View File

@ -265,7 +265,7 @@ impl<'a> Scan<'a> {
let mut buf = [0; 512];
let packet = game::QueryServers {
region: server::Region::RestOfTheWorld,
last: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0),
last: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)),
filter: self.cli.filter.as_str(),
};
let n = packet.encode(&mut buf)?;
@ -306,7 +306,7 @@ impl<'a> Scan<'a> {
if self.is_master(&from) {
if let Ok(packet) = master::QueryServersResponse::decode(&buf[..n]) {
if self.check_key(&from, packet.key) {
set.extend(packet.iter());
set.extend(packet.iter::<SocketAddrV4>());
}
} else {
eprintln!(