From 6c1fdb1159eb2b3460e71a51cde4363fe8ee045a Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:13:12 +0000 Subject: [PATCH] [Generic] Add struct for all extra request settings (#93) * [Generic] Add struct for all extra request settings Adds a new struct `ExtraRequestSettings` that contains all possible extra settings for all protocols, and can be implicitly converted with `.into()` into each protocol's extra settings type. This is then used in a new query method `query_with_timeout_and_extra_settings()` that passes the object on to the selected protocol. This also updates the generic example to set some of these generic settings so that it can be used for certain queries like "mc.hypixel.net". * [Minecraft] Add `request_settings` parameter to auto query This allows generic queries to pass through request settings when using the `mc` game so that servers like `mc.hypixel.net` will still work when using auto query. * Fix generic examples tests (and enable example tests in pre-commit) --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 7 ++ examples/generic.rs | 25 +++++--- src/games/mod.rs | 41 ++++++++++-- src/protocols/minecraft/protocol/mod.rs | 8 ++- src/protocols/minecraft/types.rs | 12 +++- src/protocols/mod.rs | 2 +- src/protocols/types.rs | 85 +++++++++++++++++++++++++ src/protocols/valve/types.rs | 13 +++- 9 files changed, 175 insertions(+), 20 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e197bc..7449aee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: language: system files: '[.]rs$' pass_filenames: false - entry: cargo test + entry: cargo test --bins --lib --examples --all-features - id: format name: Check rustfmt language: system diff --git a/CHANGELOG.md b/CHANGELOG.md index 3753d06..dba38c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ Crate: - Rich errors, capturing backtrace is done on `RUST_BACKTRACE=1`. - Applied some nursery Clippy lints. +Generics: +- Added `ExtraRequestSettings` containing all possible extra request settings. +- Added `query_with_timeout_and_extra_settings()` to allow generic queries with extra settings. + ### Breaking... Crate: - The enum used for errors, `GDError` has been renamed to `GDErrorKind`. @@ -56,6 +60,9 @@ Protocols: - Minecraft Bedrock 1. Renamed `version_protocol` to `protocol_version`. +- Minecraft: +1. Added `request_settings` parameter to `query()` + - The Ship: 1. Renamed `protocol` to `protocol_version`. 2. Renamed `max_players` to `players_maximum` and changed its type from `u64` to `u32`. diff --git a/examples/generic.rs b/examples/generic.rs index 5b9260c..bcad9d8 100644 --- a/examples/generic.rs +++ b/examples/generic.rs @@ -1,6 +1,6 @@ use gamedig::{ - protocols::types::{CommonResponse, TimeoutSettings}, - query_with_timeout, + protocols::types::{CommonResponse, ExtraRequestSettings, TimeoutSettings}, + query_with_timeout_and_extra_settings, GDResult, GAMES, }; @@ -13,6 +13,7 @@ fn generic_query( addr: &IpAddr, port: Option, timeout_settings: Option, + extra_settings: Option, ) -> GDResult> { let game = GAMES .get(game_name) @@ -20,7 +21,7 @@ fn generic_query( println!("Querying {:#?} with game {:#?}.", addr, game); - let response = query_with_timeout(game, addr, port, timeout_settings)?; + let response = query_with_timeout_and_extra_settings(game, addr, port, timeout_settings, extra_settings)?; println!("Response: {:#?}", response.as_json()); let common = response.as_original(); @@ -34,16 +35,22 @@ fn main() { // Handle arguments if let Some(game_name) = args.next() { + let hostname = args.next().expect("Must provide an address"); // Use to_socket_addrs to resolve hostname to IP - let addr: SocketAddr = args - .next() - .map(|s| format!("{}:0", s).to_socket_addrs().unwrap()) - .expect("Must provide address") + let addr: SocketAddr = format!("{}:0", hostname) + .to_socket_addrs() + .unwrap() .next() .expect("Could not lookup host"); let port: Option = args.next().map(|s| s.parse().unwrap()); - generic_query(&game_name, &addr.ip(), port, None).unwrap(); + let extra_settings = ExtraRequestSettings::default() + .set_hostname(hostname.to_string()) + .set_gather_rules(true) + .set_gather_players(true) + .set_check_app_id(false); + + generic_query(&game_name, &addr.ip(), port, None, Some(extra_settings)).unwrap(); } else { // Without arguments print a list of games @@ -68,7 +75,7 @@ mod test { fn test_game(game_name: &str) { let timeout_settings = Some(TimeoutSettings::new(Some(Duration::from_nanos(1)), Some(Duration::from_nanos(1))).unwrap()); - assert!(generic_query(game_name, &ADDR, None, timeout_settings).is_err()); + assert!(generic_query(game_name, &ADDR, None, timeout_settings, None).is_err()); } #[test] diff --git a/src/games/mod.rs b/src/games/mod.rs index 16a4c67..9a87049 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -118,7 +118,7 @@ pub mod warsow; use crate::protocols::gamespy::GameSpyVersion; use crate::protocols::quake::QuakeVersion; -use crate::protocols::types::{CommonResponse, ProprietaryProtocol, TimeoutSettings}; +use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, TimeoutSettings}; use crate::protocols::{self, Protocol}; use crate::GDResult; use std::net::{IpAddr, SocketAddr}; @@ -142,26 +142,50 @@ mod definitions; pub use definitions::GAMES; /// Make a query given a game definition +#[inline] pub fn query(game: &Game, address: &IpAddr, port: Option) -> GDResult> { - query_with_timeout(game, address, port, None) + query_with_timeout_and_extra_settings(game, address, port, None, None) } /// Make a query given a game definition and timeout settings +#[inline] pub fn query_with_timeout( game: &Game, address: &IpAddr, port: Option, timeout_settings: Option, +) -> GDResult> { + query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None) +} + +/// Make a query given a game definition, timeout settings, and extra settings +pub fn query_with_timeout_and_extra_settings( + game: &Game, + address: &IpAddr, + port: Option, + timeout_settings: Option, + extra_settings: Option, ) -> GDResult> { let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port)); Ok(match &game.protocol { Protocol::Valve(steam_app) => { - protocols::valve::query(&socket_addr, steam_app.as_engine(), None, timeout_settings).map(Box::new)? + protocols::valve::query( + &socket_addr, + steam_app.as_engine(), + extra_settings.map(ExtraRequestSettings::into), + timeout_settings, + ) + .map(Box::new)? } Protocol::Minecraft(version) => { match version { Some(protocols::minecraft::Server::Java) => { - protocols::minecraft::query_java(&socket_addr, timeout_settings, None).map(Box::new)? + protocols::minecraft::query_java( + &socket_addr, + timeout_settings, + extra_settings.map(ExtraRequestSettings::into), + ) + .map(Box::new)? } Some(protocols::minecraft::Server::Bedrock) => { protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)? @@ -169,7 +193,14 @@ pub fn query_with_timeout( Some(protocols::minecraft::Server::Legacy(group)) => { protocols::minecraft::query_legacy_specific(*group, &socket_addr, timeout_settings).map(Box::new)? } - None => protocols::minecraft::query(&socket_addr, timeout_settings).map(Box::new)?, + None => { + protocols::minecraft::query( + &socket_addr, + timeout_settings, + extra_settings.map(ExtraRequestSettings::into), + ) + .map(Box::new)? + } } } Protocol::Gamespy(version) => { diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 5b79e07..00e9eba 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -26,8 +26,12 @@ mod legacy_v1_6; /// Queries a Minecraft server with all the protocol variants one by one (Java /// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &SocketAddr, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_java(address, timeout_settings.clone(), None) { +pub fn query( + address: &SocketAddr, + timeout_settings: Option, + request_settings: Option, +) -> GDResult { + if let Ok(response) = query_java(address, timeout_settings.clone(), request_settings) { return Ok(response); } diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 1ddfb0f..0a9b49d 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -5,7 +5,7 @@ use crate::{ buffer::Buffer, protocols::{ - types::{CommonPlayer, CommonResponse, GenericPlayer}, + types::{CommonPlayer, CommonResponse, ExtraRequestSettings, GenericPlayer}, GenericResponse, }, GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast}, @@ -110,6 +110,16 @@ impl Default for RequestSettings { } } +impl From for RequestSettings { + fn from(value: ExtraRequestSettings) -> Self { + let default = Self::default(); + Self { + hostname: value.hostname.unwrap_or(default.hostname), + protocol_version: value.protocol_version.unwrap_or(default.protocol_version), + } + } +} + impl CommonResponse for JavaResponse { fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) } diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index efb820a..1b5128f 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -15,4 +15,4 @@ pub mod types; /// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) pub mod valve; -pub use types::{GenericResponse, Protocol}; +pub use types::{ExtraRequestSettings, GenericResponse, Protocol}; diff --git a/src/protocols/types.rs b/src/protocols/types.rs index ed8a8de..adacdcc 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -185,6 +185,82 @@ impl Default for TimeoutSettings { } } +/// Generic extra request settings +/// +/// Fields of this struct may not be used depending on which protocol is +/// selected, the individual fields link to the specific places they will be +/// used with additional documentation. +/// +/// ## Examples +/// Create minecraft settings with builder: +/// ``` +/// use gamedig::protocols::{minecraft, ExtraRequestSettings}; +/// let mc_settings: minecraft::RequestSettings = ExtraRequestSettings::default().set_hostname("mc.hypixel.net".to_string()).into(); +/// ``` +/// +/// Create valve settings with builder: +/// ``` +/// use gamedig::protocols::{valve, ExtraRequestSettings}; +/// let valve_settings: valve::GatheringSettings = ExtraRequestSettings::default().set_check_app_id(false).into(); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct ExtraRequestSettings { + /// The server's hostname. + /// + /// Used by: + /// - [minecraft::RequestSettings#structfield.hostname] + pub hostname: Option, + /// The protocol version to use. + /// + /// Used by: + /// - [minecraft::RequestSettings#structfield.protocol_version] + pub protocol_version: Option, + /// Whether to gather player information + /// + /// Used by: + /// - [valve::GatheringSettings#structfield.players] + pub gather_players: Option, + /// Whether to gather rule information. + /// + /// Used by: + /// - [valve::GatheringSettings#structfield.rules] + pub gather_rules: Option, + /// Whether to check if the App ID is valid. + /// + /// Used by: + /// - [valve::GatheringSettings#structfield.check_app_id] + pub check_app_id: Option, +} + +impl ExtraRequestSettings { + /// [Sets hostname](ExtraRequestSettings#structfield.hostname) + pub fn set_hostname(mut self, hostname: String) -> Self { + self.hostname = Some(hostname); + self + } + /// [Sets protocol + /// version](ExtraRequestSettings#structfield.protocol_version) + pub fn set_protocol_version(mut self, protocol_version: i32) -> Self { + self.protocol_version = Some(protocol_version); + self + } + /// [Sets gather players](ExtraRequestSettings#structfield.gather_players) + pub fn set_gather_players(mut self, gather_players: bool) -> Self { + self.gather_players = Some(gather_players); + self + } + /// [Sets gather rules](ExtraRequestSettings#structfield.gather_rules) + pub fn set_gather_rules(mut self, gather_rules: bool) -> Self { + self.gather_rules = Some(gather_rules); + self + } + /// [Sets check app ID](ExtraRequestSettings#structfield.check_app_id) + pub fn set_check_app_id(mut self, check_app_id: bool) -> Self { + self.check_app_id = Some(check_app_id); + self + } +} + #[cfg(test)] mod tests { use super::*; @@ -235,4 +311,13 @@ mod tests { assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4))); assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4))); } + + // Test that extra request settings can be converted + #[test] + fn test_extra_request_settings() { + let settings = ExtraRequestSettings::default(); + + let _: minecraft::RequestSettings = settings.clone().into(); + let _: valve::GatheringSettings = settings.into(); + } } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 500aa07..1f754b8 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer}; +use crate::protocols::types::{CommonPlayer, CommonResponse, ExtraRequestSettings, GenericPlayer}; use crate::GDErrorKind::UnknownEnumCast; use crate::GDResult; use crate::{buffer::Buffer, protocols::GenericResponse}; @@ -430,6 +430,17 @@ impl Default for GatheringSettings { } } +impl From for GatheringSettings { + fn from(value: ExtraRequestSettings) -> Self { + let default = Self::default(); + Self { + players: value.gather_players.unwrap_or(default.players), + rules: value.gather_rules.unwrap_or(default.rules), + check_app_id: value.check_app_id.unwrap_or(default.check_app_id), + } + } +} + /// Generic response types that are used by many games, they are the protocol /// ones, but without the unnecessary bits (example: the **The Ship**-only /// fields).