[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 - name: Install MSRV
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.60.0 toolchain: 1.65.0
override: true override: true
- name: Run MSRV - name: Run MSRV
run: cargo build run: cargo build

View file

@ -38,7 +38,7 @@ repos:
language: system language: system
files: '[.]rs$' files: '[.]rs$'
pass_filenames: false pass_filenames: false
entry: rustup run --install 1.60 cargo build entry: rustup run --install 1.65 cargo build
- id: docs - id: docs
name: Check rustdoc compiles name: Check rustdoc compiles
language: system language: system

View file

@ -14,7 +14,7 @@ documentation = "https://docs.rs/gamedig/latest/gamedig/"
repository = "https://github.com/gamedig/rust-gamedig" repository = "https://github.com/gamedig/rust-gamedig"
readme = "README.md" readme = "README.md"
keywords = ["server", "query", "game", "check", "status"] keywords = ["server", "query", "game", "check", "status"]
rust-version = "1.60.0" rust-version = "1.65.0"
[features] [features]
default = ["games", "services", "game_defs"] 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. on Discord.
## Usage ## 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. 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) # MSRV (Minimum Supported Rust Version)
Current: `1.60.0` Current: `1.65.0`
Places to update: Places to update:
- `Cargo.toml` - `Cargo.toml`
- `README.md` - `README.md`
- `.github/workflows/ci.yml` - `.github/workflows/ci.yml`
- `.pre-commit-config.yaml`
# rustfmt version # rustfmt version
@ -22,5 +23,5 @@ The toolchain version used to run rustfmt in CI
Current: `nightly-2023-07-09` Current: `nightly-2023-07-09`
Places to update: Places to update:
- `./.github/workflows/ci.yml` - `.github/workflows/ci.yml`
- `./.pre-commit-config.yaml` - `.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 crate::GDResult;
use byteorder::{BigEndian, ByteOrder, LittleEndian}; use byteorder::{BigEndian, ByteOrder, LittleEndian};
use std::{convert::TryInto, marker::PhantomData}; use std::{convert::TryInto, marker::PhantomData};
@ -69,12 +70,12 @@ impl<'a, B: ByteOrder> Buffer<'a, B> {
match new_cursor { match new_cursor {
// If the addition was not successful (i.e., it resulted in an overflow or underflow), // 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. // 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) // 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), // 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. // 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 // If the new cursor position is within the bounds of the buffer, update the cursor
// position and return Ok. // 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 // If the size of `T` is larger than the remaining length, return an error
// because we don't have enough data left to read. // because we don't have enough data left to read.
if size > remaining { 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 // 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) .map($map_func)
// If the data array is empty (and thus `first` returns None), // If the data array is empty (and thus `first` returns None),
// `ok_or_else` will return a BufferError. // `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 { impl<B: ByteOrder> BufferRead<B> for $type {
fn read_from_buffer(data: &[u8]) -> GDResult<Self> { fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
// Convert the byte slice into an array of the appropriate type. // 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 // If conversion fails, return an error indicating the required and provided
// lengths. // lengths.
PacketBad PacketBad.context(e)
})?; })?;
// Use the provided function to read the data from the array into the given // Use the provided function to read the data from the array into the given
@ -345,7 +349,7 @@ impl StringDecoder for Utf8Decoder {
&data[.. position] &data[.. position]
) )
// If the data cannot be converted into a UTF-8 string, return an error // 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 // Convert the resulting &str into a String
.to_owned(); .to_owned();
@ -393,7 +397,7 @@ impl<B: ByteOrder> StringDecoder for Utf16Decoder<B> {
B::read_u16_into(&data[.. position], &mut paired_buf); B::read_u16_into(&data[.. position], &mut paired_buf);
// Convert the buffer of u16 values into a String // 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 // Update the cursor position
// The +2 accounts for the delimiter // The +2 accounts for the delimiter
@ -543,6 +547,9 @@ mod tests {
let mut buffer = Buffer::<LittleEndian>::new(data); let mut buffer = Buffer::<LittleEndian>::new(data);
let result: Result<u32, _> = buffer.read(); 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::{ use std::{
backtrace,
error::Error, error::Error,
fmt::{self, Formatter}, fmt::{self, Formatter},
}; };
@ -8,7 +9,7 @@ pub type GDResult<T> = Result<T, GDError>;
/// GameDig Error. /// GameDig Error.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum GDError { pub enum GDErrorKind {
/// The received packet was bigger than the buffer size. /// The received packet was bigger than the buffer size.
PacketOverflow, PacketOverflow,
/// The received packet was shorter than the expected one. /// The received packet was shorter than the expected one.
@ -28,7 +29,7 @@ pub enum GDError {
/// Invalid input. /// Invalid input.
InvalidInput, InvalidInput,
/// The server queried is not the queried game server. /// The server queried is not the queried game server.
BadGame(String), BadGame,
/// Couldn't automatically query. /// Couldn't automatically query.
AutoQuery, AutoQuery,
/// A protocol-defined expected format was not met. /// A protocol-defined expected format was not met.
@ -41,12 +42,108 @@ pub enum GDError {
TypeParse, 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 { impl fmt::Display for GDError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -61,29 +158,71 @@ mod tests {
// Testing Err variant of the GDResult type // Testing Err variant of the GDResult type
#[test] #[test]
fn test_gdresult_err() { fn test_gdresult_err() {
let result: GDResult<u32> = Err(GDError::InvalidInput); let result: GDResult<u32> = Err(GDErrorKind::InvalidInput.into());
assert!(result.is_err()); assert!(result.is_err());
} }
// Testing the Display trait for the GDError type // Testing cloning the GDErrorKind 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
#[test] #[test]
fn test_cloning() { fn test_cloning() {
let error = GDError::BadGame(String::from("MyGame")); let error = GDErrorKind::BadGame;
let cloned_error = error.clone(); let cloned_error = error.clone();
assert_eq!(error, cloned_error); 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::{ use crate::{
protocols::valve::{self, game, SteamApp}, protocols::valve::{self, game, SteamApp},
GDError::TypeParse, GDErrorKind::TypeParse,
GDResult, GDResult,
}; };
use std::net::{IpAddr, SocketAddr}; 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(rules) = &mut valve_response.rules {
if let Some(bat_max_players) = rules.get("bat_max_players_i") { 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"); rules.remove("bat_max_players_i");
} }
if let Some(bat_player_count) = rules.get("bat_player_count_s") { 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"); 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::gamespy::three::{data_to_map, GameSpy3};
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings}; use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings};
use crate::protocols::GenericResponse; use crate::protocols::GenericResponse;
use crate::{GDError, GDResult}; use crate::GDErrorKind::{PacketBad, TypeParse};
use crate::{GDErrorKind, GDResult};
use byteorder::BigEndian; use byteorder::BigEndian;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -91,20 +92,22 @@ pub fn query_with_timeout(
)?; )?;
let packets = client.get_server_packets()?; 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 (mut server_vars, remaining_data) = data_to_map(data)?;
let players = parse_players_and_teams(remaining_data)?; let players = parse_players_and_teams(remaining_data)?;
let players_maximum = server_vars let players_maximum = server_vars
.remove("maxplayers") .remove("maxplayers")
.ok_or(GDError::PacketBad)? .ok_or(PacketBad.context("Server variables missing maxplayers"))?
.parse() .parse()
.map_err(|_| GDError::TypeParse)?; .map_err(|e| TypeParse.context(e))?;
let players_online = match server_vars.remove("numplayers") { let players_online = match server_vars.remove("numplayers") {
None => players.len(), None => players.len(),
Some(v) => { 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() { match reported_players < players.len() {
true => players.len(), true => players.len(),
false => reported_players, false => reported_players,
@ -113,11 +116,15 @@ pub fn query_with_timeout(
}; };
Ok(Response { Ok(Response {
version: server_vars.remove("version").ok_or(GDError::PacketBad)?, version: server_vars
.remove("version")
.ok_or(GDErrorKind::PacketBad)?,
description: server_vars description: server_vars
.remove("description") .remove("description")
.ok_or(GDError::PacketBad)?, .ok_or(GDErrorKind::PacketBad)?,
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, name: server_vars
.remove("hostname")
.ok_or(GDErrorKind::PacketBad)?,
has_password: has_password(&mut server_vars)?, has_password: has_password(&mut server_vars)?,
players, players,
players_maximum, players_maximum,

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup},
GDError, GDErrorKind,
GDResult, GDResult,
}; };
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
@ -20,7 +20,7 @@ pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
return Ok(response); return Ok(response);
} }
Err(GDError::AutoQuery) Err(GDErrorKind::AutoQuery.into())
} }
/// Query a Java Server. /// Query a Java Server.

View file

@ -1,17 +1,19 @@
use crate::{GDError, GDResult}; use crate::{GDErrorKind, GDResult};
use std::collections::HashMap; use std::collections::HashMap;
pub fn has_password(server_vars: &mut HashMap<String, String>) -> GDResult<bool> { pub fn has_password(server_vars: &mut HashMap<String, String>) -> GDResult<bool> {
let password_value = server_vars let password_value = server_vars
.remove("password") .remove("password")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad.context("Missing password (exists) field"))?
.to_lowercase(); .to_lowercase();
if let Ok(has) = password_value.parse::<bool>() { if let Ok(has) = password_value.parse::<bool>() {
return Ok(has); 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) Ok(as_numeral != 0)
} }

View file

@ -2,6 +2,8 @@ use byteorder::LittleEndian;
use crate::buffer::Utf8Decoder; use crate::buffer::Utf8Decoder;
use crate::protocols::gamespy::common::has_password; use crate::protocols::gamespy::common::has_password;
use crate::GDErrorKind::TypeParse;
use crate::{ use crate::{
buffer::Buffer, buffer::Buffer,
protocols::{ protocols::{
@ -9,7 +11,7 @@ use crate::{
types::TimeoutSettings, types::TimeoutSettings,
}, },
socket::{Socket, UdpSocket}, socket::{Socket, UdpSocket},
GDError, GDErrorKind,
GDResult, GDResult,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -59,25 +61,25 @@ fn get_server_values(
if let Some(qid) = query_data { if let Some(qid) = query_data {
let split: Vec<&str> = qid.split('.').collect(); 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() { match split.len() {
1 => (), 1 => (),
2 => part = split[1].parse().map_err(|_| GDError::TypeParse)?, 2 => part = split[1].parse().map_err(|e| TypeParse.context(e))?,
_ => Err(GDError::PacketBad)?, /* the queryid can't be splitted in more than 2 _ => Err(GDErrorKind::PacketBad)?, /* the queryid can't be splitted in more than 2
* elements */ * elements */
}; };
} }
server_values.remove("queryid"); server_values.remove("queryid");
if received_query_id.is_some() && received_query_id != query_id { 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 { } else {
received_query_id = query_id; received_query_id = query_id;
} }
match parts.contains(&part) { match parts.contains(&part) {
true => Err(GDError::PacketBad)?, true => Err(GDErrorKind::PacketBad)?,
false => parts.push(part), false => parts.push(part),
} }
} }
@ -128,45 +130,54 @@ fn extract_players(server_vars: &mut HashMap<String, String>, players_maximum: u
None => { None => {
player_data player_data
.get("playername") .get("playername")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.clone() .clone()
} }
}, },
team: player_data team: player_data
.get("team") .get("team")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.trim() .trim()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
ping: player_data ping: player_data
.get("ping") .get("ping")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.trim() .trim()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
face: player_data.get("face").ok_or(GDError::PacketBad)?.clone(), face: player_data
skin: player_data.get("skin").ok_or(GDError::PacketBad)?.clone(), .get("face")
mesh: player_data.get("mesh").ok_or(GDError::PacketBad)?.clone(), .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 score: player_data
.get("frags") .get("frags")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.trim() .trim()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
deaths: match player_data.get("deaths") { 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, None => None,
}, },
health: match player_data.get("health") { 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, None => None,
}, },
secret: player_data secret: player_data
.get("ngsecret") .get("ngsecret")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.to_lowercase() .to_lowercase()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
}; };
players.push(new_player); players.push(new_player);
@ -192,27 +203,35 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
let players_maximum = server_vars let players_maximum = server_vars
.remove("maxplayers") .remove("maxplayers")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::TypeParse)?; .map_err(|e| TypeParse.context(e))?;
let players_minimum = match server_vars.remove("minplayers") { let players_minimum = match server_vars.remove("minplayers") {
None => None, 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)?; let players = extract_players(&mut server_vars, players_maximum)?;
Ok(Response { Ok(Response {
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, name: server_vars
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, .remove("hostname")
.ok_or(GDErrorKind::PacketBad)?,
map: server_vars
.remove("mapname")
.ok_or(GDErrorKind::PacketBad)?,
map_title: server_vars.remove("maptitle"), map_title: server_vars.remove("maptitle"),
admin_contact: server_vars.remove("AdminEMail"), admin_contact: server_vars.remove("AdminEMail"),
admin_name: server_vars admin_name: server_vars
.remove("AdminName") .remove("AdminName")
.or_else(|| server_vars.remove("admin")), .or_else(|| server_vars.remove("admin")),
has_password: has_password(&mut server_vars)?, has_password: has_password(&mut server_vars)?,
game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, game_type: server_vars
game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, .remove("gametype")
.ok_or(GDErrorKind::PacketBad)?,
game_version: server_vars
.remove("gamever")
.ok_or(GDErrorKind::PacketBad)?,
players_maximum, players_maximum,
players_online: players.len(), players_online: players.len(),
players_minimum, players_minimum,
@ -222,7 +241,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
.unwrap_or_else(|| "true".to_string()) .unwrap_or_else(|| "true".to_string())
.to_lowercase() .to_lowercase()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
unused_entries: server_vars, 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::gamespy::three::{Player, Response, Team};
use crate::protocols::types::TimeoutSettings; use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket}; use crate::socket::{Socket, UdpSocket};
use crate::{GDError, GDResult}; use crate::GDErrorKind::{PacketBad, TypeParse};
use crate::{GDErrorKind, GDResult};
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -80,11 +81,11 @@ impl GameSpy3 {
let mut buf = Buffer::<BigEndian>::new(&received); let mut buf = Buffer::<BigEndian>::new(&received);
if buf.read::<u8>()? != kind { 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 { 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()) Ok(buf.remaining_bytes().to_vec())
@ -108,7 +109,7 @@ impl GameSpy3 {
let challenge_as_string = buf.read_string::<Utf8Decoder>(None)?; let challenge_as_string = buf.read_string::<Utf8Decoder>(None)?;
let challenge = challenge_as_string let challenge = challenge_as_string
.parse() .parse()
.map_err(|_| GDError::TypeParse)?; .map_err(|e| TypeParse.context(e))?;
Ok(match challenge == 0 { Ok(match challenge == 0 {
true => None, true => None,
@ -147,7 +148,7 @@ impl GameSpy3 {
} }
if buf.read_string::<Utf8Decoder>(None)? != "splitnum" { if buf.read_string::<Utf8Decoder>(None)? != "splitnum" {
return Err(GDError::PacketBad); return Err(PacketBad.context("Expected string \"splitnum\""));
} }
let id = buf.read::<u8>()?; let id = buf.read::<u8>()?;
@ -167,7 +168,7 @@ impl GameSpy3 {
} }
if values.iter().any(|v| v.is_empty()) { if values.iter().any(|v| v.is_empty()) {
return Err(GDError::PacketBad); return Err(PacketBad.context("One (or more) packets is empty"));
} }
Ok(values) 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_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) { if !["player", "score", "ping", "team", "deaths", "pid", "skill"].contains(field_name) {
continue; continue;
} }
@ -243,7 +244,7 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
true => None, true => None,
false => { false => {
if v != &"t" { if v != &"t" {
Err(GDError::PacketBad)? Err(GDErrorKind::PacketBad)?
} }
Some(v) Some(v)
@ -284,35 +285,32 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
} }
players.push(Player { players.push(Player {
name: player_data name: player_data.get("player").ok_or(PacketBad)?.to_string(),
.get("player")
.ok_or(GDError::PacketBad)?
.to_string(),
score: player_data score: player_data
.get("score") .get("score")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::PacketBad)?, .map_err(|e| TypeParse.context(e))?,
ping: player_data ping: player_data
.get("ping") .get("ping")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::PacketBad)?, .map_err(|e| TypeParse.context(e))?,
team: player_data team: player_data
.get("team") .get("team")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::PacketBad)?, .map_err(|e| TypeParse.context(e))?,
deaths: player_data deaths: player_data
.get("deaths") .get("deaths")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::PacketBad)?, .map_err(|e| TypeParse.context(e))?,
skill: player_data skill: player_data
.get("skill") .get("skill")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .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 { 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 score: team_data
.get("score") .get("score")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .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 mut client = GameSpy3::new(address, timeout_settings)?;
let packets = client.get_server_packets()?; 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]; let mut remaining_data_packets = vec![remaining_data];
remaining_data_packets.extend_from_slice(&packets[1 ..]); 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 let players_maximum = server_vars
.remove("maxplayers") .remove("maxplayers")
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::TypeParse)?; .map_err(|e| TypeParse.context(e))?;
let players_minimum = match server_vars.remove("minplayers") { let players_minimum = match server_vars.remove("minplayers") {
None => None, 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") { let players_online = match server_vars.remove("numplayers") {
None => players.len(), None => players.len(),
Some(v) => { 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() { match reported_players < players.len() {
true => players.len(), true => players.len(),
false => reported_players, false => reported_players,
@ -369,11 +370,19 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
}; };
Ok(Response { Ok(Response {
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, name: server_vars
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, .remove("hostname")
.ok_or(GDErrorKind::PacketBad)?,
map: server_vars
.remove("mapname")
.ok_or(GDErrorKind::PacketBad)?,
has_password: has_password(&mut server_vars)?, has_password: has_password(&mut server_vars)?,
game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, game_type: server_vars
game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, .remove("gametype")
.ok_or(GDErrorKind::PacketBad)?,
game_version: server_vars
.remove("gamever")
.ok_or(GDErrorKind::PacketBad)?,
players_maximum, players_maximum,
players_online, players_online,
players_minimum, players_minimum,
@ -384,7 +393,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
.unwrap_or_else(|| "true".to_string()) .unwrap_or_else(|| "true".to_string())
.to_lowercase() .to_lowercase()
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
unused_entries: server_vars, 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::gamespy::two::{Player, Response, Team};
use crate::protocols::types::TimeoutSettings; use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket}; use crate::socket::{Socket, UdpSocket};
use crate::{GDError, GDResult}; use crate::GDErrorKind::{PacketBad, TypeParse};
use crate::{GDErrorKind, GDResult};
use byteorder::BigEndian; use byteorder::BigEndian;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -15,9 +16,9 @@ macro_rules! table_extract {
($table:expr, $name:literal, $index:expr) => { ($table:expr, $name:literal, $index:expr) => {
$table $table
.get($name) .get($name)
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.get($index) .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:expr, $name:literal, $index:expr) => {
table_extract!($table, $name, $index) table_extract!($table, $name, $index)
.parse() .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)> { fn data_as_table(data: &mut Buffer<BigEndian>) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
if data.read::<u8>()? != 0 { if data.read::<u8>()? != 0 {
Err(GDError::PacketBad)? Err(GDErrorKind::PacketBad)?
} }
let rows = data.read::<u8>()? as usize; 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 _ in 0 .. rows {
for column in column_heads.iter() { for column in column_heads.iter() {
let value = data.read_string::<Utf8Decoder>(None)?; 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); let mut buf = Buffer::<BigEndian>::new(&received);
if buf.read::<u8>()? != 0 || buf.read::<u32>()? != 1 { if buf.read::<u8>()? != 0 || buf.read::<u32>()? != 1 {
return Err(GDError::PacketBad); return Err(PacketBad.into());
} }
let buf_index = buf.current_position(); 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") { let players_online = match server_vars.remove("numplayers") {
None => players.len(), None => players.len(),
Some(v) => { 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() { match reported_players < players.len() {
true => players.len(), true => players.len(),
false => reported_players, false => reported_players,
@ -172,19 +176,19 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
}; };
let players_minimum = match server_vars.remove("minplayers") { let players_minimum = match server_vars.remove("minplayers") {
None => None, 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 { Ok(Response {
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, name: server_vars.remove("hostname").ok_or(PacketBad)?,
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, map: server_vars.remove("mapname").ok_or(PacketBad)?,
has_password: server_vars.remove("password").ok_or(GDError::PacketBad)? == "1", has_password: server_vars.remove("password").ok_or(PacketBad)? == "1",
teams: get_teams(&mut buffer)?, teams: get_teams(&mut buffer)?,
players_maximum: server_vars players_maximum: server_vars
.remove("maxplayers") .remove("maxplayers")
.ok_or(GDError::PacketBad)? .ok_or(PacketBad)?
.parse() .parse()
.map_err(|_| GDError::PacketBad)?, .map_err(|e| TypeParse.context(e))?,
players_online, players_online,
players_minimum, players_minimum,
players, players,

View file

@ -8,7 +8,7 @@ use crate::{
}, },
socket::{Socket, UdpSocket}, socket::{Socket, UdpSocket},
utils::error_by_expected_size, utils::error_by_expected_size,
GDError::{PacketBad, TypeParse}, GDErrorKind::{PacketBad, TypeParse},
GDResult, GDResult,
}; };
@ -46,12 +46,12 @@ impl Bedrock {
let mut buffer = Buffer::<LittleEndian>::new(&received); let mut buffer = Buffer::<LittleEndian>::new(&received);
if buffer.read::<u8>()? != 0x1c { 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). // Checking for our nonce directly from a u64 (as the nonce is 8 bytes).
if buffer.read::<u64>()? != 9833440827789222417 { 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 // 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) // Verifying the magic value (as we need 16 bytes, cast to two u64 values)
if buffer.read::<u64>()? != 18374403896610127616 { if buffer.read::<u64>()? != 18374403896610127616 {
return Err(PacketBad); return Err(PacketBad.context("Invalid magic"));
} }
if buffer.read::<u64>()? != 8671175388723805693 { 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; 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 // We must have at least 6 values
if status.len() < 6 { if status.len() < 6 {
return Err(PacketBad); return Err(PacketBad.context("Not enough values"));
} }
Ok(BedrockResponse { Ok(BedrockResponse {
@ -84,8 +84,8 @@ impl Bedrock {
name: status[1].to_string(), name: status[1].to_string(),
version_name: status[3].to_string(), version_name: status[3].to_string(),
version_protocol: status[2].to_string(), version_protocol: status[2].to_string(),
players_maximum: status[5].parse().map_err(|_| TypeParse)?, players_maximum: status[5].parse().map_err(|e| TypeParse.context(e))?,
players_online: status[4].parse().map_err(|_| TypeParse)?, players_online: status[4].parse().map_err(|e| TypeParse.context(e))?,
id: status.get(6).map(|v| v.to_string()), id: status.get(6).map(|v| v.to_string()),
map: status.get(7).map(|v| v.to_string()), map: status.get(7).map(|v| v.to_string()),
game_mode: match status.get(8) { game_mode: match status.get(8) {

View file

@ -5,7 +5,7 @@ use crate::{
types::TimeoutSettings, types::TimeoutSettings,
}, },
socket::{Socket, TcpSocket}, socket::{Socket, TcpSocket},
GDError::{JsonParse, PacketBad}, GDErrorKind::{JsonParse, PacketBad},
GDResult, GDResult,
}; };
@ -90,11 +90,11 @@ impl Java {
if get_varint(&mut buffer)? != 0 { if get_varint(&mut buffer)? != 0 {
// first var int is the packet id // first var int is the packet id
return Err(PacketBad); return Err(PacketBad.context("Expected 0"));
} }
let json_response = get_string(&mut buffer)?; 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"] let version_name = value_response["version"]["name"]
.as_str() .as_str()

View file

@ -6,7 +6,7 @@ use crate::{
}, },
socket::{Socket, TcpSocket}, socket::{Socket, TcpSocket},
utils::error_by_expected_size, utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat}, GDErrorKind::{PacketBad, ProtocolFormat},
GDResult, GDResult,
}; };
@ -35,7 +35,7 @@ impl LegacyBV1_8 {
let mut buffer = Buffer::<BigEndian>::new(&data); let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF { if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat); return Err(ProtocolFormat.context("Expected 0xFF"));
} }
let length = buffer.read::<u16>()? * 2; let length = buffer.read::<u16>()? * 2;
@ -47,8 +47,8 @@ impl LegacyBV1_8 {
error_by_expected_size(3, split.len())?; error_by_expected_size(3, split.len())?;
let description = split[0].to_string(); let description = split[0].to_string();
let online_players = split[1].parse().map_err(|_| PacketBad)?; let online_players = split[1].parse().map_err(|e| PacketBad.context(e))?;
let max_players = split[2].parse().map_err(|_| PacketBad)?; let max_players = split[2].parse().map_err(|e| PacketBad.context(e))?;
Ok(JavaResponse { Ok(JavaResponse {
version_name: "Beta 1.8+".to_string(), version_name: "Beta 1.8+".to_string(),

View file

@ -8,7 +8,7 @@ use crate::{
}, },
socket::{Socket, TcpSocket}, socket::{Socket, TcpSocket},
utils::error_by_expected_size, utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat}, GDErrorKind::{PacketBad, ProtocolFormat},
GDResult, GDResult,
}; };
use std::net::SocketAddr; use std::net::SocketAddr;
@ -34,7 +34,7 @@ impl LegacyV1_4 {
let mut buffer = Buffer::<BigEndian>::new(&data); let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF { if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat); return Err(ProtocolFormat.context("Expected 0xFF"));
} }
let length = buffer.read::<u16>()? * 2; let length = buffer.read::<u16>()? * 2;
@ -50,8 +50,8 @@ impl LegacyV1_4 {
error_by_expected_size(3, split.len())?; error_by_expected_size(3, split.len())?;
let description = split[0].to_string(); let description = split[0].to_string();
let online_players = split[1].parse().map_err(|_| PacketBad)?; let online_players = split[1].parse().map_err(|e| PacketBad.context(e))?;
let max_players = split[2].parse().map_err(|_| PacketBad)?; let max_players = split[2].parse().map_err(|e| PacketBad.context(e))?;
Ok(JavaResponse { Ok(JavaResponse {
version_name: "1.4+".to_string(), version_name: "1.4+".to_string(),

View file

@ -8,7 +8,7 @@ use crate::{
}, },
socket::{Socket, TcpSocket}, socket::{Socket, TcpSocket},
utils::error_by_expected_size, utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat}, GDErrorKind::{PacketBad, ProtocolFormat},
GDResult, GDResult,
}; };
use std::net::SocketAddr; use std::net::SocketAddr;
@ -55,17 +55,17 @@ impl LegacyV1_6 {
let version_protocol = buffer let version_protocol = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)? .read_string::<Utf16Decoder<BigEndian>>(None)?
.parse() .parse()
.map_err(|_| PacketBad)?; .map_err(|e| PacketBad.context(e))?;
let version_name = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?; let version_name = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let description = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?; let description = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let online_players = buffer let online_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)? .read_string::<Utf16Decoder<BigEndian>>(None)?
.parse() .parse()
.map_err(|_| PacketBad)?; .map_err(|e| PacketBad.context(e))?;
let max_players = buffer let max_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)? .read_string::<Utf16Decoder<BigEndian>>(None)?
.parse() .parse()
.map_err(|_| PacketBad)?; .map_err(|e| PacketBad.context(e))?;
Ok(JavaResponse { Ok(JavaResponse {
version_name, version_name,
@ -88,14 +88,14 @@ impl LegacyV1_6 {
let mut buffer = Buffer::<BigEndian>::new(&data); let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.read::<u8>()? != 0xFF { if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat); return Err(ProtocolFormat.context("Expected 0xFF"));
} }
let length = buffer.read::<u16>()? * 2; let length = buffer.read::<u16>()? * 2;
error_by_expected_size((length + 3) as usize, data.len())?; error_by_expected_size((length + 3) as usize, data.len())?;
if !LegacyV1_6::is_protocol(&mut buffer)? { 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) LegacyV1_6::get_response(&mut buffer)

View file

@ -12,7 +12,7 @@ use crate::{
LegacyGroup, LegacyGroup,
}, },
protocols::types::TimeoutSettings, protocols::types::TimeoutSettings,
GDError::AutoQuery, GDErrorKind::AutoQuery,
GDResult, GDResult,
}; };
use std::net::SocketAddr; use std::net::SocketAddr;
@ -38,7 +38,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
return Ok(response); return Ok(response);
} }
Err(AutoQuery) Err(AutoQuery.into())
} }
/// Query a Java Server. /// Query a Java Server.
@ -60,7 +60,7 @@ pub fn query_legacy(address: &SocketAddr, timeout_settings: Option<TimeoutSettin
return Ok(response); return Ok(response);
} }
Err(AutoQuery) Err(AutoQuery.into())
} }
/// Query a specific (Java) Legacy Server. /// Query a specific (Java) Legacy Server.

View file

@ -8,7 +8,7 @@ use crate::{
types::{CommonPlayer, CommonResponse, GenericPlayer}, types::{CommonPlayer, CommonResponse, GenericPlayer},
GenericResponse, GenericResponse,
}, },
GDError::{PacketBad, UnknownEnumCast}, GDErrorKind::{PacketBad, UnknownEnumCast},
GDResult, GDResult,
}; };
@ -175,7 +175,7 @@ impl GameMode {
"Hardcore" => Ok(GameMode::Hardcore), "Hardcore" => Ok(GameMode::Hardcore),
"Spectator" => Ok(GameMode::Spectator), "Spectator" => Ok(GameMode::Spectator),
"Adventure" => Ok(GameMode::Adventure), "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 // The 5th byte is only allowed to have the 4 smallest bits set
if i == 4 && (current_byte & 0xf0 != 0) { if i == 4 && (current_byte & 0xf0 != 0) {
return Err(PacketBad); return Err(PacketBad.context("Bad 5th byte"));
} }
if (current_byte & msb) == 0 { 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>()?) 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)] #[allow(dead_code)]

View file

@ -4,7 +4,8 @@ use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::quake::types::Response; use crate::protocols::quake::types::Response;
use crate::protocols::types::TimeoutSettings; use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket}; use crate::socket::{Socket, UdpSocket};
use crate::{GDError, GDResult}; use crate::GDErrorKind::{PacketBad, TypeParse};
use crate::{GDErrorKind, GDResult};
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::slice::Iter; 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); let mut bufferer = Buffer::<LittleEndian>::new(&data);
if bufferer.read::<u32>()? != 4294967295 { if bufferer.read::<u32>()? != 4294967295 {
return Err(GDError::PacketBad); return Err(PacketBad.context("Expected 4294967295"));
} }
let response_header = Client::get_response_header().as_bytes(); let response_header = Client::get_response_header().as_bytes();
if !bufferer.remaining_bytes().starts_with(response_header) { if !bufferer.remaining_bytes().starts_with(response_header) {
Err(GDError::PacketBad)? Err(GDErrorKind::PacketBad)?
} }
bufferer.move_cursor(response_header.len() as isize)?; bufferer.move_cursor(response_header.len() as isize)?;
@ -104,18 +105,18 @@ pub(crate) fn client_query<Client: QuakeClient>(
name: server_vars name: server_vars
.remove("hostname") .remove("hostname")
.or(server_vars.remove("sv_hostname")) .or(server_vars.remove("sv_hostname"))
.ok_or(GDError::PacketBad)?, .ok_or(GDErrorKind::PacketBad)?,
map: server_vars map: server_vars
.remove("mapname") .remove("mapname")
.or(server_vars.remove("map")) .or(server_vars.remove("map"))
.ok_or(GDError::PacketBad)?, .ok_or(GDErrorKind::PacketBad)?,
players_online: players.len() as u8, players_online: players.len() as u8,
players_maximum: server_vars players_maximum: server_vars
.remove("maxclients") .remove("maxclients")
.or(server_vars.remove("sv_maxclients")) .or(server_vars.remove("sv_maxclients"))
.ok_or(GDError::PacketBad)? .ok_or(GDErrorKind::PacketBad)?
.parse() .parse()
.map_err(|_| GDError::TypeParse)?, .map_err(|e| TypeParse.context(e))?,
players, players,
version: server_vars version: server_vars
.remove("version") .remove("version")

View file

@ -1,7 +1,8 @@
use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient}; use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient};
use crate::protocols::quake::Response; use crate::protocols::quake::Response;
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings}; use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
use crate::{GDError, GDResult}; use crate::GDErrorKind::TypeParse;
use crate::{GDErrorKind, GDResult};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr; use std::net::SocketAddr;
@ -46,36 +47,36 @@ impl QuakeClient for QuakeOne {
fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> { fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> {
Ok(Player { Ok(Player {
id: match data.next() { id: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
score: match data.next() { score: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
time: match data.next() { time: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
ping: match data.next() { ping: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
name: match data.next() { name: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => remove_wrapping_quotes(v).to_string(), Some(v) => remove_wrapping_quotes(v).to_string(),
}, },
skin: match data.next() { skin: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => remove_wrapping_quotes(v).to_string(), Some(v) => remove_wrapping_quotes(v).to_string(),
}, },
color_primary: match data.next() { color_primary: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
color_secondary: match data.next() { color_secondary: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::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::one::QuakeOne;
use crate::protocols::quake::Response; use crate::protocols::quake::Response;
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings}; use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
use crate::{GDError, GDResult}; use crate::GDErrorKind::TypeParse;
use crate::{GDErrorKind, GDResult};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr; use std::net::SocketAddr;
@ -45,15 +46,15 @@ impl QuakeClient for QuakeTwo {
fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> { fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> {
Ok(Player { Ok(Player {
score: match data.next() { score: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
ping: match data.next() { ping: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?, Some(v) => v.parse().map_err(|e| TypeParse.context(e))?,
}, },
name: match data.next() { name: match data.next() {
None => Err(GDError::PacketBad)?, None => Err(GDErrorKind::PacketBad)?,
Some(v) => remove_wrapping_quotes(v).to_string(), Some(v) => remove_wrapping_quotes(v).to_string(),
}, },
address: data.next().map(|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::protocols::{gamespy, minecraft, quake, valve};
use crate::{GDError::InvalidInput, GDResult}; use crate::GDErrorKind::InvalidInput;
use crate::GDResult;
use std::time::Duration; use std::time::Duration;
@ -149,17 +150,18 @@ pub struct TimeoutSettings {
impl TimeoutSettings { impl TimeoutSettings {
/// Construct new settings, passing None will block indefinitely. Passing /// 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> { pub fn new(read: Option<Duration>, write: Option<Duration>) -> GDResult<Self> {
if let Some(read_duration) = read { if let Some(read_duration) = read {
if read_duration == Duration::new(0, 0) { 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 let Some(write_duration) = write {
if write_duration == Duration::new(0, 0) { 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 // Verify that the function returned an error and that the error type is
// InvalidInput // InvalidInput
assert!(result.is_err()); 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 // Test that the default TimeoutSettings values are correct

View file

@ -21,7 +21,7 @@ use crate::{
}, },
socket::{Socket, UdpSocket}, socket::{Socket, UdpSocket},
utils::u8_lower_upper, utils::u8_lower_upper,
GDError::{BadGame, Decompress, UnknownEnumCast}, GDErrorKind::{BadGame, Decompress, UnknownEnumCast},
GDResult, GDResult,
}; };
@ -97,7 +97,9 @@ impl SplitPacket {
fn get_payload(&self) -> GDResult<Vec<u8>> { fn get_payload(&self) -> GDResult<Vec<u8>> {
if self.compressed { if self.compressed {
let mut decoder = Decoder::new(); 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; let decompressed_size = self.decompressed_size.unwrap() as usize;
@ -105,12 +107,16 @@ impl SplitPacket {
decoder decoder
.read(&mut decompressed_payload) .read(&mut decompressed_payload)
.map_err(|_| Decompress)?; .map_err(|e| Decompress.context(e))?;
if decompressed_payload.len() != decompressed_size if decompressed_payload.len() != decompressed_size
|| crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() || 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 { } else {
Ok(decompressed_payload) Ok(decompressed_payload)
} }
@ -444,7 +450,7 @@ fn get_response(
} }
if !is_specified_id { 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 std::collections::HashMap;
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer}; use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
use crate::GDError::UnknownEnumCast; use crate::GDErrorKind::UnknownEnumCast;
use crate::GDResult; use crate::GDResult;
use crate::{buffer::Buffer, protocols::GenericResponse}; use crate::{buffer::Buffer, protocols::GenericResponse};
use byteorder::LittleEndian; use byteorder::LittleEndian;

View file

@ -2,7 +2,7 @@ use crate::{
buffer::Buffer, buffer::Buffer,
socket::{Socket, UdpSocket}, socket::{Socket, UdpSocket},
valve_master_server::{Region, SearchFilters}, valve_master_server::{Region, SearchFilters},
GDError, GDErrorKind::PacketBad,
GDResult, GDResult,
}; };
@ -72,7 +72,7 @@ impl ValveMasterServer {
let mut buf = Buffer::<BigEndian>::new(&received_data); let mut buf = Buffer::<BigEndian>::new(&received_data);
if buf.read::<u32>()? != 4294967295 || buf.read::<u16>()? != 26122 { 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(); let mut ips: Vec<(IpAddr, u16)> = Vec::new();

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
protocols::types::TimeoutSettings, protocols::types::TimeoutSettings,
GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}, GDErrorKind::{PacketReceive, PacketSend, SocketBind, SocketConnect},
GDResult, GDResult,
}; };
@ -29,7 +29,7 @@ pub struct TcpSocket {
impl Socket for TcpSocket { impl Socket for TcpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> { fn new(address: &SocketAddr) -> GDResult<Self> {
Ok(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<()> { 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(()) Ok(())
} }
@ -50,7 +50,7 @@ impl Socket for TcpSocket {
let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE));
self.socket self.socket
.read_to_end(&mut buf) .read_to_end(&mut buf)
.map_err(|_| PacketReceive)?; .map_err(|e| PacketReceive.context(e))?;
Ok(buf) Ok(buf)
} }
@ -63,7 +63,7 @@ pub struct UdpSocket {
impl Socket for UdpSocket { impl Socket for UdpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> { 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 { Ok(Self {
socket, socket,
@ -82,14 +82,17 @@ impl Socket for UdpSocket {
fn send(&mut self, data: &[u8]) -> GDResult<()> { fn send(&mut self, data: &[u8]) -> GDResult<()> {
self.socket self.socket
.send_to(data, self.address) .send_to(data, self.address)
.map_err(|_| PacketSend)?; .map_err(|e| PacketSend.context(e))?;
Ok(()) Ok(())
} }
fn receive(&mut self, size: Option<usize>) -> GDResult<Vec<u8>> { fn receive(&mut self, size: Option<usize>) -> GDResult<Vec<u8>> {
let mut buf: Vec<u8> = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; 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()) Ok(buf[.. number_of_bytes_received].to_vec())
} }

View file

@ -1,13 +1,11 @@
use crate::{ use crate::GDErrorKind::{PacketOverflow, PacketUnderflow};
GDError::{PacketOverflow, PacketUnderflow}, use crate::GDResult;
GDResult,
};
use std::cmp::Ordering; use std::cmp::Ordering;
pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> {
match size.cmp(&expected) { match size.cmp(&expected) {
Ordering::Greater => Err(PacketOverflow), Ordering::Greater => Err(PacketOverflow.into()),
Ordering::Less => Err(PacketUnderflow), Ordering::Less => Err(PacketUnderflow.into()),
Ordering::Equal => Ok(()), Ordering::Equal => Ok(()),
} }
} }