[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
This commit is contained in:
Tom 2023-08-05 09:36:48 +00:00 committed by GitHub
parent f7b5463073
commit 5e7e010d24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 417 additions and 217 deletions

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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.

View file

@ -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`

View file

@ -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<B: ByteOrder> BufferRead<B> for $type {
fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
// 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<B: ByteOrder> StringDecoder for Utf16Decoder<B> {
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::<LittleEndian>::new(data);
let result: Result<u32, _> = buffer.read();
assert_eq!(result.unwrap_err(), PacketUnderflow);
assert_eq!(
result.unwrap_err(),
crate::GDErrorKind::PacketUnderflow.into()
);
}
}

View file

@ -1,4 +1,5 @@
use std::{
backtrace,
error::Error,
fmt::{self, Formatter},
};
@ -8,7 +9,7 @@ pub type GDResult<T> = Result<T, GDError>;
/// 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<u32> = "thing".parse().map_err(|e| GDErrorKind::TypeParse.context(e));
/// ```
pub fn context<E: Into<Box<dyn std::error::Error + 'static>>>(self, source: E) -> GDError {
GDError::from_error(self, source)
}
}
type ErrorSource = Box<dyn std::error::Error + 'static>;
/// 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<Box<dyn std::error::Error + 'static>>)
///
/// ```
/// 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<ErrorSource>,
pub backtrace: Option<std::backtrace::Backtrace>,
}
impl From<GDErrorKind> 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<ErrorSource>) -> 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<E: Into<Box<dyn std::error::Error + 'static>>>(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<u32> = Err(GDError::InvalidInput);
let result: GDResult<u32> = 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=<disabled>\n}\n"
);
}
// test error trait GDError
#[test]
fn test_error_trait() {
let source: Result<u32, _> = "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<GDErrorKind> 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());
}
}

View file

@ -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<u16>) -> GDResult<game::Response> {
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");
}

View file

@ -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,

View file

@ -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<u16>) -> GDResult<JavaResponse> {
return Ok(response);
}
Err(GDError::AutoQuery)
Err(GDErrorKind::AutoQuery.into())
}
/// Query a Java Server.

View file

@ -1,17 +1,19 @@
use crate::{GDError, GDResult};
use crate::{GDErrorKind, GDResult};
use std::collections::HashMap;
pub fn has_password(server_vars: &mut HashMap<String, String>) -> GDResult<bool> {
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::<bool>() {
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)
}

View file

@ -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<String, String>, 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<TimeoutSettings>) ->
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::<u8>().map_err(|_| GDError::TypeParse)?),
Some(v) => Some(v.parse::<u8>().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<TimeoutSettings>) ->
.unwrap_or_else(|| "true".to_string())
.to_lowercase()
.parse()
.map_err(|_| GDError::TypeParse)?,
.map_err(|e| TypeParse.context(e))?,
unused_entries: server_vars,
})
}

View file

@ -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::<BigEndian>::new(&received);
if buf.read::<u8>()? != kind {
return Err(GDError::PacketBad);
return Err(PacketBad.context("Kind of packet did not match"));
}
if buf.read::<u32>()? != 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::<Utf8Decoder>(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::<Utf8Decoder>(None)? != "splitnum" {
return Err(GDError::PacketBad);
return Err(PacketBad.context("Expected string \"splitnum\""));
}
let id = buf.read::<u8>()?;
@ -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<Vec<u8>>) -> GDResult<(Vec<Player>, 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<Vec<u8>>) -> GDResult<(Vec<Player>, 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<Vec<u8>>) -> GDResult<(Vec<Player>, 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<Vec<u8>>) -> GDResult<(Vec<Player>, 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<TimeoutSettings>) ->
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<TimeoutSettings>) ->
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::<u8>().map_err(|_| GDError::TypeParse)?),
Some(v) => Some(v.parse::<u8>().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<TimeoutSettings>) ->
};
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<TimeoutSettings>) ->
.unwrap_or_else(|| "true".to_string())
.to_lowercase()
.parse()
.map_err(|_| GDError::TypeParse)?,
.map_err(|e| TypeParse.context(e))?,
unused_entries: server_vars,
})
}

View file

@ -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<BigEndian>) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
if data.read::<u8>()? != 0 {
Err(GDError::PacketBad)?
Err(GDErrorKind::PacketBad)?
}
let rows = data.read::<u8>()? as usize;
@ -64,7 +65,10 @@ fn data_as_table(data: &mut Buffer<BigEndian>) -> GDResult<(HashMap<String, Vec<
for _ in 0 .. rows {
for column in column_heads.iter() {
let value = data.read_string::<Utf8Decoder>(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::<BigEndian>::new(&received);
if buf.read::<u8>()? != 0 || buf.read::<u32>()? != 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<TimeoutSettings>) ->
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<TimeoutSettings>) ->
};
let players_minimum = match server_vars.remove("minplayers") {
None => None,
Some(v) => Some(v.parse::<u8>().map_err(|_| GDError::TypeParse)?),
Some(v) => Some(v.parse::<u8>().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,

View file

@ -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::<LittleEndian>::new(&received);
if buffer.read::<u8>()? != 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::<u64>()? != 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::<u64>()? != 18374403896610127616 {
return Err(PacketBad);
return Err(PacketBad.context("Invalid magic"));
}
if buffer.read::<u64>()? != 8671175388723805693 {
return Err(PacketBad);
return Err(PacketBad.context("Invalid magic"));
}
let remaining_length = buffer.switch_endian_chunk(2)?.read::<u16>()? 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) {

View file

@ -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()

View file

@ -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::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
return Err(ProtocolFormat.context("Expected 0xFF"));
}
let length = buffer.read::<u16>()? * 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(),

View file

@ -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::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
return Err(ProtocolFormat.context("Expected 0xFF"));
}
let length = buffer.read::<u16>()? * 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(),

View file

@ -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::<Utf16Decoder<BigEndian>>(None)?
.parse()
.map_err(|_| PacketBad)?;
.map_err(|e| PacketBad.context(e))?;
let version_name = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let description = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let online_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)?
.parse()
.map_err(|_| PacketBad)?;
.map_err(|e| PacketBad.context(e))?;
let max_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(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::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
return Err(ProtocolFormat.context("Expected 0xFF"));
}
let length = buffer.read::<u16>()? * 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)

View file

@ -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<TimeoutSettings>) ->
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<TimeoutSettin
return Ok(response);
}
Err(AutoQuery)
Err(AutoQuery.into())
}
/// Query a specific (Java) Legacy Server.

View file

@ -8,7 +8,7 @@ use crate::{
types::{CommonPlayer, CommonResponse, GenericPlayer},
GenericResponse,
},
GDError::{PacketBad, UnknownEnumCast},
GDErrorKind::{PacketBad, UnknownEnumCast},
GDResult,
};
@ -175,7 +175,7 @@ impl GameMode {
"Hardcore" => 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<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<i32>
// 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<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<Strin
text.push(buffer.read::<u8>()?)
}
String::from_utf8(text).map_err(|_| PacketBad)
String::from_utf8(text).map_err(|e| PacketBad.context(e))
}
#[allow(dead_code)]

View file

@ -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<Client: QuakeClient>(address: &SocketAddr, timeout_settings: Option<
let mut bufferer = Buffer::<LittleEndian>::new(&data);
if bufferer.read::<u32>()? != 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<Client: QuakeClient>(
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")

View file

@ -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<Self::Player> {
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))?,
},
})
}

View file

@ -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<Self::Player> {
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()),

View file

@ -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<Duration>, write: Option<Duration>) -> GDResult<Self> {
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

View file

@ -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<Vec<u8>> {
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)));
}
}

View file

@ -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;

View file

@ -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::<BigEndian>::new(&received_data);
if buf.read::<u32>()? != 4294967295 || buf.read::<u16>()? != 26122 {
return Err(GDError::PacketBad);
return Err(PacketBad.context("Expected 4294967295 or 26122"));
}
let mut ips: Vec<(IpAddr, u16)> = Vec::new();

View file

@ -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<Self> {
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<Self> {
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<usize>) -> GDResult<Vec<u8>> {
let mut buf: Vec<u8> = 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())
}

View file

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