[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

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