mirror of
https://git.mentality.rip/numas13/xash3d-master.git
synced 2025-01-22 04:44:31 +00:00
query: filter parameter
This commit is contained in:
parent
7e676620eb
commit
1254e41adf
@ -229,7 +229,7 @@ impl MasterServer {
|
||||
match p {
|
||||
game::Packet::QueryServers(p) => {
|
||||
trace!("{}: recv {:?}", from, p);
|
||||
if p.filter.clver < self.clver {
|
||||
if p.filter.clver.map_or(false, |v| v < self.clver) {
|
||||
let iter = std::iter::once(self.update_addr);
|
||||
self.send_server_list(from, iter)?;
|
||||
} else {
|
||||
|
@ -59,7 +59,10 @@ impl<'a> Iterator for ColorIter<'a> {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.inner.is_empty() {
|
||||
let i = self.inner[1..].find('^').map(|i| i + 1).unwrap_or(self.inner.len());
|
||||
let i = self.inner[1..]
|
||||
.find('^')
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(self.inner.len());
|
||||
let (head, tail) = self.inner.split_at(i);
|
||||
let (color, text) = trim_start_color(head);
|
||||
self.inner = tail;
|
||||
|
@ -8,7 +8,7 @@ use std::slice;
|
||||
use std::str;
|
||||
|
||||
use super::types::Str;
|
||||
use super::{Error, color};
|
||||
use super::{color, Error};
|
||||
|
||||
pub trait GetKeyValue<'a>: Sized {
|
||||
fn get_key_value(cur: &mut Cursor<'a>) -> Result<Self, Error>;
|
||||
|
@ -90,7 +90,7 @@ pub struct Version {
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn new(major: u8, minor: u8) -> Self {
|
||||
pub const fn new(major: u8, minor: u8) -> Self {
|
||||
Self { major, minor }
|
||||
}
|
||||
}
|
||||
@ -136,11 +136,11 @@ impl PutKeyValue for Version {
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Filter<'a> {
|
||||
/// Servers running the specified modification (ex. cstrike)
|
||||
pub gamedir: &'a [u8],
|
||||
pub gamedir: Option<&'a [u8]>,
|
||||
/// Servers running the specified map (ex. cs_italy)
|
||||
pub map: &'a [u8],
|
||||
pub map: Option<&'a [u8]>,
|
||||
/// Client version.
|
||||
pub clver: Version,
|
||||
pub clver: Option<Version>,
|
||||
|
||||
pub flags: FilterFlags,
|
||||
pub flags_mask: FilterFlags,
|
||||
@ -154,13 +154,15 @@ impl Filter<'_> {
|
||||
|
||||
pub fn matches(&self, _addr: SocketAddrV4, info: &ServerInfo) -> bool {
|
||||
!((info.flags & self.flags_mask) != self.flags
|
||||
|| (!self.gamedir.is_empty() && self.gamedir != &*info.gamedir)
|
||||
|| (!self.map.is_empty() && self.map != &*info.map))
|
||||
|| self.gamedir.map_or(false, |s| s != &*info.gamedir)
|
||||
|| self.map.map_or(false, |s| s != &*info.map))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Filter<'a> {
|
||||
pub fn from_bytes(src: &'a [u8]) -> Result<Self, Error> {
|
||||
impl<'a> TryFrom<&'a [u8]> for Filter<'a> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(src: &'a [u8]) -> Result<Self, Self::Error> {
|
||||
let mut cur = Cursor::new(src);
|
||||
let mut filter = Self::default();
|
||||
|
||||
@ -174,17 +176,18 @@ impl<'a> Filter<'a> {
|
||||
match *key {
|
||||
b"dedicated" => filter.insert_flag(FilterFlags::DEDICATED, cur.get_key_value()?),
|
||||
b"secure" => filter.insert_flag(FilterFlags::SECURE, cur.get_key_value()?),
|
||||
b"gamedir" => filter.gamedir = cur.get_key_value()?,
|
||||
b"map" => filter.map = cur.get_key_value()?,
|
||||
b"gamedir" => filter.gamedir = Some(cur.get_key_value()?),
|
||||
b"map" => filter.map = Some(cur.get_key_value()?),
|
||||
b"empty" => filter.insert_flag(FilterFlags::NOT_EMPTY, cur.get_key_value()?),
|
||||
b"full" => filter.insert_flag(FilterFlags::FULL, cur.get_key_value()?),
|
||||
b"password" => filter.insert_flag(FilterFlags::PASSWORD, cur.get_key_value()?),
|
||||
b"noplayers" => filter.insert_flag(FilterFlags::NOPLAYERS, cur.get_key_value()?),
|
||||
b"clver" => {
|
||||
filter.clver = cur
|
||||
.get_key_value::<&str>()?
|
||||
.parse()
|
||||
.map_err(|_| Error::InvalidPacket)?
|
||||
filter.clver = Some(
|
||||
cur.get_key_value::<&str>()?
|
||||
.parse()
|
||||
.map_err(|_| Error::InvalidPacket)?,
|
||||
);
|
||||
}
|
||||
b"nat" => filter.insert_flag(FilterFlags::NAT, cur.get_key_value()?),
|
||||
b"lan" => filter.insert_flag(FilterFlags::LAN, cur.get_key_value()?),
|
||||
@ -214,18 +217,20 @@ impl fmt::Display for &Filter<'_> {
|
||||
|
||||
display_flag!("dedicated", FilterFlags::DEDICATED);
|
||||
display_flag!("secure", FilterFlags::SECURE);
|
||||
if !self.gamedir.is_empty() {
|
||||
write!(fmt, "\\gamedir\\{}", Str(self.gamedir))?;
|
||||
if let Some(s) = self.gamedir {
|
||||
write!(fmt, "\\gamedir\\{}", Str(s))?;
|
||||
}
|
||||
display_flag!("secure", FilterFlags::SECURE);
|
||||
if !self.map.is_empty() {
|
||||
write!(fmt, "\\map\\{}", Str(self.map))?;
|
||||
if let Some(s) = self.map {
|
||||
write!(fmt, "\\map\\{}", Str(s))?;
|
||||
}
|
||||
display_flag!("empty", FilterFlags::NOT_EMPTY);
|
||||
display_flag!("full", FilterFlags::FULL);
|
||||
display_flag!("password", FilterFlags::PASSWORD);
|
||||
display_flag!("noplayers", FilterFlags::NOPLAYERS);
|
||||
write!(fmt, "\\clver\\{}", self.clver)?;
|
||||
if let Some(v) = self.clver {
|
||||
write!(fmt, "\\clver\\{}", v)?;
|
||||
}
|
||||
display_flag!("nat", FilterFlags::NAT);
|
||||
display_flag!("lan", FilterFlags::LAN);
|
||||
display_flag!("bots", FilterFlags::BOTS);
|
||||
@ -253,7 +258,7 @@ mod tests {
|
||||
.. Filter::default()
|
||||
};
|
||||
$(assert_eq!(
|
||||
Filter::from_bytes($src),
|
||||
Filter::try_from($src as &[u8]),
|
||||
Ok(Filter {
|
||||
$($field: $value,)*
|
||||
..predefined
|
||||
@ -266,17 +271,17 @@ mod tests {
|
||||
tests! {
|
||||
parse_gamedir {
|
||||
b"\\gamedir\\valve" => {
|
||||
gamedir: &b"valve"[..],
|
||||
gamedir: Some(&b"valve"[..]),
|
||||
}
|
||||
}
|
||||
parse_map {
|
||||
b"\\map\\crossfire" => {
|
||||
map: &b"crossfire"[..],
|
||||
map: Some(&b"crossfire"[..]),
|
||||
}
|
||||
}
|
||||
parse_clver {
|
||||
b"\\clver\\0.20" => {
|
||||
clver: Version::new(0, 20),
|
||||
clver: Some(Version::new(0, 20)),
|
||||
}
|
||||
}
|
||||
parse_dedicated(flags_mask: FilterFlags::DEDICATED) {
|
||||
@ -349,9 +354,9 @@ mod tests {
|
||||
\\password\\1\
|
||||
\\secure\\1\
|
||||
" => {
|
||||
gamedir: &b"valve"[..],
|
||||
map: &b"crossfire"[..],
|
||||
clver: Version::new(0, 20),
|
||||
gamedir: Some(&b"valve"[..]),
|
||||
map: Some(&b"crossfire"[..]),
|
||||
clver: Some(Version::new(0, 20)),
|
||||
flags: FilterFlags::all(),
|
||||
flags_mask: FilterFlags::all(),
|
||||
}
|
||||
@ -383,7 +388,7 @@ mod tests {
|
||||
macro_rules! matches {
|
||||
($servers:expr, $filter:expr$(, $expected:expr)*) => (
|
||||
let servers = &$servers;
|
||||
let filter = Filter::from_bytes($filter).unwrap();
|
||||
let filter = Filter::try_from($filter as &[u8]).unwrap();
|
||||
let iter = servers
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
||||
|
||||
use std::fmt;
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use crate::cursor::{Cursor, CursorMut};
|
||||
@ -9,18 +10,23 @@ use crate::server::Region;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct QueryServers<'a> {
|
||||
pub struct QueryServers<T> {
|
||||
pub region: Region,
|
||||
pub last: SocketAddrV4,
|
||||
pub filter: Filter<'a>,
|
||||
pub filter: T,
|
||||
}
|
||||
|
||||
impl<'a> QueryServers<'a> {
|
||||
impl QueryServers<()> {
|
||||
pub const HEADER: &'static [u8] = b"1";
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> QueryServers<T>
|
||||
where
|
||||
T: TryFrom<&'a [u8], Error = Error>,
|
||||
{
|
||||
pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
|
||||
let mut cur = Cursor::new(src);
|
||||
cur.expect(Self::HEADER)?;
|
||||
cur.expect(QueryServers::HEADER)?;
|
||||
let region = cur.get_u8()?.try_into().map_err(|_| Error::InvalidPacket)?;
|
||||
let last = cur.get_cstr_as_str()?;
|
||||
let filter = cur.get_cstr()?;
|
||||
@ -28,13 +34,18 @@ impl<'a> QueryServers<'a> {
|
||||
Ok(Self {
|
||||
region,
|
||||
last: last.parse().map_err(|_| Error::InvalidPacket)?,
|
||||
filter: Filter::from_bytes(&filter)?,
|
||||
filter: T::try_from(*filter)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> QueryServers<T>
|
||||
where
|
||||
for<'b> &'b T: fmt::Display,
|
||||
{
|
||||
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
Ok(CursorMut::new(buf)
|
||||
.put_bytes(Self::HEADER)?
|
||||
.put_bytes(QueryServers::HEADER)?
|
||||
.put_u8(self.region as u8)?
|
||||
.put_as_str(self.last)?
|
||||
.put_u8(0)?
|
||||
@ -76,7 +87,7 @@ impl GetServerInfo {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Packet<'a> {
|
||||
QueryServers(QueryServers<'a>),
|
||||
QueryServers(QueryServers<Filter<'a>>),
|
||||
GetServerInfo(GetServerInfo),
|
||||
}
|
||||
|
||||
@ -106,9 +117,9 @@ mod tests {
|
||||
region: Region::RestOfTheWorld,
|
||||
last: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0),
|
||||
filter: Filter {
|
||||
gamedir: &b"valve"[..],
|
||||
map: &b"crossfire"[..],
|
||||
clver: Version::new(0, 20),
|
||||
gamedir: Some(&b"valve"[..]),
|
||||
map: Some(&b"crossfire"[..]),
|
||||
clver: Some(Version::new(0, 20)),
|
||||
flags: FilterFlags::all(),
|
||||
flags_mask: FilterFlags::all(),
|
||||
},
|
||||
|
@ -5,18 +5,22 @@ mod cursor;
|
||||
mod server_info;
|
||||
|
||||
pub mod admin;
|
||||
pub mod color;
|
||||
pub mod filter;
|
||||
pub mod game;
|
||||
pub mod master;
|
||||
pub mod server;
|
||||
pub mod types;
|
||||
pub mod color;
|
||||
|
||||
pub use server_info::ServerInfo;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub const VERSION: u8 = 49;
|
||||
use crate::filter::Version;
|
||||
|
||||
pub const PROTOCOL_VERSION: u8 = 49;
|
||||
|
||||
pub const CLIENT_VERSION: Version = Version::new(0, 20);
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
|
@ -5,6 +5,8 @@ use std::process;
|
||||
|
||||
use getopts::Options;
|
||||
|
||||
use xash3d_protocol as proto;
|
||||
|
||||
const BIN_NAME: &str = env!("CARGO_BIN_NAME");
|
||||
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
@ -22,6 +24,7 @@ pub struct Cli {
|
||||
pub json: bool,
|
||||
pub debug: bool,
|
||||
pub force_color: bool,
|
||||
pub filter: String,
|
||||
}
|
||||
|
||||
impl Default for Cli {
|
||||
@ -34,10 +37,12 @@ impl Default for Cli {
|
||||
args: Default::default(),
|
||||
master_timeout: 2,
|
||||
server_timeout: 2,
|
||||
protocol: vec![xash3d_protocol::VERSION, xash3d_protocol::VERSION - 1],
|
||||
protocol: vec![proto::PROTOCOL_VERSION, proto::PROTOCOL_VERSION - 1],
|
||||
json: false,
|
||||
debug: false,
|
||||
force_color: false,
|
||||
// if changed do not forget to update cli parsing
|
||||
filter: format!("\\gamedir\\valve\\clver\\{}", proto::CLIENT_VERSION),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,6 +99,8 @@ pub fn parse() -> Cli {
|
||||
opts.optflag("j", "json", "output JSON");
|
||||
opts.optflag("d", "debug", "output debug");
|
||||
opts.optflag("F", "force-color", "force colored output");
|
||||
let help = format!("query filter [default: {:?}]", cli.filter);
|
||||
opts.optopt("f", "filter", &help, "FILTER");
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
@ -124,7 +131,7 @@ pub fn parse() -> Cli {
|
||||
}
|
||||
}
|
||||
|
||||
match matches.opt_get("master") {
|
||||
match matches.opt_get("master-timeout") {
|
||||
Ok(Some(t)) => cli.master_timeout = t,
|
||||
Ok(None) => {}
|
||||
Err(_) => {
|
||||
@ -161,6 +168,18 @@ pub fn parse() -> Cli {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = matches.opt_str("filter") {
|
||||
let mut filter = String::with_capacity(cli.filter.len() + s.len());
|
||||
if !s.contains("\\gamedir") {
|
||||
filter.push_str("\\gamedir\\valve");
|
||||
}
|
||||
if !s.contains("\\clver") {
|
||||
filter.push_str("\\clver\\0.20");
|
||||
}
|
||||
filter.push_str(&s);
|
||||
cli.filter = filter;
|
||||
}
|
||||
|
||||
cli.json = matches.opt_present("json");
|
||||
cli.debug = matches.opt_present("debug");
|
||||
cli.force_color = matches.opt_present("force-color");
|
||||
|
@ -9,14 +9,14 @@ use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket};
|
||||
use std::process;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use xash3d_protocol::types::Str;
|
||||
use xash3d_protocol::{color, filter, game, master, server, Error as ProtocolError};
|
||||
use xash3d_protocol::{color, game, master, server, Error as ProtocolError};
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
||||
@ -135,6 +135,7 @@ struct InfoResult<'a> {
|
||||
master_timeout: u32,
|
||||
server_timeout: u32,
|
||||
masters: &'a [Box<str>],
|
||||
filter: &'a str,
|
||||
servers: &'a [&'a ServerResult],
|
||||
}
|
||||
|
||||
@ -142,6 +143,7 @@ struct InfoResult<'a> {
|
||||
struct ListResult<'a> {
|
||||
master_timeout: u32,
|
||||
masters: &'a [Box<str>],
|
||||
filter: &'a str,
|
||||
servers: &'a [&'a str],
|
||||
}
|
||||
|
||||
@ -199,7 +201,12 @@ fn cmp_address(a: &str, b: &str) -> cmp::Ordering {
|
||||
}
|
||||
}
|
||||
|
||||
fn query_servers(host: &str, timeout: Duration, tx: &mpsc::Sender<Message>) -> Result<(), Error> {
|
||||
fn query_servers(
|
||||
host: &str,
|
||||
cli: &Cli,
|
||||
timeout: Duration,
|
||||
tx: &mpsc::Sender<Message>,
|
||||
) -> Result<(), Error> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
sock.connect(host)?;
|
||||
|
||||
@ -207,12 +214,7 @@ fn query_servers(host: &str, timeout: Duration, tx: &mpsc::Sender<Message>) -> R
|
||||
let p = game::QueryServers {
|
||||
region: server::Region::RestOfTheWorld,
|
||||
last: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0),
|
||||
filter: filter::Filter {
|
||||
gamedir: b"valve",
|
||||
clver: filter::Version::new(0, 20),
|
||||
// TODO: filter
|
||||
..Default::default()
|
||||
},
|
||||
filter: cli.filter.as_str(),
|
||||
};
|
||||
let n = p.encode(&mut buf)?;
|
||||
sock.send(&buf[..n])?;
|
||||
@ -282,7 +284,7 @@ fn get_server_info(
|
||||
Ok(ServerResult::protocol(addr))
|
||||
}
|
||||
|
||||
fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {
|
||||
fn query_server_info(cli: &Arc<Cli>, servers: &[String]) -> Result<(), Error> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut workers = 0;
|
||||
|
||||
@ -291,8 +293,9 @@ fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {
|
||||
let master = i.to_owned();
|
||||
let tx = tx.clone();
|
||||
let timeout = Duration::from_secs(cli.master_timeout as u64);
|
||||
let cli = cli.clone();
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = query_servers(&master, timeout, &tx) {
|
||||
if let Err(e) = query_servers(&master, &cli, timeout, &tx) {
|
||||
eprintln!("master({}) error: {}", master, e);
|
||||
}
|
||||
tx.send(Message::End).unwrap();
|
||||
@ -341,6 +344,7 @@ fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {
|
||||
master_timeout: cli.master_timeout,
|
||||
server_timeout: cli.server_timeout,
|
||||
masters: &cli.masters,
|
||||
filter: &cli.filter,
|
||||
servers: &servers,
|
||||
};
|
||||
|
||||
@ -408,7 +412,7 @@ fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_servers(cli: &Cli) -> Result<(), Error> {
|
||||
fn list_servers(cli: &Arc<Cli>) -> Result<(), Error> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut workers = 0;
|
||||
|
||||
@ -416,8 +420,9 @@ fn list_servers(cli: &Cli) -> Result<(), Error> {
|
||||
let master = i.to_owned();
|
||||
let tx = tx.clone();
|
||||
let timeout = Duration::from_secs(cli.master_timeout as u64);
|
||||
let cli = cli.clone();
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = query_servers(&master, timeout, &tx) {
|
||||
if let Err(e) = query_servers(&master, &cli, timeout, &tx) {
|
||||
eprintln!("master({}) error: {}", master, e);
|
||||
}
|
||||
tx.send(Message::End).unwrap();
|
||||
@ -448,6 +453,7 @@ fn list_servers(cli: &Cli) -> Result<(), Error> {
|
||||
let result = ListResult {
|
||||
master_timeout: cli.master_timeout,
|
||||
masters: &cli.masters,
|
||||
filter: &cli.filter,
|
||||
servers: &servers,
|
||||
};
|
||||
|
||||
@ -468,6 +474,7 @@ fn list_servers(cli: &Cli) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
fn execute(cli: Cli) -> Result<(), Error> {
|
||||
let cli = Arc::new(cli);
|
||||
match cli.args.get(0).map(|s| s.as_str()).unwrap_or_default() {
|
||||
"all" | "" => query_server_info(&cli, &[])?,
|
||||
"info" => query_server_info(&cli, &cli.args[1..])?,
|
||||
|
Loading…
x
Reference in New Issue
Block a user