From 5e7e010d24c302cd0cac1fb2a40952789a8b279f Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Sat, 5 Aug 2023 09:36:48 +0000 Subject: [PATCH] [Crate] Add rich error type (#80) * Add rich error type with source and backtrace Adds a rich error type that will take a backtrace and allow capturing the source of the error. The best way to use this is with the included helpers that will automatically capture the backtrace and convert the source error: ``` GDRichError::packet_bad_from_into("Reason packet was bad") ``` * [Crate] Bump MSRV to 1.65.0 This is required for backtraces in rich errors. * Remove leftover debug logging * [Errors] Replace variant overloads with single .rich method on kind enum This overhaul replaces the exhaustive impls of each variant as multiple methods on the rich error type with a singular .rich() method on the kind enum. This consumes the variant and converts it to a rich error with a source (.into() can be used if a source is not needed). I also took the liberty of replacing all usages with the this new method as I saw fit (adding various error messages) and converting a few PacketBad errors to TypeParse errors when they are the result of parse failing. * Fix problem with rustdoc * Remove BadGame's owned string * Rename GDError to GDErrorKind * Rename GDRichError to GDError * Remove error impl from GDErrorKind * Fix tests not passing * Use error context everywhere map_err is used * Improve GDError display formatter * Add tests for new error type --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.toml | 2 +- README.md | 2 +- VERSIONS.md | 7 +- src/buffer.rs | 27 ++- src/errors.rs | 179 ++++++++++++++++-- src/games/bat1944.rs | 6 +- src/games/jc2mp.rs | 23 ++- src/games/mc.rs | 4 +- src/protocols/gamespy/common.rs | 8 +- .../gamespy/protocols/one/protocol.rs | 77 +++++--- .../gamespy/protocols/three/protocol.rs | 79 ++++---- .../gamespy/protocols/two/protocol.rs | 32 ++-- src/protocols/minecraft/protocol/bedrock.rs | 16 +- src/protocols/minecraft/protocol/java.rs | 6 +- .../minecraft/protocol/legacy_bv1_8.rs | 8 +- .../minecraft/protocol/legacy_v1_4.rs | 8 +- .../minecraft/protocol/legacy_v1_6.rs | 12 +- src/protocols/minecraft/protocol/mod.rs | 6 +- src/protocols/minecraft/types.rs | 8 +- src/protocols/quake/client.rs | 15 +- src/protocols/quake/one.rs | 31 +-- src/protocols/quake/two.rs | 13 +- src/protocols/types.rs | 12 +- src/protocols/valve/protocol.rs | 16 +- src/protocols/valve/types.rs | 2 +- src/services/valve_master_server/service.rs | 4 +- src/socket.rs | 17 +- src/utils.rs | 10 +- 30 files changed, 417 insertions(+), 217 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e445fb6..393bd00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Install MSRV uses: actions-rs/toolchain@v1 with: - toolchain: 1.60.0 + toolchain: 1.65.0 override: true - name: Run MSRV run: cargo build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d46e58..9e197bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: language: system files: '[.]rs$' pass_filenames: false - entry: rustup run --install 1.60 cargo build + entry: rustup run --install 1.65 cargo build - id: docs name: Check rustdoc compiles language: system diff --git a/Cargo.toml b/Cargo.toml index 5963289..25eac41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ documentation = "https://docs.rs/gamedig/latest/gamedig/" repository = "https://github.com/gamedig/rust-gamedig" readme = "README.md" keywords = ["server", "query", "game", "check", "status"] -rust-version = "1.60.0" +rust-version = "1.65.0" [features] default = ["games", "services", "game_defs"] diff --git a/README.md b/README.md index 658aec9..72930b6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ within the library or want to request a feature, it's better to do so here rathe on Discord. ## Usage -Minimum Supported Rust Version is `1.60.0` and the code is cross-platform. +Minimum Supported Rust Version is `1.65.0` and the code is cross-platform. Pick a game/service/protocol (check the [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) files to see the currently supported ones), provide the ip and the port (be aware that some game servers use a separate port for the info queries, the port can also be optional if the server is running the default ports) then query on it. diff --git a/VERSIONS.md b/VERSIONS.md index b64bf5a..5e111d9 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -1,11 +1,12 @@ # MSRV (Minimum Supported Rust Version) -Current: `1.60.0` +Current: `1.65.0` Places to update: - `Cargo.toml` - `README.md` - `.github/workflows/ci.yml` +- `.pre-commit-config.yaml` # rustfmt version @@ -22,5 +23,5 @@ The toolchain version used to run rustfmt in CI Current: `nightly-2023-07-09` Places to update: -- `./.github/workflows/ci.yml` -- `./.pre-commit-config.yaml` +- `.github/workflows/ci.yml` +- `.pre-commit-config.yaml` diff --git a/src/buffer.rs b/src/buffer.rs index 692f547..9eac3f2 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,5 @@ -use crate::GDError::{PacketBad, PacketUnderflow}; +use crate::GDErrorKind::PacketBad; +use crate::GDErrorKind::PacketUnderflow; use crate::GDResult; use byteorder::{BigEndian, ByteOrder, LittleEndian}; use std::{convert::TryInto, marker::PhantomData}; @@ -69,12 +70,12 @@ impl<'a, B: ByteOrder> Buffer<'a, B> { match new_cursor { // If the addition was not successful (i.e., it resulted in an overflow or underflow), // return an error indicating that the cursor is out of bounds. - None => Err(PacketBad), + None => Err(PacketBad.into()), // If the new cursor position is either less than zero (i.e., before the start of the buffer) // or greater than the remaining length of the buffer (i.e., past the end of the buffer), // return an error indicating that the cursor is out of bounds. - Some(x) if x < 0 || x as usize > self.data_length() => Err(PacketBad), + Some(x) if x < 0 || x as usize > self.data_length() => Err(PacketBad.into()), // If the new cursor position is within the bounds of the buffer, update the cursor // position and return Ok. @@ -107,7 +108,10 @@ impl<'a, B: ByteOrder> Buffer<'a, B> { // If the size of `T` is larger than the remaining length, return an error // because we don't have enough data left to read. if size > remaining { - return Err(PacketUnderflow); + return Err(PacketUnderflow.context(format!( + "Size requested {} was larger than remaining bytes {}", + size, remaining + ))); } // Slice the data array from the current cursor position for `size` amount of @@ -242,7 +246,7 @@ macro_rules! impl_buffer_read_byte { .map($map_func) // If the data array is empty (and thus `first` returns None), // `ok_or_else` will return a BufferError. - .ok_or_else(|| PacketBad) + .ok_or_else(|| PacketBad.into()) } } }; @@ -264,10 +268,10 @@ macro_rules! impl_buffer_read { impl BufferRead for $type { fn read_from_buffer(data: &[u8]) -> GDResult { // Convert the byte slice into an array of the appropriate type. - let array = data.try_into().map_err(|_| { + let array = data.try_into().map_err(|e| { // If conversion fails, return an error indicating the required and provided // lengths. - PacketBad + PacketBad.context(e) })?; // Use the provided function to read the data from the array into the given @@ -345,7 +349,7 @@ impl StringDecoder for Utf8Decoder { &data[.. position] ) // If the data cannot be converted into a UTF-8 string, return an error - .map_err(|_| PacketBad)? + .map_err(|e| PacketBad.context(e))? // Convert the resulting &str into a String .to_owned(); @@ -393,7 +397,7 @@ impl StringDecoder for Utf16Decoder { B::read_u16_into(&data[.. position], &mut paired_buf); // Convert the buffer of u16 values into a String - let result = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?; + let result = String::from_utf16(&paired_buf).map_err(|e| PacketBad.context(e))?; // Update the cursor position // The +2 accounts for the delimiter @@ -543,6 +547,9 @@ mod tests { let mut buffer = Buffer::::new(data); let result: Result = buffer.read(); - assert_eq!(result.unwrap_err(), PacketUnderflow); + assert_eq!( + result.unwrap_err(), + crate::GDErrorKind::PacketUnderflow.into() + ); } } diff --git a/src/errors.rs b/src/errors.rs index 80101a5..95263d8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,4 +1,5 @@ use std::{ + backtrace, error::Error, fmt::{self, Formatter}, }; @@ -8,7 +9,7 @@ pub type GDResult = Result; /// GameDig Error. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum GDError { +pub enum GDErrorKind { /// The received packet was bigger than the buffer size. PacketOverflow, /// The received packet was shorter than the expected one. @@ -28,7 +29,7 @@ pub enum GDError { /// Invalid input. InvalidInput, /// The server queried is not the queried game server. - BadGame(String), + BadGame, /// Couldn't automatically query. AutoQuery, /// A protocol-defined expected format was not met. @@ -41,12 +42,108 @@ pub enum GDError { TypeParse, } -impl Error for GDError {} +impl GDErrorKind { + /// Convert error kind into a full error with a source (and implicit + /// backtrace) + /// + /// ``` + /// use gamedig::{GDErrorKind, GDResult}; + /// let _: GDResult = "thing".parse().map_err(|e| GDErrorKind::TypeParse.context(e)); + /// ``` + pub fn context>>(self, source: E) -> GDError { + GDError::from_error(self, source) + } +} + +type ErrorSource = Box; + +/// Gamedig error type +/// +/// Can be created in three ways (all of which will implicitly generate a +/// backtrace): +/// +/// Directly from an [error kind](crate::errors::GDErrorKind) (without a source) +/// +/// ``` +/// use gamedig::{GDError, GDErrorKind}; +/// let _: GDError = GDErrorKind::PacketBad.into(); +/// ``` +/// +/// [From an error kind with a source](crate::errors::GDErrorKind::context) (any +/// type that implements `Into>) +/// +/// ``` +/// use gamedig::{GDError, GDErrorKind}; +/// let _: GDError = GDErrorKind::PacketBad.context("Reason the packet was bad"); +/// ``` +/// +/// Using the [new helper](crate::errors::GDError::new) +/// +/// ``` +/// use gamedig::{GDError, GDErrorKind}; +/// let _: GDError = GDError::new(GDErrorKind::PacketBad, Some("Reason the packet was bad".into())); +/// ``` +pub struct GDError { + pub kind: GDErrorKind, + pub source: Option, + pub backtrace: Option, +} + +impl From for GDError { + fn from(value: GDErrorKind) -> Self { + let backtrace = Some(backtrace::Backtrace::capture()); + Self { + kind: value, + source: None, + backtrace, + } + } +} + +impl PartialEq for GDError { + fn eq(&self, other: &Self) -> bool { self.kind == other.kind } +} + +impl Error for GDError { + fn source(&self) -> Option<&(dyn Error + 'static)> { self.source.as_ref().map(Box::as_ref) } +} + +impl fmt::Debug for GDError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "GDError{{ kind={:?}", self.kind)?; + if let Some(source) = &self.source { + writeln!(f, " source={:?}", source)?; + } + if let Some(backtrace) = &self.backtrace { + let bt = format!("{:#?}", backtrace); + writeln!(f, " backtrace={}", bt.replace('\n', "\n "))?; + } + writeln!(f, "}}")?; + Ok(()) + } +} impl fmt::Display for GDError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } +impl GDError { + /// Create a new error (with automatic backtrace) + pub fn new(kind: GDErrorKind, source: Option) -> Self { + let backtrace = Some(std::backtrace::Backtrace::capture()); + Self { + kind, + source, + backtrace, + } + } + + /// Create a new error using any type that can be converted to an error + pub fn from_error>>(kind: GDErrorKind, source: E) -> Self { + Self::new(kind, Some(source.into())) + } +} + #[cfg(test)] mod tests { use super::*; @@ -61,29 +158,71 @@ mod tests { // Testing Err variant of the GDResult type #[test] fn test_gdresult_err() { - let result: GDResult = Err(GDError::InvalidInput); + let result: GDResult = Err(GDErrorKind::InvalidInput.into()); assert!(result.is_err()); } - // Testing the Display trait for the GDError type - #[test] - fn test_display() { - let error = GDError::PacketOverflow; - assert_eq!(format!("{}", error), "PacketOverflow"); - } - - // Testing the Error trait for the GDError type - #[test] - fn test_error_trait() { - let error = GDError::PacketBad; - assert!(error.source().is_none()); - } - - // Testing cloning the GDError type + // Testing cloning the GDErrorKind type #[test] fn test_cloning() { - let error = GDError::BadGame(String::from("MyGame")); + let error = GDErrorKind::BadGame; let cloned_error = error.clone(); assert_eq!(error, cloned_error); } + + // test display GDError + #[test] + fn test_display() { + let err = GDErrorKind::BadGame.context("Rust is not a game"); + let s = format!("{}", err); + println!("{}", s); + assert_eq!( + s, + "GDError{ kind=BadGame\n source=\"Rust is not a game\"\n backtrace=\n}\n" + ); + } + + // test error trait GDError + #[test] + fn test_error_trait() { + let source: Result = "nan".parse(); + let source_err = source.unwrap_err(); + + let error_with_context = GDErrorKind::TypeParse.context(source_err.clone()); + assert!(error_with_context.source().is_some()); + assert_eq!( + format!("{}", error_with_context.source().unwrap()), + format!("{}", source_err) + ); + + let error_without_context: GDError = GDErrorKind::TypeParse.into(); + assert!(error_without_context.source().is_none()); + } + + // Test creating GDError with GDError::new + #[test] + fn test_create_new() { + let error_from_new = GDError::new(GDErrorKind::InvalidInput, None); + assert!(error_from_new.backtrace.is_some()); + assert_eq!(error_from_new.kind, GDErrorKind::InvalidInput); + assert!(error_from_new.source.is_none()); + } + + // Test creating GDError with GDErrorKind::context + #[test] + fn test_create_context() { + let error_from_context = GDErrorKind::InvalidInput.context("test"); + assert!(error_from_context.backtrace.is_some()); + assert_eq!(error_from_context.kind, GDErrorKind::InvalidInput); + assert!(error_from_context.source.is_some()); + } + + // Test creating GDError with From for GDError + #[test] + fn test_create_into() { + let error_from_into: GDError = GDErrorKind::InvalidInput.into(); + assert!(error_from_into.backtrace.is_some()); + assert_eq!(error_from_into.kind, GDErrorKind::InvalidInput); + assert!(error_from_into.source.is_none()); + } } diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index 54b0295..f5f61cc 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,6 +1,6 @@ use crate::{ protocols::valve::{self, game, SteamApp}, - GDError::TypeParse, + GDErrorKind::TypeParse, GDResult, }; use std::net::{IpAddr, SocketAddr}; @@ -15,12 +15,12 @@ pub fn query(address: &IpAddr, port: Option) -> GDResult { if let Some(rules) = &mut valve_response.rules { if let Some(bat_max_players) = rules.get("bat_max_players_i") { - valve_response.info.players_maximum = bat_max_players.parse().map_err(|_| TypeParse)?; + valve_response.info.players_maximum = bat_max_players.parse().map_err(|e| TypeParse.context(e))?; rules.remove("bat_max_players_i"); } if let Some(bat_player_count) = rules.get("bat_player_count_s") { - valve_response.info.players_online = bat_player_count.parse().map_err(|_| TypeParse)?; + valve_response.info.players_online = bat_player_count.parse().map_err(|e| TypeParse.context(e))?; rules.remove("bat_player_count_s"); } diff --git a/src/games/jc2mp.rs b/src/games/jc2mp.rs index 4a8aaff..a1cfe49 100644 --- a/src/games/jc2mp.rs +++ b/src/games/jc2mp.rs @@ -3,7 +3,8 @@ use crate::protocols::gamespy::common::has_password; use crate::protocols::gamespy::three::{data_to_map, GameSpy3}; use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings}; use crate::protocols::GenericResponse; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::{PacketBad, TypeParse}; +use crate::{GDErrorKind, GDResult}; use byteorder::BigEndian; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -91,20 +92,22 @@ pub fn query_with_timeout( )?; let packets = client.get_server_packets()?; - let data = packets.get(0).ok_or(GDError::PacketBad)?; + let data = packets + .get(0) + .ok_or(PacketBad.context("First packet missing"))?; let (mut server_vars, remaining_data) = data_to_map(data)?; let players = parse_players_and_teams(remaining_data)?; let players_maximum = server_vars .remove("maxplayers") - .ok_or(GDError::PacketBad)? + .ok_or(PacketBad.context("Server variables missing maxplayers"))? .parse() - .map_err(|_| GDError::TypeParse)?; + .map_err(|e| TypeParse.context(e))?; let players_online = match server_vars.remove("numplayers") { None => players.len(), Some(v) => { - let reported_players = v.parse().map_err(|_| GDError::TypeParse)?; + let reported_players = v.parse().map_err(|e| TypeParse.context(e))?; match reported_players < players.len() { true => players.len(), false => reported_players, @@ -113,11 +116,15 @@ pub fn query_with_timeout( }; Ok(Response { - version: server_vars.remove("version").ok_or(GDError::PacketBad)?, + version: server_vars + .remove("version") + .ok_or(GDErrorKind::PacketBad)?, description: server_vars .remove("description") - .ok_or(GDError::PacketBad)?, - name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, + .ok_or(GDErrorKind::PacketBad)?, + name: server_vars + .remove("hostname") + .ok_or(GDErrorKind::PacketBad)?, has_password: has_password(&mut server_vars)?, players, players_maximum, diff --git a/src/games/mc.rs b/src/games/mc.rs index cbd7900..368f851 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,6 +1,6 @@ use crate::{ protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, - GDError, + GDErrorKind, GDResult, }; use std::net::{IpAddr, SocketAddr}; @@ -20,7 +20,7 @@ pub fn query(address: &IpAddr, port: Option) -> GDResult { return Ok(response); } - Err(GDError::AutoQuery) + Err(GDErrorKind::AutoQuery.into()) } /// Query a Java Server. diff --git a/src/protocols/gamespy/common.rs b/src/protocols/gamespy/common.rs index 5c1950d..9f78e17 100644 --- a/src/protocols/gamespy/common.rs +++ b/src/protocols/gamespy/common.rs @@ -1,17 +1,19 @@ -use crate::{GDError, GDResult}; +use crate::{GDErrorKind, GDResult}; use std::collections::HashMap; pub fn has_password(server_vars: &mut HashMap) -> GDResult { let password_value = server_vars .remove("password") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad.context("Missing password (exists) field"))? .to_lowercase(); if let Ok(has) = password_value.parse::() { return Ok(has); } - let as_numeral: u8 = password_value.parse().map_err(|_| GDError::TypeParse)?; + let as_numeral: u8 = password_value + .parse() + .map_err(|e| GDErrorKind::TypeParse.context(e))?; Ok(as_numeral != 0) } diff --git a/src/protocols/gamespy/protocols/one/protocol.rs b/src/protocols/gamespy/protocols/one/protocol.rs index 487eec1..a2737bb 100644 --- a/src/protocols/gamespy/protocols/one/protocol.rs +++ b/src/protocols/gamespy/protocols/one/protocol.rs @@ -2,6 +2,8 @@ use byteorder::LittleEndian; use crate::buffer::Utf8Decoder; use crate::protocols::gamespy::common::has_password; +use crate::GDErrorKind::TypeParse; + use crate::{ buffer::Buffer, protocols::{ @@ -9,7 +11,7 @@ use crate::{ types::TimeoutSettings, }, socket::{Socket, UdpSocket}, - GDError, + GDErrorKind, GDResult, }; use std::collections::HashMap; @@ -59,25 +61,25 @@ fn get_server_values( if let Some(qid) = query_data { let split: Vec<&str> = qid.split('.').collect(); - query_id = Some(split[0].parse().map_err(|_| GDError::TypeParse)?); + query_id = Some(split[0].parse().map_err(|e| TypeParse.context(e))?); match split.len() { 1 => (), - 2 => part = split[1].parse().map_err(|_| GDError::TypeParse)?, - _ => Err(GDError::PacketBad)?, /* the queryid can't be splitted in more than 2 - * elements */ + 2 => part = split[1].parse().map_err(|e| TypeParse.context(e))?, + _ => Err(GDErrorKind::PacketBad)?, /* the queryid can't be splitted in more than 2 + * elements */ }; } server_values.remove("queryid"); if received_query_id.is_some() && received_query_id != query_id { - return Err(GDError::PacketBad); // wrong query id! + return Err(GDErrorKind::PacketBad.into()); // wrong query id! } else { received_query_id = query_id; } match parts.contains(&part) { - true => Err(GDError::PacketBad)?, + true => Err(GDErrorKind::PacketBad)?, false => parts.push(part), } } @@ -128,45 +130,54 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u None => { player_data .get("playername") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .clone() } }, team: player_data .get("team") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .trim() .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, ping: player_data .get("ping") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .trim() .parse() - .map_err(|_| GDError::TypeParse)?, - face: player_data.get("face").ok_or(GDError::PacketBad)?.clone(), - skin: player_data.get("skin").ok_or(GDError::PacketBad)?.clone(), - mesh: player_data.get("mesh").ok_or(GDError::PacketBad)?.clone(), + .map_err(|e| TypeParse.context(e))?, + face: player_data + .get("face") + .ok_or(GDErrorKind::PacketBad)? + .clone(), + skin: player_data + .get("skin") + .ok_or(GDErrorKind::PacketBad)? + .clone(), + mesh: player_data + .get("mesh") + .ok_or(GDErrorKind::PacketBad)? + .clone(), score: player_data .get("frags") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .trim() .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, deaths: match player_data.get("deaths") { - Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), + Some(v) => Some(v.trim().parse().map_err(|e| TypeParse.context(e))?), None => None, }, health: match player_data.get("health") { - Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), + Some(v) => Some(v.trim().parse().map_err(|e| TypeParse.context(e))?), None => None, }, secret: player_data .get("ngsecret") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .to_lowercase() .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, }; players.push(new_player); @@ -192,27 +203,35 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> let players_maximum = server_vars .remove("maxplayers") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::TypeParse)?; + .map_err(|e| TypeParse.context(e))?; let players_minimum = match server_vars.remove("minplayers") { None => None, - Some(v) => Some(v.parse::().map_err(|_| GDError::TypeParse)?), + Some(v) => Some(v.parse::().map_err(|e| TypeParse.context(e))?), }; let players = extract_players(&mut server_vars, players_maximum)?; Ok(Response { - name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, - map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, + name: server_vars + .remove("hostname") + .ok_or(GDErrorKind::PacketBad)?, + map: server_vars + .remove("mapname") + .ok_or(GDErrorKind::PacketBad)?, map_title: server_vars.remove("maptitle"), admin_contact: server_vars.remove("AdminEMail"), admin_name: server_vars .remove("AdminName") .or_else(|| server_vars.remove("admin")), has_password: has_password(&mut server_vars)?, - game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, - game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, + game_type: server_vars + .remove("gametype") + .ok_or(GDErrorKind::PacketBad)?, + game_version: server_vars + .remove("gamever") + .ok_or(GDErrorKind::PacketBad)?, players_maximum, players_online: players.len(), players_minimum, @@ -222,7 +241,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> .unwrap_or_else(|| "true".to_string()) .to_lowercase() .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, unused_entries: server_vars, }) } diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index b2927d4..23ed7cf 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -5,7 +5,8 @@ use crate::protocols::gamespy::common::has_password; use crate::protocols::gamespy::three::{Player, Response, Team}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::{PacketBad, TypeParse}; +use crate::{GDErrorKind, GDResult}; use std::collections::HashMap; use std::net::SocketAddr; @@ -80,11 +81,11 @@ impl GameSpy3 { let mut buf = Buffer::::new(&received); if buf.read::()? != kind { - return Err(GDError::PacketBad); + return Err(PacketBad.context("Kind of packet did not match")); } if buf.read::()? != THIS_SESSION_ID { - return Err(GDError::PacketBad); + return Err(PacketBad.context("Session ID did not match")); } Ok(buf.remaining_bytes().to_vec()) @@ -108,7 +109,7 @@ impl GameSpy3 { let challenge_as_string = buf.read_string::(None)?; let challenge = challenge_as_string .parse() - .map_err(|_| GDError::TypeParse)?; + .map_err(|e| TypeParse.context(e))?; Ok(match challenge == 0 { true => None, @@ -147,7 +148,7 @@ impl GameSpy3 { } if buf.read_string::(None)? != "splitnum" { - return Err(GDError::PacketBad); + return Err(PacketBad.context("Expected string \"splitnum\"")); } let id = buf.read::()?; @@ -167,7 +168,7 @@ impl GameSpy3 { } if values.iter().any(|v| v.is_empty()) { - return Err(GDError::PacketBad); + return Err(PacketBad.context("One (or more) packets is empty")); } Ok(values) @@ -231,7 +232,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< } let field_split: Vec<&str> = field.split('_').collect(); - let field_name = field_split.first().ok_or(GDError::PacketBad)?; + let field_name = field_split.first().ok_or(GDErrorKind::PacketBad)?; if !["player", "score", "ping", "team", "deaths", "pid", "skill"].contains(field_name) { continue; } @@ -243,7 +244,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< true => None, false => { if v != &"t" { - Err(GDError::PacketBad)? + Err(GDErrorKind::PacketBad)? } Some(v) @@ -284,35 +285,32 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< } players.push(Player { - name: player_data - .get("player") - .ok_or(GDError::PacketBad)? - .to_string(), + name: player_data.get("player").ok_or(PacketBad)?.to_string(), score: player_data .get("score") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, ping: player_data .get("ping") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, team: player_data .get("team") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, deaths: player_data .get("deaths") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, skill: player_data .get("skill") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, }) } @@ -323,12 +321,15 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< } teams.push(Team { - name: team_data.get("team").ok_or(GDError::PacketBad)?.to_string(), + name: team_data + .get("team") + .ok_or(GDErrorKind::PacketBad)? + .to_string(), score: team_data .get("score") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, }) } @@ -342,7 +343,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> let mut client = GameSpy3::new(address, timeout_settings)?; let packets = client.get_server_packets()?; - let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?; + let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDErrorKind::PacketBad)?)?; let mut remaining_data_packets = vec![remaining_data]; remaining_data_packets.extend_from_slice(&packets[1 ..]); @@ -350,17 +351,17 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> let players_maximum = server_vars .remove("maxplayers") - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::TypeParse)?; + .map_err(|e| TypeParse.context(e))?; let players_minimum = match server_vars.remove("minplayers") { None => None, - Some(v) => Some(v.parse::().map_err(|_| GDError::TypeParse)?), + Some(v) => Some(v.parse::().map_err(|e| TypeParse.context(e))?), }; let players_online = match server_vars.remove("numplayers") { None => players.len(), Some(v) => { - let reported_players = v.parse().map_err(|_| GDError::TypeParse)?; + let reported_players = v.parse().map_err(|e| TypeParse.context(e))?; match reported_players < players.len() { true => players.len(), false => reported_players, @@ -369,11 +370,19 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> }; Ok(Response { - name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, - map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, + name: server_vars + .remove("hostname") + .ok_or(GDErrorKind::PacketBad)?, + map: server_vars + .remove("mapname") + .ok_or(GDErrorKind::PacketBad)?, has_password: has_password(&mut server_vars)?, - game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, - game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, + game_type: server_vars + .remove("gametype") + .ok_or(GDErrorKind::PacketBad)?, + game_version: server_vars + .remove("gamever") + .ok_or(GDErrorKind::PacketBad)?, players_maximum, players_online, players_minimum, @@ -384,7 +393,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> .unwrap_or_else(|| "true".to_string()) .to_lowercase() .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, unused_entries: server_vars, }) } diff --git a/src/protocols/gamespy/protocols/two/protocol.rs b/src/protocols/gamespy/protocols/two/protocol.rs index b3cc0b1..f1c3e55 100644 --- a/src/protocols/gamespy/protocols/two/protocol.rs +++ b/src/protocols/gamespy/protocols/two/protocol.rs @@ -2,7 +2,8 @@ use crate::buffer::{Buffer, Utf8Decoder}; use crate::protocols::gamespy::two::{Player, Response, Team}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::{PacketBad, TypeParse}; +use crate::{GDErrorKind, GDResult}; use byteorder::BigEndian; use std::collections::HashMap; use std::net::SocketAddr; @@ -15,9 +16,9 @@ macro_rules! table_extract { ($table:expr, $name:literal, $index:expr) => { $table .get($name) - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .get($index) - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? }; } @@ -25,13 +26,13 @@ macro_rules! table_extract_parse { ($table:expr, $name:literal, $index:expr) => { table_extract!($table, $name, $index) .parse() - .map_err(|_| GDError::PacketBad)? + .map_err(|e| PacketBad.context(e))? }; } fn data_as_table(data: &mut Buffer) -> GDResult<(HashMap>, usize)> { if data.read::()? != 0 { - Err(GDError::PacketBad)? + Err(GDErrorKind::PacketBad)? } let rows = data.read::()? as usize; @@ -64,7 +65,10 @@ fn data_as_table(data: &mut Buffer) -> GDResult<(HashMap(None)?; - table.get_mut(column).ok_or(GDError::PacketBad)?.push(value); + table + .get_mut(column) + .ok_or(GDErrorKind::PacketBad)? + .push(value); } } @@ -87,7 +91,7 @@ impl GameSpy2 { let mut buf = Buffer::::new(&received); if buf.read::()? != 0 || buf.read::()? != 1 { - return Err(GDError::PacketBad); + return Err(PacketBad.into()); } let buf_index = buf.current_position(); @@ -163,7 +167,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> let players_online = match server_vars.remove("numplayers") { None => players.len(), Some(v) => { - let reported_players = v.parse().map_err(|_| GDError::TypeParse)?; + let reported_players = v.parse().map_err(|e| TypeParse.context(e))?; match reported_players < players.len() { true => players.len(), false => reported_players, @@ -172,19 +176,19 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> }; let players_minimum = match server_vars.remove("minplayers") { None => None, - Some(v) => Some(v.parse::().map_err(|_| GDError::TypeParse)?), + Some(v) => Some(v.parse::().map_err(|e| TypeParse.context(e))?), }; Ok(Response { - name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, - map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, - has_password: server_vars.remove("password").ok_or(GDError::PacketBad)? == "1", + name: server_vars.remove("hostname").ok_or(PacketBad)?, + map: server_vars.remove("mapname").ok_or(PacketBad)?, + has_password: server_vars.remove("password").ok_or(PacketBad)? == "1", teams: get_teams(&mut buffer)?, players_maximum: server_vars .remove("maxplayers") - .ok_or(GDError::PacketBad)? + .ok_or(PacketBad)? .parse() - .map_err(|_| GDError::PacketBad)?, + .map_err(|e| TypeParse.context(e))?, players_online, players_minimum, players, diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index 46b3062..cbc8a0a 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -8,7 +8,7 @@ use crate::{ }, socket::{Socket, UdpSocket}, utils::error_by_expected_size, - GDError::{PacketBad, TypeParse}, + GDErrorKind::{PacketBad, TypeParse}, GDResult, }; @@ -46,12 +46,12 @@ impl Bedrock { let mut buffer = Buffer::::new(&received); if buffer.read::()? != 0x1c { - return Err(PacketBad); + return Err(PacketBad.context("Expected 0x1c")); } // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). if buffer.read::()? != 9833440827789222417 { - return Err(PacketBad); + return Err(PacketBad.context("Invalid nonce")); } // These 8 bytes are identical to the serverId string we receive in decimal @@ -60,11 +60,11 @@ impl Bedrock { // Verifying the magic value (as we need 16 bytes, cast to two u64 values) if buffer.read::()? != 18374403896610127616 { - return Err(PacketBad); + return Err(PacketBad.context("Invalid magic")); } if buffer.read::()? != 8671175388723805693 { - return Err(PacketBad); + return Err(PacketBad.context("Invalid magic")); } let remaining_length = buffer.switch_endian_chunk(2)?.read::()? as usize; @@ -76,7 +76,7 @@ impl Bedrock { // We must have at least 6 values if status.len() < 6 { - return Err(PacketBad); + return Err(PacketBad.context("Not enough values")); } Ok(BedrockResponse { @@ -84,8 +84,8 @@ impl Bedrock { name: status[1].to_string(), version_name: status[3].to_string(), version_protocol: status[2].to_string(), - players_maximum: status[5].parse().map_err(|_| TypeParse)?, - players_online: status[4].parse().map_err(|_| TypeParse)?, + players_maximum: status[5].parse().map_err(|e| TypeParse.context(e))?, + players_online: status[4].parse().map_err(|e| TypeParse.context(e))?, id: status.get(6).map(|v| v.to_string()), map: status.get(7).map(|v| v.to_string()), game_mode: match status.get(8) { diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index d014a0a..0e12735 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -5,7 +5,7 @@ use crate::{ types::TimeoutSettings, }, socket::{Socket, TcpSocket}, - GDError::{JsonParse, PacketBad}, + GDErrorKind::{JsonParse, PacketBad}, GDResult, }; @@ -90,11 +90,11 @@ impl Java { if get_varint(&mut buffer)? != 0 { // first var int is the packet id - return Err(PacketBad); + return Err(PacketBad.context("Expected 0")); } let json_response = get_string(&mut buffer)?; - let value_response: Value = serde_json::from_str(&json_response).map_err(|_| JsonParse)?; + let value_response: Value = serde_json::from_str(&json_response).map_err(|e| JsonParse.context(e))?; let version_name = value_response["version"]["name"] .as_str() diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index b67618b..31da3d2 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -6,7 +6,7 @@ use crate::{ }, socket::{Socket, TcpSocket}, utils::error_by_expected_size, - GDError::{PacketBad, ProtocolFormat}, + GDErrorKind::{PacketBad, ProtocolFormat}, GDResult, }; @@ -35,7 +35,7 @@ impl LegacyBV1_8 { let mut buffer = Buffer::::new(&data); if buffer.read::()? != 0xFF { - return Err(ProtocolFormat); + return Err(ProtocolFormat.context("Expected 0xFF")); } let length = buffer.read::()? * 2; @@ -47,8 +47,8 @@ impl LegacyBV1_8 { error_by_expected_size(3, split.len())?; let description = split[0].to_string(); - let online_players = split[1].parse().map_err(|_| PacketBad)?; - let max_players = split[2].parse().map_err(|_| PacketBad)?; + let online_players = split[1].parse().map_err(|e| PacketBad.context(e))?; + let max_players = split[2].parse().map_err(|e| PacketBad.context(e))?; Ok(JavaResponse { version_name: "Beta 1.8+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index e67b60b..6d7aad2 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -8,7 +8,7 @@ use crate::{ }, socket::{Socket, TcpSocket}, utils::error_by_expected_size, - GDError::{PacketBad, ProtocolFormat}, + GDErrorKind::{PacketBad, ProtocolFormat}, GDResult, }; use std::net::SocketAddr; @@ -34,7 +34,7 @@ impl LegacyV1_4 { let mut buffer = Buffer::::new(&data); if buffer.read::()? != 0xFF { - return Err(ProtocolFormat); + return Err(ProtocolFormat.context("Expected 0xFF")); } let length = buffer.read::()? * 2; @@ -50,8 +50,8 @@ impl LegacyV1_4 { error_by_expected_size(3, split.len())?; let description = split[0].to_string(); - let online_players = split[1].parse().map_err(|_| PacketBad)?; - let max_players = split[2].parse().map_err(|_| PacketBad)?; + let online_players = split[1].parse().map_err(|e| PacketBad.context(e))?; + let max_players = split[2].parse().map_err(|e| PacketBad.context(e))?; Ok(JavaResponse { version_name: "1.4+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 013825d..02b1214 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -8,7 +8,7 @@ use crate::{ }, socket::{Socket, TcpSocket}, utils::error_by_expected_size, - GDError::{PacketBad, ProtocolFormat}, + GDErrorKind::{PacketBad, ProtocolFormat}, GDResult, }; use std::net::SocketAddr; @@ -55,17 +55,17 @@ impl LegacyV1_6 { let version_protocol = buffer .read_string::>(None)? .parse() - .map_err(|_| PacketBad)?; + .map_err(|e| PacketBad.context(e))?; let version_name = buffer.read_string::>(None)?; let description = buffer.read_string::>(None)?; let online_players = buffer .read_string::>(None)? .parse() - .map_err(|_| PacketBad)?; + .map_err(|e| PacketBad.context(e))?; let max_players = buffer .read_string::>(None)? .parse() - .map_err(|_| PacketBad)?; + .map_err(|e| PacketBad.context(e))?; Ok(JavaResponse { version_name, @@ -88,14 +88,14 @@ impl LegacyV1_6 { let mut buffer = Buffer::::new(&data); if buffer.read::()? != 0xFF { - return Err(ProtocolFormat); + return Err(ProtocolFormat.context("Expected 0xFF")); } let length = buffer.read::()? * 2; error_by_expected_size((length + 3) as usize, data.len())?; if !LegacyV1_6::is_protocol(&mut buffer)? { - return Err(ProtocolFormat); + return Err(ProtocolFormat.context("Not legacy 1.6 protocol")); } LegacyV1_6::get_response(&mut buffer) diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index e555fc4..66c9ed7 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -12,7 +12,7 @@ use crate::{ LegacyGroup, }, protocols::types::TimeoutSettings, - GDError::AutoQuery, + GDErrorKind::AutoQuery, GDResult, }; use std::net::SocketAddr; @@ -38,7 +38,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> return Ok(response); } - Err(AutoQuery) + Err(AutoQuery.into()) } /// Query a Java Server. @@ -60,7 +60,7 @@ pub fn query_legacy(address: &SocketAddr, timeout_settings: Option Ok(GameMode::Hardcore), "Spectator" => Ok(GameMode::Spectator), "Adventure" => Ok(GameMode::Adventure), - _ => Err(UnknownEnumCast), + _ => Err(UnknownEnumCast.context(format!("Unknown gamemode {:?}", value))), } } } @@ -193,7 +193,7 @@ pub(crate) fn get_varint(buffer: &mut Buffer) -> GDResult // The 5th byte is only allowed to have the 4 smallest bits set if i == 4 && (current_byte & 0xf0 != 0) { - return Err(PacketBad); + return Err(PacketBad.context("Bad 5th byte")); } if (current_byte & msb) == 0 { @@ -236,7 +236,7 @@ pub(crate) fn get_string(buffer: &mut Buffer) -> GDResult()?) } - String::from_utf8(text).map_err(|_| PacketBad) + String::from_utf8(text).map_err(|e| PacketBad.context(e)) } #[allow(dead_code)] diff --git a/src/protocols/quake/client.rs b/src/protocols/quake/client.rs index 3902d1c..c67c76d 100644 --- a/src/protocols/quake/client.rs +++ b/src/protocols/quake/client.rs @@ -4,7 +4,8 @@ use crate::buffer::{Buffer, Utf8Decoder}; use crate::protocols::quake::types::Response; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::{PacketBad, TypeParse}; +use crate::{GDErrorKind, GDResult}; use std::collections::HashMap; use std::net::SocketAddr; use std::slice::Iter; @@ -34,12 +35,12 @@ fn get_data(address: &SocketAddr, timeout_settings: Option< let mut bufferer = Buffer::::new(&data); if bufferer.read::()? != 4294967295 { - return Err(GDError::PacketBad); + return Err(PacketBad.context("Expected 4294967295")); } let response_header = Client::get_response_header().as_bytes(); if !bufferer.remaining_bytes().starts_with(response_header) { - Err(GDError::PacketBad)? + Err(GDErrorKind::PacketBad)? } bufferer.move_cursor(response_header.len() as isize)?; @@ -104,18 +105,18 @@ pub(crate) fn client_query( name: server_vars .remove("hostname") .or(server_vars.remove("sv_hostname")) - .ok_or(GDError::PacketBad)?, + .ok_or(GDErrorKind::PacketBad)?, map: server_vars .remove("mapname") .or(server_vars.remove("map")) - .ok_or(GDError::PacketBad)?, + .ok_or(GDErrorKind::PacketBad)?, players_online: players.len() as u8, players_maximum: server_vars .remove("maxclients") .or(server_vars.remove("sv_maxclients")) - .ok_or(GDError::PacketBad)? + .ok_or(GDErrorKind::PacketBad)? .parse() - .map_err(|_| GDError::TypeParse)?, + .map_err(|e| TypeParse.context(e))?, players, version: server_vars .remove("version") diff --git a/src/protocols/quake/one.rs b/src/protocols/quake/one.rs index 7078227..38d1cc1 100644 --- a/src/protocols/quake/one.rs +++ b/src/protocols/quake/one.rs @@ -1,7 +1,8 @@ use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient}; use crate::protocols::quake::Response; use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings}; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::TypeParse; +use crate::{GDErrorKind, GDResult}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::net::SocketAddr; @@ -46,36 +47,36 @@ impl QuakeClient for QuakeOne { fn parse_player_string(mut data: Iter<&str>) -> GDResult { Ok(Player { id: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, score: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, time: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, ping: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, name: match data.next() { - None => Err(GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, Some(v) => remove_wrapping_quotes(v).to_string(), }, skin: match data.next() { - None => Err(GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, Some(v) => remove_wrapping_quotes(v).to_string(), }, color_primary: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, color_secondary: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, }) } diff --git a/src/protocols/quake/two.rs b/src/protocols/quake/two.rs index a45bb45..77a5d51 100644 --- a/src/protocols/quake/two.rs +++ b/src/protocols/quake/two.rs @@ -2,7 +2,8 @@ use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, Quak use crate::protocols::quake::one::QuakeOne; use crate::protocols::quake::Response; use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings}; -use crate::{GDError, GDResult}; +use crate::GDErrorKind::TypeParse; +use crate::{GDErrorKind, GDResult}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::net::SocketAddr; @@ -45,15 +46,15 @@ impl QuakeClient for QuakeTwo { fn parse_player_string(mut data: Iter<&str>) -> GDResult { Ok(Player { score: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, ping: match data.next() { - None => Err(GDError::PacketBad)?, - Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, + Some(v) => v.parse().map_err(|e| TypeParse.context(e))?, }, name: match data.next() { - None => Err(GDError::PacketBad)?, + None => Err(GDErrorKind::PacketBad)?, Some(v) => remove_wrapping_quotes(v).to_string(), }, address: data.next().map(|v| remove_wrapping_quotes(v).to_string()), diff --git a/src/protocols/types.rs b/src/protocols/types.rs index 9b7f7dd..687bb52 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -1,5 +1,6 @@ use crate::protocols::{gamespy, minecraft, quake, valve}; -use crate::{GDError::InvalidInput, GDResult}; +use crate::GDErrorKind::InvalidInput; +use crate::GDResult; use std::time::Duration; @@ -149,17 +150,18 @@ pub struct TimeoutSettings { impl TimeoutSettings { /// Construct new settings, passing None will block indefinitely. Passing - /// zero Duration throws GDError::[InvalidInput](InvalidInput). + /// zero Duration throws + /// GDErrorKind::[InvalidInput](crate::GDErrorKind::InvalidInput). pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { - return Err(InvalidInput); + return Err(InvalidInput.context("Read duration must not be 0")); } } if let Some(write_duration) = write { if write_duration == Duration::new(0, 0) { - return Err(InvalidInput); + return Err(InvalidInput.context("Write duration must not be 0")); } } @@ -219,7 +221,7 @@ mod tests { // Verify that the function returned an error and that the error type is // InvalidInput assert!(result.is_err()); - assert_eq!(result.unwrap_err(), InvalidInput); + assert_eq!(result.unwrap_err(), crate::GDErrorKind::InvalidInput.into()); } // Test that the default TimeoutSettings values are correct diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index ba17854..778fe99 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -21,7 +21,7 @@ use crate::{ }, socket::{Socket, UdpSocket}, utils::u8_lower_upper, - GDError::{BadGame, Decompress, UnknownEnumCast}, + GDErrorKind::{BadGame, Decompress, UnknownEnumCast}, GDResult, }; @@ -97,7 +97,9 @@ impl SplitPacket { fn get_payload(&self) -> GDResult> { if self.compressed { let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|_| Decompress)?; + decoder + .write(&self.payload) + .map_err(|e| Decompress.context(e))?; let decompressed_size = self.decompressed_size.unwrap() as usize; @@ -105,12 +107,16 @@ impl SplitPacket { decoder .read(&mut decompressed_payload) - .map_err(|_| Decompress)?; + .map_err(|e| Decompress.context(e))?; if decompressed_payload.len() != decompressed_size || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(Decompress) + Err(Decompress.context(format!( + "Decompressed size {} was not expected {}", + decompressed_payload.len(), + decompressed_size + ))) } else { Ok(decompressed_payload) } @@ -444,7 +450,7 @@ fn get_response( } if !is_specified_id { - return Err(BadGame(format!("AppId: {}", info.appid))); + return Err(BadGame.context(format!("AppId: {}", info.appid))); } } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index ab31665..207a46f 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer}; -use crate::GDError::UnknownEnumCast; +use crate::GDErrorKind::UnknownEnumCast; use crate::GDResult; use crate::{buffer::Buffer, protocols::GenericResponse}; use byteorder::LittleEndian; diff --git a/src/services/valve_master_server/service.rs b/src/services/valve_master_server/service.rs index 6ad6a36..ce9b3f4 100644 --- a/src/services/valve_master_server/service.rs +++ b/src/services/valve_master_server/service.rs @@ -2,7 +2,7 @@ use crate::{ buffer::Buffer, socket::{Socket, UdpSocket}, valve_master_server::{Region, SearchFilters}, - GDError, + GDErrorKind::PacketBad, GDResult, }; @@ -72,7 +72,7 @@ impl ValveMasterServer { let mut buf = Buffer::::new(&received_data); if buf.read::()? != 4294967295 || buf.read::()? != 26122 { - return Err(GDError::PacketBad); + return Err(PacketBad.context("Expected 4294967295 or 26122")); } let mut ips: Vec<(IpAddr, u16)> = Vec::new(); diff --git a/src/socket.rs b/src/socket.rs index 37aa035..64988db 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1,6 +1,6 @@ use crate::{ protocols::types::TimeoutSettings, - GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}, + GDErrorKind::{PacketReceive, PacketSend, SocketBind, SocketConnect}, GDResult, }; @@ -29,7 +29,7 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &SocketAddr) -> GDResult { Ok(Self { - socket: net::TcpStream::connect(address).map_err(|_| SocketConnect)?, + socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?, }) } @@ -42,7 +42,7 @@ impl Socket for TcpSocket { } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.write(data).map_err(|_| PacketSend)?; + self.socket.write(data).map_err(|e| PacketSend.context(e))?; Ok(()) } @@ -50,7 +50,7 @@ impl Socket for TcpSocket { let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); self.socket .read_to_end(&mut buf) - .map_err(|_| PacketReceive)?; + .map_err(|e| PacketReceive.context(e))?; Ok(buf) } @@ -63,7 +63,7 @@ pub struct UdpSocket { impl Socket for UdpSocket { fn new(address: &SocketAddr) -> GDResult { - let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; + let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| SocketBind.context(e))?; Ok(Self { socket, @@ -82,14 +82,17 @@ impl Socket for UdpSocket { fn send(&mut self, data: &[u8]) -> GDResult<()> { self.socket .send_to(data, self.address) - .map_err(|_| PacketSend)?; + .map_err(|e| PacketSend.context(e))?; Ok(()) } fn receive(&mut self, size: Option) -> GDResult> { let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; - let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; + let (number_of_bytes_received, _) = self + .socket + .recv_from(&mut buf) + .map_err(|e| PacketReceive.context(e))?; Ok(buf[.. number_of_bytes_received].to_vec()) } diff --git a/src/utils.rs b/src/utils.rs index f93f755..e890284 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,13 +1,11 @@ -use crate::{ - GDError::{PacketOverflow, PacketUnderflow}, - GDResult, -}; +use crate::GDErrorKind::{PacketOverflow, PacketUnderflow}; +use crate::GDResult; use std::cmp::Ordering; pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { match size.cmp(&expected) { - Ordering::Greater => Err(PacketOverflow), - Ordering::Less => Err(PacketUnderflow), + Ordering::Greater => Err(PacketOverflow.into()), + Ordering::Less => Err(PacketUnderflow.into()), Ordering::Equal => Ok(()), } }