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(()), } }