Browse Source

protocol: add docs and deny(missing_docs)

ipv6
Denis Drakhnia 1 year ago
parent
commit
417254d32e
  1. 22
      protocol/src/admin.rs
  2. 51
      protocol/src/color.rs
  3. 16
      protocol/src/filter.rs
  4. 19
      protocol/src/game.rs
  5. 12
      protocol/src/lib.rs
  6. 46
      protocol/src/master.rs
  7. 93
      protocol/src/server.rs
  8. 8
      protocol/src/server_info.rs
  9. 22
      protocol/src/types.rs

22
protocol/src/admin.rs

@ -1,20 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Admin packets.
use crate::cursor::{Cursor, CursorMut}; use crate::cursor::{Cursor, CursorMut};
use crate::types::Hide; use crate::types::Hide;
use crate::Error; use crate::Error;
/// Default hash length.
pub const HASH_LEN: usize = 64; pub const HASH_LEN: usize = 64;
/// Default hash key.
pub const HASH_KEY: &str = "Half-Life"; pub const HASH_KEY: &str = "Half-Life";
/// Default hash personality.
pub const HASH_PERSONAL: &str = "Freeman"; pub const HASH_PERSONAL: &str = "Freeman";
/// Admin challenge request.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AdminChallenge; pub struct AdminChallenge;
impl AdminChallenge { impl AdminChallenge {
/// Packet header.
pub const HEADER: &'static [u8] = b"adminchallenge"; pub const HEADER: &'static [u8] = b"adminchallenge";
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
if src == Self::HEADER { if src == Self::HEADER {
Ok(Self) Ok(Self)
@ -23,21 +31,28 @@ impl AdminChallenge {
} }
} }
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf).put_bytes(Self::HEADER)?.pos()) Ok(CursorMut::new(buf).put_bytes(Self::HEADER)?.pos())
} }
} }
/// Admin command.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AdminCommand<'a> { pub struct AdminCommand<'a> {
/// A number received in admin challenge response.
pub master_challenge: u32, pub master_challenge: u32,
/// A password hash mixed with a challenge number received in admin challenge response.
pub hash: Hide<&'a [u8]>, pub hash: Hide<&'a [u8]>,
/// A command to execute on a master server.
pub command: &'a str, pub command: &'a str,
} }
impl<'a> AdminCommand<'a> { impl<'a> AdminCommand<'a> {
/// Packet header.
pub const HEADER: &'static [u8] = b"admin"; pub const HEADER: &'static [u8] = b"admin";
/// Creates a new `AdminCommand`.
pub fn new(master_challenge: u32, hash: &'a [u8], command: &'a str) -> Self { pub fn new(master_challenge: u32, hash: &'a [u8], command: &'a str) -> Self {
Self { Self {
master_challenge, master_challenge,
@ -46,6 +61,7 @@ impl<'a> AdminCommand<'a> {
} }
} }
/// Decode packet from `src` with specified hash length.
pub fn decode_with_hash_len(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> { pub fn decode_with_hash_len(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -60,11 +76,13 @@ impl<'a> AdminCommand<'a> {
}) })
} }
/// Decode packet from `src`.
#[inline] #[inline]
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
Self::decode_with_hash_len(HASH_LEN, src) Self::decode_with_hash_len(HASH_LEN, src)
} }
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
@ -75,13 +93,17 @@ impl<'a> AdminCommand<'a> {
} }
} }
/// Admin packet.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Packet<'a> { pub enum Packet<'a> {
/// Admin challenge request.
AdminChallenge(AdminChallenge), AdminChallenge(AdminChallenge),
/// Admin command.
AdminCommand(AdminCommand<'a>), AdminCommand(AdminCommand<'a>),
} }
impl<'a> Packet<'a> { impl<'a> Packet<'a> {
/// Decode packet from `src` with specified hash length.
pub fn decode(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> { pub fn decode(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> {
if let Ok(p) = AdminChallenge::decode(src) { if let Ok(p) = AdminChallenge::decode(src) {
return Ok(Self::AdminChallenge(p)); return Ok(Self::AdminChallenge(p));

51
protocol/src/color.rs

@ -1,17 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Color codes for strings.
use std::borrow::Cow; use std::borrow::Cow;
/// Color codes `^digit`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Color { pub enum Color {
/// Black is coded as `^0`.
Black, Black,
/// Red is coded as `^1`.
Red, Red,
/// Green is coded as `^2`.
Green, Green,
/// Yellow is coded as `^3`.
Yellow, Yellow,
/// Blue is coded as `^4`.
Blue, Blue,
/// Cyan is coded as `^5`.
Cyan, Cyan,
/// Magenta is coded as `^6`.
Magenta, Magenta,
/// White is coded as `^7`.
White, White,
} }
@ -33,11 +44,29 @@ impl TryFrom<&str> for Color {
} }
} }
/// Test if string starts with color code.
///
/// # Examples
/// ```rust
/// # use xash3d_protocol::color::is_color_code;
/// assert_eq!(is_color_code("hello"), false);
/// assert_eq!(is_color_code("^4blue ocean"), true);
/// ```
#[inline] #[inline]
pub fn is_color_code(s: &str) -> bool { pub fn is_color_code(s: &str) -> bool {
matches!(s.as_bytes(), [b'^', c, ..] if c.is_ascii_digit()) matches!(s.as_bytes(), [b'^', c, ..] if c.is_ascii_digit())
} }
/// Trim color codes from a start of string.
///
/// # Examples
///
/// ```rust
/// # use xash3d_protocol::color::trim_start_color;
/// assert_eq!(trim_start_color("hello"), ("", "hello"));
/// assert_eq!(trim_start_color("^1red apple"), ("^1", "red apple"));
/// assert_eq!(trim_start_color("^1^2^3yellow roof"), ("^3", "yellow roof"));
/// ```
#[inline] #[inline]
pub fn trim_start_color(s: &str) -> (&str, &str) { pub fn trim_start_color(s: &str) -> (&str, &str) {
let mut n = 0; let mut n = 0;
@ -51,11 +80,25 @@ pub fn trim_start_color(s: &str) -> (&str, &str) {
} }
} }
/// Iterator for colored parts of a string.
///
/// # Examples
///
/// ```rust
/// # use xash3d_protocol::color::ColorIter;
/// let colored = "^1red flower^7 and ^2green grass";
/// let mut iter = ColorIter::new(colored);
/// assert_eq!(iter.next(), Some(("^1", "red flower")));
/// assert_eq!(iter.next(), Some(("^7", " and ")));
/// assert_eq!(iter.next(), Some(("^2", "green grass")));
/// assert_eq!(iter.next(), None);
/// ```
pub struct ColorIter<'a> { pub struct ColorIter<'a> {
inner: &'a str, inner: &'a str,
} }
impl<'a> ColorIter<'a> { impl<'a> ColorIter<'a> {
/// Creates a new `ColorIter`.
pub fn new(inner: &'a str) -> Self { pub fn new(inner: &'a str) -> Self {
Self { inner } Self { inner }
} }
@ -80,6 +123,14 @@ impl<'a> Iterator for ColorIter<'a> {
} }
} }
/// Trim color codes from a string.
///
/// # Examples
///
/// ```rust
/// # use xash3d_protocol::color::trim_color;
/// assert_eq!(trim_color("^1no^7 ^2colors^7"), "no colors");
/// ```
pub fn trim_color(s: &str) -> Cow<'_, str> { pub fn trim_color(s: &str) -> Cow<'_, str> {
let (_, s) = trim_start_color(s); let (_, s) = trim_start_color(s);
if !s.chars().any(|c| c == '^') { if !s.chars().any(|c| c == '^') {

16
protocol/src/filter.rs

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Server query filter //! Server query filter.
//! //!
//! # Supported filters: //! # Supported filters:
//! //!
@ -43,6 +43,7 @@ use crate::types::Str;
use crate::{Error, ServerInfo}; use crate::{Error, ServerInfo};
bitflags! { bitflags! {
/// Additional filter flags.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct FilterFlags: u16 { pub struct FilterFlags: u16 {
/// Servers running dedicated /// Servers running dedicated
@ -84,18 +85,24 @@ impl<T> From<&ServerAdd<T>> for FilterFlags {
} }
} }
/// Client or server version.
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version { pub struct Version {
/// MAJOR version.
pub major: u8, pub major: u8,
/// MINOR version.
pub minor: u8, pub minor: u8,
/// PATCH version.
pub patch: u8, pub patch: u8,
} }
impl Version { impl Version {
/// Creates a new `Version`.
pub const fn new(major: u8, minor: u8) -> Self { pub const fn new(major: u8, minor: u8) -> Self {
Self::with_patch(major, minor, 0) Self::with_patch(major, minor, 0)
} }
/// Creates a new `Version` with the specified `patch` version.
pub const fn with_patch(major: u8, minor: u8, patch: u8) -> Self { pub const fn with_patch(major: u8, minor: u8, patch: u8) -> Self {
Self { Self {
major, major,
@ -155,6 +162,7 @@ impl PutKeyValue for Version {
} }
} }
/// Server filter.
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Filter<'a> { pub struct Filter<'a> {
/// Servers running the specified modification (ex. cstrike) /// Servers running the specified modification (ex. cstrike)
@ -165,18 +173,22 @@ pub struct Filter<'a> {
pub clver: Option<Version>, pub clver: Option<Version>,
/// Protocol version /// Protocol version
pub protocol: Option<u8>, pub protocol: Option<u8>,
/// A number that master must sent back to game client.
pub key: Option<u32>, pub key: Option<u32>,
/// Additional filter flags.
pub flags: FilterFlags, pub flags: FilterFlags,
/// Filter flags mask.
pub flags_mask: FilterFlags, pub flags_mask: FilterFlags,
} }
impl Filter<'_> { impl Filter<'_> {
/// Insert filter flag.
pub fn insert_flag(&mut self, flag: FilterFlags, value: bool) { pub fn insert_flag(&mut self, flag: FilterFlags, value: bool) {
self.flags.set(flag, value); self.flags.set(flag, value);
self.flags_mask.insert(flag); self.flags_mask.insert(flag);
} }
/// Returns `true` if a server matches the filter.
pub fn matches(&self, _addr: SocketAddrV4, info: &ServerInfo) -> bool { pub fn matches(&self, _addr: SocketAddrV4, info: &ServerInfo) -> bool {
!((info.flags & self.flags_mask) != self.flags !((info.flags & self.flags_mask) != self.flags
|| self.gamedir.map_or(false, |s| *s != &*info.gamedir) || self.gamedir.map_or(false, |s| *s != &*info.gamedir)

19
protocol/src/game.rs

@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Game client packets.
use std::fmt; use std::fmt;
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
@ -9,14 +11,19 @@ use crate::filter::Filter;
use crate::server::Region; use crate::server::Region;
use crate::Error; use crate::Error;
/// Request a list of server addresses from master servers.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct QueryServers<T> { pub struct QueryServers<T> {
/// Servers must be from the `region`.
pub region: Region, pub region: Region,
/// Last received server address __(not used)__.
pub last: SocketAddrV4, pub last: SocketAddrV4,
/// Select only servers that match the `filter`.
pub filter: T, pub filter: T,
} }
impl QueryServers<()> { impl QueryServers<()> {
/// Packet header.
pub const HEADER: &'static [u8] = b"1"; pub const HEADER: &'static [u8] = b"1";
} }
@ -24,6 +31,7 @@ impl<'a, T: 'a> QueryServers<T>
where where
T: TryFrom<&'a [u8], Error = Error>, T: TryFrom<&'a [u8], Error = Error>,
{ {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(QueryServers::HEADER)?; cur.expect(QueryServers::HEADER)?;
@ -46,6 +54,7 @@ impl<'a, T: 'a> QueryServers<T>
where where
for<'b> &'b T: fmt::Display, for<'b> &'b T: fmt::Display,
{ {
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(QueryServers::HEADER)? .put_bytes(QueryServers::HEADER)?
@ -58,18 +67,23 @@ where
} }
} }
/// Request an information from a game server.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct GetServerInfo { pub struct GetServerInfo {
/// Client protocol version.
pub protocol: u8, pub protocol: u8,
} }
impl GetServerInfo { impl GetServerInfo {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffinfo "; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffinfo ";
/// Creates a new `GetServerInfo`.
pub fn new(protocol: u8) -> Self { pub fn new(protocol: u8) -> Self {
Self { protocol } Self { protocol }
} }
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -80,6 +94,7 @@ impl GetServerInfo {
Ok(Self { protocol }) Ok(Self { protocol })
} }
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
@ -88,13 +103,17 @@ impl GetServerInfo {
} }
} }
/// Game client packets.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Packet<'a> { pub enum Packet<'a> {
/// Request a list of server addresses from master servers.
QueryServers(QueryServers<Filter<'a>>), QueryServers(QueryServers<Filter<'a>>),
/// Request an information from a game server.
GetServerInfo(GetServerInfo), GetServerInfo(GetServerInfo),
} }
impl<'a> Packet<'a> { impl<'a> Packet<'a> {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
if let Ok(p) = QueryServers::decode(src) { if let Ok(p) = QueryServers::decode(src) {
return Ok(Self::QueryServers(p)); return Ok(Self::QueryServers(p));

12
protocol/src/lib.rs

@ -1,6 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
#![deny(missing_docs)]
//! Xash3D protocol between clients, servers and masters.
mod cursor; mod cursor;
mod server_info; mod server_info;
@ -18,18 +22,24 @@ use thiserror::Error;
use crate::filter::Version; use crate::filter::Version;
/// Current protocol version.
pub const PROTOCOL_VERSION: u8 = 49; pub const PROTOCOL_VERSION: u8 = 49;
/// Current client version.
pub const CLIENT_VERSION: Version = Version::new(0, 20); pub const CLIENT_VERSION: Version = Version::new(0, 20);
/// The error type for decoding and encoding packets.
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
/// Failed to decode a packet.
#[error("Invalid packet")] #[error("Invalid packet")]
InvalidPacket, InvalidPacket,
/// Invalid string in a packet.
#[error("Invalid UTF-8 string")] #[error("Invalid UTF-8 string")]
InvalidString, InvalidString,
/// Buffer size is no enougth to decode or encode a packet.
#[error("Unexpected end of buffer")] #[error("Unexpected end of buffer")]
UnexpectedEnd, UnexpectedEnd,
/// Server protocol version is not supported.
#[error("Invalid protocol version")] #[error("Invalid protocol version")]
InvalidProtocolVersion, InvalidProtocolVersion,
} }

46
protocol/src/master.rs

@ -1,20 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Master server packets.
use std::net::{Ipv4Addr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddrV4};
use super::cursor::{Cursor, CursorMut}; use super::cursor::{Cursor, CursorMut};
use super::Error; use super::Error;
/// Master server challenge response packet.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ChallengeResponse { pub struct ChallengeResponse {
/// A number that a game server must send back.
pub master_challenge: u32, pub master_challenge: u32,
/// A number that a master server received in challenge packet.
pub server_challenge: Option<u32>, pub server_challenge: Option<u32>,
} }
impl ChallengeResponse { impl ChallengeResponse {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffs\n"; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffs\n";
/// Creates a new `ChallengeResponse`.
pub fn new(master_challenge: u32, server_challenge: Option<u32>) -> Self { pub fn new(master_challenge: u32, server_challenge: Option<u32>) -> Self {
Self { Self {
master_challenge, master_challenge,
@ -22,6 +29,7 @@ impl ChallengeResponse {
} }
} }
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -38,6 +46,7 @@ impl ChallengeResponse {
}) })
} }
/// Encode packet to `buf`.
pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> { pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> {
let mut cur = CursorMut::new(buf); let mut cur = CursorMut::new(buf);
cur.put_bytes(Self::HEADER)?; cur.put_bytes(Self::HEADER)?;
@ -49,17 +58,21 @@ impl ChallengeResponse {
} }
} }
/// Game server addresses list.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct QueryServersResponse<I> { pub struct QueryServersResponse<I> {
inner: I, inner: I,
/// A challenge number received in a filter string.
pub key: Option<u32>, pub key: Option<u32>,
} }
impl QueryServersResponse<()> { impl QueryServersResponse<()> {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xfff\n"; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xfff\n";
} }
impl<'a> QueryServersResponse<&'a [u8]> { impl<'a> QueryServersResponse<&'a [u8]> {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(QueryServersResponse::HEADER)?; cur.expect(QueryServersResponse::HEADER)?;
@ -83,6 +96,7 @@ impl<'a> QueryServersResponse<&'a [u8]> {
Ok(Self { inner, key }) Ok(Self { inner, key })
} }
/// Iterator over game server addresses.
pub fn iter(&self) -> impl 'a + Iterator<Item = SocketAddrV4> { pub fn iter(&self) -> impl 'a + Iterator<Item = SocketAddrV4> {
let mut cur = Cursor::new(self.inner); let mut cur = Cursor::new(self.inner);
(0..self.inner.len() / 6).map(move |_| { (0..self.inner.len() / 6).map(move |_| {
@ -92,6 +106,7 @@ impl<'a> QueryServersResponse<&'a [u8]> {
}) })
} }
/// Returns `true` if game server addresses list is empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.inner.is_empty() self.inner.is_empty()
} }
@ -101,10 +116,17 @@ impl<I> QueryServersResponse<I>
where where
I: Iterator<Item = SocketAddrV4>, I: Iterator<Item = SocketAddrV4>,
{ {
/// Creates a new `QueryServersResponse`.
pub fn new(iter: I, key: Option<u32>) -> Self { pub fn new(iter: I, key: Option<u32>) -> Self {
Self { inner: iter, key } Self { inner: iter, key }
} }
/// 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.
pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> { pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> {
let mut cur = CursorMut::new(buf); let mut cur = CursorMut::new(buf);
cur.put_bytes(QueryServersResponse::HEADER)?; cur.put_bytes(QueryServersResponse::HEADER)?;
@ -129,18 +151,23 @@ where
} }
} }
/// Announce a game client to game server behind NAT.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ClientAnnounce { pub struct ClientAnnounce {
/// Address of the client.
pub addr: SocketAddrV4, pub addr: SocketAddrV4,
} }
impl ClientAnnounce { impl ClientAnnounce {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffc "; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffc ";
/// Creates a new `ClientAnnounce`.
pub fn new(addr: SocketAddrV4) -> Self { pub fn new(addr: SocketAddrV4) -> Self {
Self { addr } Self { addr }
} }
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -152,6 +179,7 @@ impl ClientAnnounce {
Ok(Self { addr }) Ok(Self { addr })
} }
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
@ -160,15 +188,20 @@ impl ClientAnnounce {
} }
} }
/// Admin challenge response.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AdminChallengeResponse { pub struct AdminChallengeResponse {
/// A number that admin must sent back to a master server.
pub master_challenge: u32, pub master_challenge: u32,
/// A number with which to mix a password hash.
pub hash_challenge: u32, pub hash_challenge: u32,
} }
impl AdminChallengeResponse { impl AdminChallengeResponse {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffadminchallenge"; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffadminchallenge";
/// Creates a new `AdminChallengeResponse`.
pub fn new(master_challenge: u32, hash_challenge: u32) -> Self { pub fn new(master_challenge: u32, hash_challenge: u32) -> Self {
Self { Self {
master_challenge, master_challenge,
@ -176,6 +209,7 @@ impl AdminChallengeResponse {
} }
} }
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -188,6 +222,7 @@ impl AdminChallengeResponse {
}) })
} }
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
@ -197,14 +232,21 @@ impl AdminChallengeResponse {
} }
} }
/// Master server packet.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Packet<'a> { pub enum Packet<'a> {
/// Master server challenge response packet.
ChallengeResponse(ChallengeResponse), ChallengeResponse(ChallengeResponse),
/// Game server addresses list.
QueryServersResponse(QueryServersResponse<&'a [u8]>), QueryServersResponse(QueryServersResponse<&'a [u8]>),
/// Announce a game client to game server behind NAT.
ClientAnnounce(ClientAnnounce),
/// Admin challenge response.
AdminChallengeResponse(AdminChallengeResponse), AdminChallengeResponse(AdminChallengeResponse),
} }
impl<'a> Packet<'a> { impl<'a> Packet<'a> {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
if let Ok(p) = ChallengeResponse::decode(src) { if let Ok(p) = ChallengeResponse::decode(src) {
return Ok(Self::ChallengeResponse(p)); return Ok(Self::ChallengeResponse(p));
@ -214,6 +256,10 @@ impl<'a> Packet<'a> {
return Ok(Self::QueryServersResponse(p)); return Ok(Self::QueryServersResponse(p));
} }
if let Ok(p) = ClientAnnounce::decode(src) {
return Ok(Self::ClientAnnounce(p));
}
if let Ok(p) = AdminChallengeResponse::decode(src) { if let Ok(p) = AdminChallengeResponse::decode(src) {
return Ok(Self::AdminChallengeResponse(p)); return Ok(Self::AdminChallengeResponse(p));
} }

93
protocol/src/server.rs

@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Game server packets.
use std::fmt; use std::fmt;
use bitflags::bitflags; use bitflags::bitflags;
@ -11,18 +13,23 @@ use super::filter::Version;
use super::types::Str; use super::types::Str;
use super::Error; use super::Error;
/// Sended to a master server before `ServerAdd` packet.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Challenge { pub struct Challenge {
/// A number that the server must return in response.
pub server_challenge: Option<u32>, pub server_challenge: Option<u32>,
} }
impl Challenge { impl Challenge {
/// Packet header.
pub const HEADER: &'static [u8] = b"q\xff"; pub const HEADER: &'static [u8] = b"q\xff";
/// Creates a new `Challenge`.
pub fn new(server_challenge: Option<u32>) -> Self { pub fn new(server_challenge: Option<u32>) -> Self {
Self { server_challenge } Self { server_challenge }
} }
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -35,6 +42,7 @@ impl Challenge {
Ok(Self { server_challenge }) Ok(Self { server_challenge })
} }
/// Encode packet to `buf`.
pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> { pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> {
let mut cur = CursorMut::new(buf); let mut cur = CursorMut::new(buf);
cur.put_bytes(Self::HEADER)?; cur.put_bytes(Self::HEADER)?;
@ -45,12 +53,17 @@ impl Challenge {
} }
} }
/// The operating system on which the game server runs.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum Os { pub enum Os {
/// GNU/Linux.
Linux, Linux,
/// Microsoft Windows
Windows, Windows,
/// Apple macOS, OS X, Mac OS X
Mac, Mac,
/// Unknown
Unknown, Unknown,
} }
@ -105,12 +118,17 @@ impl fmt::Display for Os {
} }
} }
/// Game server type.
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub enum ServerType { pub enum ServerType {
/// Dedicated server.
Dedicated, Dedicated,
/// Game client.
Local, Local,
/// Spectator proxy.
Proxy, Proxy,
/// Unknown.
Unknown, Unknown,
} }
@ -168,17 +186,27 @@ impl fmt::Display for ServerType {
} }
} }
/// The region of the world in which the server is located.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum Region { pub enum Region {
/// US East coast.
USEastCoast = 0x00, USEastCoast = 0x00,
/// US West coast.
USWestCoast = 0x01, USWestCoast = 0x01,
/// South America.
SouthAmerica = 0x02, SouthAmerica = 0x02,
/// Europe.
Europe = 0x03, Europe = 0x03,
/// Asia.
Asia = 0x04, Asia = 0x04,
/// Australia.
Australia = 0x05, Australia = 0x05,
/// Middle East.
MiddleEast = 0x06, MiddleEast = 0x06,
/// Africa.
Africa = 0x07, Africa = 0x07,
/// Rest of the world.
RestOfTheWorld = 0xff, RestOfTheWorld = 0xff,
} }
@ -214,33 +242,59 @@ impl GetKeyValue<'_> for Region {
} }
bitflags! { bitflags! {
/// Additional server flags.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ServerFlags: u8 { pub struct ServerFlags: u8 {
/// Server has bots.
const BOTS = 1 << 0; const BOTS = 1 << 0;
/// Server is behind a password.
const PASSWORD = 1 << 1; const PASSWORD = 1 << 1;
/// Server using anti-cheat.
const SECURE = 1 << 2; const SECURE = 1 << 2;
/// Server is LAN.
const LAN = 1 << 3; const LAN = 1 << 3;
/// Server behind NAT.
const NAT = 1 << 4; const NAT = 1 << 4;
} }
} }
/// Add/update game server information on the master server.
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct ServerAdd<T> { pub struct ServerAdd<T> {
/// Server is running the specified modification.
///
/// ## Examples:
///
/// * valve - Half-Life
/// * cstrike - Counter-Strike 1.6
/// * portal - Portal
/// * dod - Day of Defeat
/// * left4dead - Left 4 Dead
pub gamedir: T, pub gamedir: T,
/// Server is running `map`.
pub map: T, pub map: T,
/// Server version.
pub version: Version, pub version: Version,
pub product: T, /// Master server challenge number.
pub challenge: u32, pub challenge: u32,
/// Server type.
pub server_type: ServerType, pub server_type: ServerType,
/// Server is running on an operating system.
pub os: Os, pub os: Os,
/// Server is located in a `region`.
pub region: Region, pub region: Region,
/// Server protocol version.
pub protocol: u8, pub protocol: u8,
/// Current number of players on the server.
pub players: u8, pub players: u8,
/// Maximum number of players on the server.
pub max: u8, pub max: u8,
/// See `ServerFalgs`.
pub flags: ServerFlags, pub flags: ServerFlags,
} }
impl ServerAdd<()> { impl ServerAdd<()> {
/// Packet header.
pub const HEADER: &'static [u8] = b"0\n"; pub const HEADER: &'static [u8] = b"0\n";
} }
@ -248,6 +302,7 @@ impl<'a, T> ServerAdd<T>
where where
T: 'a + Default + GetKeyValue<'a>, T: 'a + Default + GetKeyValue<'a>,
{ {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(ServerAdd::HEADER)?; cur.expect(ServerAdd::HEADER)?;
@ -280,7 +335,6 @@ where
.unwrap_or_default() .unwrap_or_default()
} }
b"region" => ret.region = cur.get_key_value()?, b"region" => ret.region = cur.get_key_value()?,
b"product" => ret.product = cur.get_key_value()?,
b"bots" => ret.flags.set(ServerFlags::BOTS, cur.get_key_value()?), b"bots" => ret.flags.set(ServerFlags::BOTS, cur.get_key_value()?),
b"password" => ret.flags.set(ServerFlags::PASSWORD, cur.get_key_value()?), b"password" => ret.flags.set(ServerFlags::PASSWORD, cur.get_key_value()?),
b"secure" => ret.flags.set(ServerFlags::SECURE, cur.get_key_value()?), b"secure" => ret.flags.set(ServerFlags::SECURE, cur.get_key_value()?),
@ -308,6 +362,7 @@ impl<T> ServerAdd<T>
where where
T: PutKeyValue + Clone, T: PutKeyValue + Clone,
{ {
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(ServerAdd::HEADER)? .put_bytes(ServerAdd::HEADER)?
@ -321,7 +376,6 @@ where
.put_key("os", self.os)? .put_key("os", self.os)?
.put_key("version", self.version)? .put_key("version", self.version)?
.put_key("region", self.region as u8)? .put_key("region", self.region as u8)?
.put_key("product", self.product.clone())?
.put_key("bots", self.flags.contains(ServerFlags::BOTS))? .put_key("bots", self.flags.contains(ServerFlags::BOTS))?
.put_key("password", self.flags.contains(ServerFlags::PASSWORD))? .put_key("password", self.flags.contains(ServerFlags::PASSWORD))?
.put_key("secure", self.flags.contains(ServerFlags::SECURE))? .put_key("secure", self.flags.contains(ServerFlags::SECURE))?
@ -331,12 +385,15 @@ where
} }
} }
/// Remove the game server from a list.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ServerRemove; pub struct ServerRemove;
impl ServerRemove { impl ServerRemove {
/// Packet header.
pub const HEADER: &'static [u8] = b"b\n"; pub const HEADER: &'static [u8] = b"b\n";
/// Decode packet from `src`.
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
@ -344,26 +401,47 @@ impl ServerRemove {
Ok(Self) Ok(Self)
} }
/// Encode packet to `buf`.
pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> { pub fn encode<const N: usize>(&self, buf: &mut [u8; N]) -> Result<usize, Error> {
Ok(CursorMut::new(buf).put_bytes(Self::HEADER)?.pos()) Ok(CursorMut::new(buf).put_bytes(Self::HEADER)?.pos())
} }
} }
/// Game server information to game clients.
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct GetServerInfoResponse<T> { pub struct GetServerInfoResponse<T> {
/// Server is running the specified modification.
///
/// ## Examples:
///
/// * valve - Half-Life
/// * cstrike - Counter-Strike 1.6
/// * portal - Portal
/// * dod - Day of Defeat
/// * left4dead - Left 4 Dead
pub gamedir: T, pub gamedir: T,
/// Server is running `map`.
pub map: T, pub map: T,
/// Server title.
pub host: T, pub host: T,
/// Server protocol version.
pub protocol: u8, pub protocol: u8,
/// Current number of players on the server.
pub numcl: u8, pub numcl: u8,
/// Maximum number of players on the server.
pub maxcl: u8, pub maxcl: u8,
/// Server is running a deathmatch game mode.
pub dm: bool, pub dm: bool,
/// Players are grouped into teams.
pub team: bool, pub team: bool,
/// Server is running in a co-op game mode.
pub coop: bool, pub coop: bool,
/// Server is behind a password.
pub password: bool, pub password: bool,
} }
impl GetServerInfoResponse<()> { impl GetServerInfoResponse<()> {
/// Packet header.
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffinfo\n"; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffinfo\n";
} }
@ -371,6 +449,7 @@ impl<'a, T> GetServerInfoResponse<T>
where where
T: 'a + Default + GetKeyValue<'a>, T: 'a + Default + GetKeyValue<'a>,
{ {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(GetServerInfoResponse::HEADER)?; cur.expect(GetServerInfoResponse::HEADER)?;
@ -421,6 +500,7 @@ where
} }
impl<'a> GetServerInfoResponse<&'a str> { impl<'a> GetServerInfoResponse<&'a str> {
/// Encode packet to `buf`.
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(GetServerInfoResponse::HEADER)? .put_bytes(GetServerInfoResponse::HEADER)?
@ -438,15 +518,21 @@ impl<'a> GetServerInfoResponse<&'a str> {
} }
} }
/// Game server packet.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Packet<'a> { pub enum Packet<'a> {
/// Sended to a master server before `ServerAdd` packet.
Challenge(Challenge), Challenge(Challenge),
/// Add/update game server information on the master server.
ServerAdd(ServerAdd<Str<&'a [u8]>>), ServerAdd(ServerAdd<Str<&'a [u8]>>),
/// Remove the game server from a list.
ServerRemove, ServerRemove,
/// Game server information to game clients.
GetServerInfoResponse(GetServerInfoResponse<Str<&'a [u8]>>), GetServerInfoResponse(GetServerInfoResponse<Str<&'a [u8]>>),
} }
impl<'a> Packet<'a> { impl<'a> Packet<'a> {
/// Decode packet from `src`.
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
if let Ok(p) = Challenge::decode(src) { if let Ok(p) = Challenge::decode(src) {
return Ok(Self::Challenge(p)); return Ok(Self::Challenge(p));
@ -497,7 +583,6 @@ mod tests {
gamedir: "valve", gamedir: "valve",
map: "crossfire", map: "crossfire",
version: Version::new(0, 20), version: Version::new(0, 20),
product: "foobar",
challenge: 0x12345678, challenge: 0x12345678,
server_type: ServerType::Dedicated, server_type: ServerType::Dedicated,
os: Os::Linux, os: Os::Linux,

8
protocol/src/server_info.rs

@ -5,17 +5,25 @@ use super::filter::{FilterFlags, Version};
use super::server::{Region, ServerAdd}; use super::server::{Region, ServerAdd};
use super::types::Str; use super::types::Str;
/// Game server information.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ServerInfo { pub struct ServerInfo {
/// Server version.
pub version: Version, pub version: Version,
/// Server protocol version.
pub protocol: u8, pub protocol: u8,
/// Server midification.
pub gamedir: Box<[u8]>, pub gamedir: Box<[u8]>,
/// Server map.
pub map: Box<[u8]>, pub map: Box<[u8]>,
/// Server additional filter flags.
pub flags: FilterFlags, pub flags: FilterFlags,
/// Server region.
pub region: Region, pub region: Region,
} }
impl ServerInfo { impl ServerInfo {
/// Creates a new `ServerInfo`.
pub fn new(info: &ServerAdd<Str<&[u8]>>) -> Self { pub fn new(info: &ServerAdd<Str<&[u8]>>) -> Self {
Self { Self {
version: info.version, version: info.version,

22
protocol/src/types.rs

@ -1,10 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
//! Wrappers for byte slices with pretty-printers.
use std::fmt; use std::fmt;
use std::ops::Deref; use std::ops::Deref;
/// Wrapper for slice of bytes with printing the bytes as a string /// Wrapper for slice of bytes with printing the bytes as a string.
///
/// # Examples
///
/// ```rust
/// # use xash3d_protocol::types::Str;
/// let s = format!("{}", Str(b"\xff\talex\n"));
/// assert_eq!(s, "\\xff\\talex\\n");
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Default)] #[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct Str<T>(pub T); pub struct Str<T>(pub T);
@ -51,7 +61,15 @@ impl<T> Deref for Str<T> {
} }
} }
/// Wrapper for slice of bytes without printing /// Wrapper for slice of bytes without printing.
///
/// # Examples
///
/// ```rust
/// # use xash3d_protocol::types::Hide;
/// let s = format!("{}", Hide([1, 2, 3, 4]));
/// assert_eq!(s, "<hidden>");
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Default)] #[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct Hide<T>(pub T); pub struct Hide<T>(pub T);

Loading…
Cancel
Save