From 1b13d398567636f8f779f3d6d4b58fde0827bbd1 Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:31:37 +0100 Subject: [PATCH] [Crate] Add formatting (#22) * chore: add standard for formatting * chore: manually tidy up imports and format * chore: remove vscode and add to gitignore * chore: alphabetically order and fix * chore: format * chore: fix format issue with payload * chore: format as merge had unformatted code * [format] Fix comments, change max width and binop operator --------- Co-authored-by: CosminPerRam --- .gitignore | 1 + .rustfmt.toml | 72 ++ examples/master_querant.rs | 268 +++-- examples/minecraft.rs | 24 +- examples/tf2.rs | 20 +- src/bufferer.rs | 77 +- src/errors.rs | 11 +- src/games/aliens.rs | 25 +- src/games/aoc.rs | 15 +- src/games/arma2oa.rs | 25 +- src/games/ase.rs | 25 +- src/games/asrd.rs | 25 +- src/games/avorion.rs | 15 +- src/games/bat1944.rs | 17 +- src/games/bb2.rs | 15 +- src/games/bf1942.rs | 7 +- src/games/bm.rs | 15 +- src/games/bo.rs | 15 +- src/games/ccure.rs | 15 +- src/games/cosu.rs | 15 +- src/games/cs.rs | 25 +- src/games/cscz.rs | 25 +- src/games/csgo.rs | 25 +- src/games/css.rs | 15 +- src/games/dod.rs | 25 +- src/games/dods.rs | 15 +- src/games/doi.rs | 25 +- src/games/dst.rs | 15 +- src/games/gm.rs | 15 +- src/games/hl2dm.rs | 15 +- src/games/hldms.rs | 25 +- src/games/ins.rs | 25 +- src/games/insmic.rs | 25 +- src/games/inss.rs | 25 +- src/games/l4d.rs | 15 +- src/games/l4d2.rs | 15 +- src/games/mc.rs | 95 +- src/games/mod.rs | 185 ++-- src/games/ohd.rs | 15 +- src/games/onset.rs | 15 +- src/games/pz.rs | 15 +- src/games/ror2.rs | 15 +- src/games/rust.rs | 25 +- src/games/sc.rs | 25 +- src/games/sdtd.rs | 25 +- src/games/ss.rs | 7 +- src/games/tf.rs | 25 +- src/games/tf2.rs | 25 +- src/games/tfc.rs | 25 +- src/games/ts.rs | 199 ++-- src/games/unturned.rs | 25 +- src/games/ut.rs | 7 +- src/games/vr.rs | 15 +- src/protocols/gamespy/mod.rs | 3 +- src/protocols/gamespy/protocol/mod.rs | 1 - src/protocols/gamespy/protocol/one.rs | 128 ++- src/protocols/minecraft/mod.rs | 17 +- src/protocols/minecraft/protocol/bedrock.rs | 199 ++-- src/protocols/minecraft/protocol/java.rs | 271 ++--- .../minecraft/protocol/legacy_bv1_8.rs | 133 ++- .../minecraft/protocol/legacy_v1_4.rs | 142 ++- .../minecraft/protocol/legacy_v1_6.rs | 198 ++-- src/protocols/minecraft/protocol/mod.rs | 150 +-- src/protocols/minecraft/types.rs | 409 ++++---- src/protocols/mod.rs | 28 +- src/protocols/types.rs | 24 +- src/protocols/valve/mod.rs | 15 +- src/protocols/valve/protocol.rs | 967 ++++++++++-------- src/protocols/valve/types.rs | 885 ++++++++-------- src/socket.rs | 333 +++--- src/utils.rs | 80 +- 71 files changed, 3165 insertions(+), 2593 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.gitignore b/.gitignore index 23f7216..e285a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,6 @@ Cargo.lock # Others .idea/ .venv/ +.vscode/ test_everything.py diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..df854d3 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,72 @@ +attr_fn_like_width = 70 +array_width = 60 +binop_separator = "Front" +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 +brace_style = "PreferSameLine" +chain_width = 60 +color = "Auto" +combine_control_expr = false +comment_width = 80 +condense_wildcard_suffixes = true +control_brace_style = "AlwaysSameLine" +disable_all_formatting = false +doc_comment_code_block_width = 100 +edition = "2021" +emit_mode = "Files" +empty_item_single_line = true +error_on_line_overflow = false +error_on_unformatted = false +fn_call_width = 60 +fn_params_layout = "Tall" +fn_single_line = true +force_explicit_abi = true +force_multiline_blocks = true +format_generated_files = true +format_macro_bodies = true +format_strings = true +group_imports = "Preserve" +hard_tabs = false +hide_parse_errors = false +hex_literal_case = "Preserve" +ignore = [] +indent_style = "Block" +imports_granularity = "Preserve" +imports_indent = "Block" +imports_layout = "HorizontalVertical" +inline_attribute_width = 0 +make_backup = false +match_arm_blocks = true +match_arm_leading_pipes = "Never" +match_block_trailing_comma = false +max_width = 120 +merge_derives = true +newline_style = "Auto" +normalize_comments = true +normalize_doc_attributes = false +overflow_delimited_expr = false +reorder_impl_items = false +reorder_imports = true +reorder_modules = true +required_version = "1.5.2" +short_array_element_width_threshold = 10 +single_line_if_else_max_width = 50 +skip_children = false +space_after_colon = true +space_before_colon = false +spaces_around_ranges = true +struct_field_align_threshold = 0 +struct_lit_single_line = true +struct_lit_width = 18 +struct_variant_width = 35 +tab_spaces = 4 +trailing_comma = "Vertical" +trailing_semicolon = true +type_punctuation_density = "Wide" +unstable_features = false +use_field_init_shorthand = false +use_small_heuristics = "Default" +use_try_shorthand = true +version = "Two" +where_single_line = true +wrap_comments = true diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 501c584..a7052f5 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,96 +1,172 @@ - -use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr}; -use gamedig::protocols::minecraft::LegacyGroup; -use gamedig::protocols::valve; -use gamedig::protocols::valve::Engine; -use gamedig::protocols::gamespy; - -fn main() -> GDResult<()> { - let args: Vec = env::args().collect(); - - if args.len() == 1 || args[1] == "help".to_string() { - println!("Usage: "); - println!(" - any game, example: tf2"); - println!(" - an ip, example: 192.168.0.0"); - println!(" - an port, optional, example: 27015"); - return Ok(()); - } else if args.len() < 3 { - println!("Minimum number of arguments: 3, try 'help' to see the details."); - return Ok(()); - } - - let ip = args[2].as_str(); - let port = match args.len() == 4 { - false => { - if args[1].starts_with("_") { - panic!("The port must be specified with an anonymous query.") - } - - None - }, - true => Some(args[3].parse::().expect("Invalid port!")) - }; - - match args[1].as_str() { - "aliens" => println!("{:#?}", aliens::query(ip, port)?), - "asrd" => println!("{:#?}", asrd::query(ip, port)?), - "csgo" => println!("{:#?}", csgo::query(ip, port)?), - "css" => println!("{:#?}", css::query(ip, port)?), - "dods" => println!("{:#?}", dods::query(ip, port)?), - "gm" => println!("{:#?}", gm::query(ip, port)?), - "hl2dm" => println!("{:#?}", hl2dm::query(ip, port)?), - "tf2" => println!("{:#?}", tf2::query(ip, port)?), - "insmic" => println!("{:#?}", insmic::query(ip, port)?), - "ins" => println!("{:#?}", ins::query(ip, port)?), - "inss" => println!("{:#?}", inss::query(ip, port)?), - "l4d" => println!("{:#?}", l4d::query(ip, port)?), - "l4d2" => println!("{:#?}", l4d2::query(ip, port)?), - "ts" => println!("{:#?}", ts::query(ip, port)?), - "cscz" => println!("{:#?}", cscz::query(ip, port)?), - "dod" => println!("{:#?}", dod::query(ip, port)?), - "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::Source(None), None, None)?), - "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(true), None, None)?), - "mc" => println!("{:#?}", mc::query(ip, port)?), - "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), - "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), - "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), - "mc_legacy_vb1_8" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)?), - "mc_legacy_v1_4" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)?), - "mc_legacy_v1_6" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)?), - "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), - "ase" => println!("{:#?}", ase::query(ip, port)?), - "unturned" => println!("{:#?}", unturned::query(ip, port)?), - "tf" => println!("{:#?}", tf::query(ip, port)?), - "tfc" => println!("{:#?}", tfc::query(ip, port)?), - "sc" => println!("{:#?}", sc::query(ip, port)?), - "rust" => println!("{:#?}", rust::query(ip, port)?), - "cs" => println!("{:#?}", cs::query(ip, port)?), - "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), - "doi" => println!("{:#?}", doi::query(ip, port)?), - "hldms" => println!("{:#?}", hldms::query(ip, port)?), - "ror2" => println!("{:#?}", ror2::query(ip, port)?), - "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), - "bm" => println!("{:#?}", bm::query(ip, port)?), - "pz" => println!("{:#?}", pz::query(ip, port)?), - "aoc" => println!("{:#?}", aoc::query(ip, port)?), - "dst" => println!("{:#?}", dst::query(ip, port)?), - "cosu" => println!("{:#?}", cosu::query(ip, port)?), - "onset" => println!("{:#?}", onset::query(ip, port)?), - "ccure" => println!("{:#?}", ccure::query(ip, port)?), - "bo" => println!("{:#?}", bo::query(ip, port)?), - "bb2" => println!("{:#?}", bb2::query(ip, port)?), - "avorion" => println!("{:#?}", avorion::query(ip, port)?), - "ohd" => println!("{:#?}", ohd::query(ip, port)?), - "vr" => println!("{:#?}", vr::query(ip, port)?), - "_gamespy1" => println!("{:#?}", gamespy::one::query(ip, port.unwrap(), None)), - "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), - "ut" => println!("{:#?}", ut::query(ip, port)), - "bf1942" => println!("{:#?}", bf1942::query(ip, port)), - "ss" => println!("{:#?}", ss::query(ip, port)), - _ => panic!("Undefined game: {}", args[1]) - }; - - Ok(()) -} +use gamedig::protocols::gamespy; +use gamedig::protocols::minecraft::LegacyGroup; +use gamedig::protocols::valve; +use gamedig::protocols::valve::Engine; +use gamedig::{ + aliens, + aoc, + arma2oa, + ase, + asrd, + avorion, + bat1944, + bb2, + bf1942, + bm, + bo, + ccure, + cosu, + cs, + cscz, + csgo, + css, + dod, + dods, + doi, + dst, + gm, + hl2dm, + hldms, + ins, + insmic, + inss, + l4d, + l4d2, + mc, + ohd, + onset, + pz, + ror2, + rust, + sc, + sdtd, + ss, + tf, + tf2, + tfc, + ts, + unturned, + ut, + vr, + GDResult, +}; +use std::env; + +fn main() -> GDResult<()> { + let args: Vec = env::args().collect(); + + if args.len() == 1 || args[1] == "help".to_string() { + println!("Usage: "); + println!(" - any game, example: tf2"); + println!(" - an ip, example: 192.168.0.0"); + println!(" - an port, optional, example: 27015"); + return Ok(()); + } else if args.len() < 3 { + println!("Minimum number of arguments: 3, try 'help' to see the details."); + return Ok(()); + } + + let ip = args[2].as_str(); + let port = match args.len() == 4 { + false => { + if args[1].starts_with("_") { + panic!("The port must be specified with an anonymous query.") + } + + None + } + true => Some(args[3].parse::().expect("Invalid port!")), + }; + + match args[1].as_str() { + "aliens" => println!("{:#?}", aliens::query(ip, port)?), + "asrd" => println!("{:#?}", asrd::query(ip, port)?), + "csgo" => println!("{:#?}", csgo::query(ip, port)?), + "css" => println!("{:#?}", css::query(ip, port)?), + "dods" => println!("{:#?}", dods::query(ip, port)?), + "gm" => println!("{:#?}", gm::query(ip, port)?), + "hl2dm" => println!("{:#?}", hl2dm::query(ip, port)?), + "tf2" => println!("{:#?}", tf2::query(ip, port)?), + "insmic" => println!("{:#?}", insmic::query(ip, port)?), + "ins" => println!("{:#?}", ins::query(ip, port)?), + "inss" => println!("{:#?}", inss::query(ip, port)?), + "l4d" => println!("{:#?}", l4d::query(ip, port)?), + "l4d2" => println!("{:#?}", l4d2::query(ip, port)?), + "ts" => println!("{:#?}", ts::query(ip, port)?), + "cscz" => println!("{:#?}", cscz::query(ip, port)?), + "dod" => println!("{:#?}", dod::query(ip, port)?), + "_src" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::Source(None), None, None)? + ) + } + "_gld" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::GoldSrc(false), None, None)? + ) + } + "_gld_f" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::GoldSrc(true), None, None)? + ) + } + "mc" => println!("{:#?}", mc::query(ip, port)?), + "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), + "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), + "mc_legacy_vb1_8" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)? + ) + } + "mc_legacy_v1_4" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)? + ) + } + "mc_legacy_v1_6" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)? + ) + } + "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), + "ase" => println!("{:#?}", ase::query(ip, port)?), + "unturned" => println!("{:#?}", unturned::query(ip, port)?), + "tf" => println!("{:#?}", tf::query(ip, port)?), + "tfc" => println!("{:#?}", tfc::query(ip, port)?), + "sc" => println!("{:#?}", sc::query(ip, port)?), + "rust" => println!("{:#?}", rust::query(ip, port)?), + "cs" => println!("{:#?}", cs::query(ip, port)?), + "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), + "doi" => println!("{:#?}", doi::query(ip, port)?), + "hldms" => println!("{:#?}", hldms::query(ip, port)?), + "ror2" => println!("{:#?}", ror2::query(ip, port)?), + "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), + "bm" => println!("{:#?}", bm::query(ip, port)?), + "pz" => println!("{:#?}", pz::query(ip, port)?), + "aoc" => println!("{:#?}", aoc::query(ip, port)?), + "dst" => println!("{:#?}", dst::query(ip, port)?), + "cosu" => println!("{:#?}", cosu::query(ip, port)?), + "onset" => println!("{:#?}", onset::query(ip, port)?), + "ccure" => println!("{:#?}", ccure::query(ip, port)?), + "bo" => println!("{:#?}", bo::query(ip, port)?), + "bb2" => println!("{:#?}", bb2::query(ip, port)?), + "avorion" => println!("{:#?}", avorion::query(ip, port)?), + "ohd" => println!("{:#?}", ohd::query(ip, port)?), + "vr" => println!("{:#?}", vr::query(ip, port)?), + "_gamespy1" => println!("{:#?}", gamespy::one::query(ip, port.unwrap(), None)), + "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), + "ut" => println!("{:#?}", ut::query(ip, port)), + "bf1942" => println!("{:#?}", bf1942::query(ip, port)), + "ss" => println!("{:#?}", ss::query(ip, port)), + _ => panic!("Undefined game: {}", args[1]), + }; + + Ok(()) +} diff --git a/examples/minecraft.rs b/examples/minecraft.rs index ad1b98e..a61809a 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -1,12 +1,12 @@ - -use gamedig::games::mc; - -fn main() { - //or Some(), None is the default protocol port (which is 25565 for java and 19132 for bedrock) - let response = mc::query("127.0.0.1", None); - - match response { - Err(error) => println!("Couldn't query, error: {}", error), - Ok(r) => println!("{:#?}", r) - } -} +use gamedig::games::mc; + +fn main() { + // or Some(), None is the default protocol port (which is 25565 for java + // and 19132 for bedrock) + let response = mc::query("127.0.0.1", None); + + match response { + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:#?}", r), + } +} diff --git a/examples/tf2.rs b/examples/tf2.rs index 058fde8..a4167d0 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -1,10 +1,10 @@ - -use gamedig::games::tf2; - -fn main() { - let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) - match response { // Result type, must check what it is... - Err(error) => println!("Couldn't query, error: {}", error), - Ok(r) => println!("{:#?}", r) - } -} +use gamedig::games::tf2; + +fn main() { + let response = tf2::query("127.0.0.1", None); // or Some(27015), None is the default protocol port (which is 27015) + match response { + // Result type, must check what it is... + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:#?}", r), + } +} diff --git a/src/bufferer.rs b/src/bufferer.rs index 30c01de..00fae6b 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -1,15 +1,19 @@ -use crate::GDResult; -use crate::GDError::{PacketBad, PacketUnderflow}; -use byteorder::{ByteOrder, LittleEndian, BigEndian}; +use crate::{ + GDError::{PacketBad, PacketUnderflow}, + GDResult, +}; + +use byteorder::{BigEndian, ByteOrder, LittleEndian}; pub enum Endianess { - Little, Big + Little, + Big, } pub struct Bufferer { data: Vec, endianess: Endianess, - position: usize + position: usize, } impl Bufferer { @@ -17,13 +21,11 @@ impl Bufferer { Bufferer { data: data.to_vec(), endianess, - position: 0 + position: 0, } } - fn check_size(&self, by: usize) -> bool { - by > self.remaining_length() - } + fn check_size(&self, by: usize) -> bool { by > self.remaining_length() } pub fn get_u8(&mut self) -> GDResult { if self.check_size(1) { @@ -70,7 +72,7 @@ impl Bufferer { let value = match self.endianess { Endianess::Little => LittleEndian::read_f32(self.remaining_data()), - Endianess::Big => BigEndian::read_f32(self.remaining_data()) + Endianess::Big => BigEndian::read_f32(self.remaining_data()), }; self.move_position_ahead(4); @@ -84,7 +86,7 @@ impl Bufferer { let value = match self.endianess { Endianess::Little => LittleEndian::read_u64(self.remaining_data()), - Endianess::Big => BigEndian::read_u64(self.remaining_data()) + Endianess::Big => BigEndian::read_u64(self.remaining_data()), }; self.move_position_ahead(8); @@ -97,10 +99,10 @@ impl Bufferer { return Err(PacketUnderflow); } - let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(PacketBad)?; - let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| PacketBad)?.to_string(); + let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(PacketBad)?; + let value = std::str::from_utf8(&sub_buf[.. first_null_position]) + .map_err(|_| PacketBad)? + .to_string(); self.move_position_ahead(value.len() + 1); Ok(value) @@ -113,7 +115,8 @@ impl Bufferer { } let value = std::str::from_utf8(sub_buf) - .map_err(|_| PacketBad)?.to_string(); + .map_err(|_| PacketBad)? + .to_string(); self.move_position_ahead(value.len()); Ok(value) @@ -127,9 +130,11 @@ impl Bufferer { let paired_buf: Vec = sub_buf .chunks_exact(2) - .map(|pair| match self.endianess { - Endianess::Little => LittleEndian::read_u16(pair), - Endianess::Big => BigEndian::read_u16(pair), + .map(|pair| { + match self.endianess { + Endianess::Little => LittleEndian::read_u16(pair), + Endianess::Big => BigEndian::read_u16(pair), + } }) .collect(); @@ -139,29 +144,17 @@ impl Bufferer { Ok(value) } - pub fn move_position_ahead(&mut self, by: usize) { - self.position += by; - } + pub fn move_position_ahead(&mut self, by: usize) { self.position += by; } - pub fn move_position_backward(&mut self, by: usize) { - self.position -= by; - } + pub fn move_position_backward(&mut self, by: usize) { self.position -= by; } - pub fn data_length(&self) -> usize { - self.data.len() - } + pub fn data_length(&self) -> usize { self.data.len() } - pub fn remaining_data(&self) -> &[u8] { - &self.data[self.position..] - } + pub fn remaining_data(&self) -> &[u8] { &self.data[self.position ..] } - pub fn remaining_data_vec(&self) -> Vec { - self.remaining_data().to_vec() - } + pub fn remaining_data_vec(&self) -> Vec { self.remaining_data().to_vec() } - pub fn remaining_length(&self) -> usize { - self.data_length() - self.position - } + pub fn remaining_length(&self) -> usize { self.data_length() - self.position } pub fn as_endianess(&self, endianess: Endianess) -> Self { Bufferer { @@ -277,7 +270,10 @@ mod tests { #[test] fn get_string_utf16_le() { - let mut buffer = Bufferer::new_with_data(Endianess::Little, &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00]); + let mut buffer = Bufferer::new_with_data( + Endianess::Little, + &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00], + ); assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); assert_eq!(buffer.remaining_length(), 0); @@ -286,7 +282,10 @@ mod tests { #[test] fn get_string_utf16_be() { - let mut buffer = Bufferer::new_with_data(Endianess::Big, &[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]); + let mut buffer = Bufferer::new_with_data( + Endianess::Big, + &[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f], + ); assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); assert_eq!(buffer.remaining_length(), 0); diff --git a/src/errors.rs b/src/errors.rs index a647123..80f04e6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ -//! The library's possible errors. -use std::fmt; -use std::{error::Error, fmt::Formatter}; +use std::{ + error::Error, + fmt::{self, Formatter}, +}; /// Result of Type and GDError. pub type GDResult = Result; @@ -43,9 +44,7 @@ pub enum GDError { impl Error 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) } } #[cfg(test)] diff --git a/src/games/aliens.rs b/src/games/aliens.rs index dafc7b9..2144186 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ALIENS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ALIENS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/aoc.rs b/src/games/aoc.rs index 29d913b..c02d8e6 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::AOC.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::AOC.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index f54123c..4c01c06 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(2304), SteamApp::ARMA2OA.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(2304), + SteamApp::ARMA2OA.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ase.rs b/src/games/ase.rs index 09ff37f..7b05fa3 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASE.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ASE.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 8f2808e..57d6a70 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASRD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ASRD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/avorion.rs b/src/games/avorion.rs index 804389a..b0c5ee2 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27020), SteamApp::AVORION.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27020), + SteamApp::AVORION.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index a401819..1336176 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,10 +1,17 @@ -use crate::GDError::TypeParse; -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDError::TypeParse, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let mut valve_response = valve::query(address, port.unwrap_or(7780), SteamApp::BAT1944.as_engine(), None, None)?; + let mut valve_response = valve::query( + address, + port.unwrap_or(7780), + SteamApp::BAT1944.as_engine(), + None, + None, + )?; if let Some(rules) = &mut valve_response.rules { if let Some(bat_max_players) = rules.get("bat_max_players_i") { diff --git a/src/games/bb2.rs b/src/games/bb2.rs index 97bec4b..7ccb040 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BB2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::BB2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index 12d530b..b813694 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(23000), None) diff --git a/src/games/bm.rs b/src/games/bm.rs index 3423c40..7f99a96 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::BM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bo.rs b/src/games/bo.rs index 88dcfc4..7576f81 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::BO.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::BO.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ccure.rs b/src/games/ccure.rs index 357f5e3..23713bf 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CCURE.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CCURE.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cosu.rs b/src/games/cosu.rs index 48e1595..079e7c1 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27004), SteamApp::COSU.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27004), + SteamApp::COSU.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cs.rs b/src/games/cs.rs index 81a18a0..1a926bc 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/cscz.rs b/src/games/cscz.rs index ac8b199..19b83dc 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSCZ.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSCZ.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/csgo.rs b/src/games/csgo.rs index f65944a..68dfef2 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSGO.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSGO.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/css.rs b/src/games/css.rs index 01a0ae3..8d29825 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSS.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSS.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 56687bf..1eeb355 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DOD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/dods.rs b/src/games/dods.rs index 96be872..f0f6394 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DODS.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DODS.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/doi.rs b/src/games/doi.rs index bfee0f3..f314a5c 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOI.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DOI.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/dst.rs b/src/games/dst.rs index 256a1b7..218a461 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::DST.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::DST.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index 6fec055..7fe0e70 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::GM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::GM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index c885128..ecbf631 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HL2DM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::HL2DM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 325fd95..5cbad74 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HLDMS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::HLDMS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ins.rs b/src/games/ins.rs index 61f7759..bf4e9c4 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::INS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 82ac53a..8f3bb33 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INSMIC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::INSMIC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/inss.rs b/src/games/inss.rs index ee53a45..ad7f73e 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27131), SteamApp::INSS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27131), + SteamApp::INSS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 168f678..3e2d56d 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::L4D.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 09120cf..524b496 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::L4D2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/mc.rs b/src/games/mc.rs index 6172220..84f6f3e 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,48 +1,47 @@ -use crate::{GDError, GDResult}; -use crate::protocols::minecraft; -use crate::protocols::minecraft::{JavaResponse, LegacyGroup, BedrockResponse}; - -/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: Option) -> GDResult { - if let Ok(response) = query_java(address, port) { - return Ok(response); - } - - if let Ok(response) = query_bedrock(address, port) { - return Ok(JavaResponse::from_bedrock_response(response)); - } - - if let Ok(response) = query_legacy(address, port) { - return Ok(response); - } - - Err(GDError::AutoQuery) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { - minecraft::query_java(address, port_or_java_default(port), None) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { - minecraft::query_legacy(address, port_or_java_default(port), None) -} - -/// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { - minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) -} - -/// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: Option) -> GDResult { - minecraft::query_bedrock(address, port_or_bedrock_default(port), None) -} - -fn port_or_java_default(port: Option) -> u16 { - port.unwrap_or(25565) -} - -fn port_or_bedrock_default(port: Option) -> u16 { - port.unwrap_or(19132) -} +use crate::{ + protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, + GDError, + GDResult, +}; + +/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy +/// (1.6 -> 1.4 -> Beta 1.8)). +pub fn query(address: &str, port: Option) -> GDResult { + if let Ok(response) = query_java(address, port) { + return Ok(response); + } + + if let Ok(response) = query_bedrock(address, port) { + return Ok(JavaResponse::from_bedrock_response(response)); + } + + if let Ok(response) = query_legacy(address, port) { + return Ok(response); + } + + Err(GDError::AutoQuery) +} + +/// Query a Java Server. +pub fn query_java(address: &str, port: Option) -> GDResult { + minecraft::query_java(address, port_or_java_default(port), None) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: Option) -> GDResult { + minecraft::query_legacy(address, port_or_java_default(port), None) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { + minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: Option) -> GDResult { + minecraft::query_bedrock(address, port_or_bedrock_default(port), None) +} + +fn port_or_java_default(port: Option) -> u16 { port.unwrap_or(25565) } + +fn port_or_bedrock_default(port: Option) -> u16 { port.unwrap_or(19132) } diff --git a/src/games/mod.rs b/src/games/mod.rs index 6530c83..a5f76a4 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,93 +1,92 @@ - -//! Currently supported games. - -/// Team Fortress 2 -pub mod tf2; -/// The Ship -pub mod ts; -/// Counter-Strike: Global Offensive -pub mod csgo; -/// Counter-Strike: Source -pub mod css; -/// Day of Defeat: Source -pub mod dods; -/// Garry's Mod -pub mod gm; -/// Left 4 Dead -pub mod l4d; -/// Left 4 Dead 2 -pub mod l4d2; -/// Half-Life 2 Deathmatch -pub mod hl2dm; -/// Alien Swarm -pub mod aliens; -/// Alien Swarm: Reactive Drop -pub mod asrd; -/// Insurgency -pub mod ins; -/// Insurgency: Sandstorm -pub mod inss; -/// Insurgency: Modern Infantry Combat -pub mod insmic; -/// Counter Strike: Condition Zero -pub mod cscz; -/// Day of Defeat -pub mod dod; -/// Minecraft -pub mod mc; -/// 7 Days To Die -pub mod sdtd; -/// ARK: Survival Evolved -pub mod ase; -/// Unturned -pub mod unturned; -/// The Forest -pub mod tf; -/// Team Fortress Classic -pub mod tfc; -/// Sven Co-op -pub mod sc; -/// Rust -pub mod rust; -/// Counter-Strike -pub mod cs; -/// ARMA 2: Operation Arrowhead -pub mod arma2oa; -/// Day of Infamy -pub mod doi; -/// Half-Life Deathmatch: Source -pub mod hldms; -/// Risk of Rain 2 -pub mod ror2; -/// Battalion 1944 -pub mod bat1944; -/// Black Mesa -pub mod bm; -/// Project Zomboid -pub mod pz; -/// Age of Chivalry -pub mod aoc; -/// Don't Starve Together -pub mod dst; -/// Colony Survival -pub mod cosu; -/// Onset -pub mod onset; -/// Codename CURE -pub mod ccure; -/// Ballistic Overkill -pub mod bo; -/// BrainBread 2 -pub mod bb2; -/// Avorion -pub mod avorion; -/// Operation: Harsh Doorstop -pub mod ohd; -/// V Rising -pub mod vr; -/// Unreal Tournament -pub mod ut; -/// Battlefield 1942 -pub mod bf1942; -/// Serious Sam -pub mod ss; +//! Currently supported games. + +/// Alien Swarm +pub mod aliens; +/// Age of Chivalry +pub mod aoc; +/// ARMA 2: Operation Arrowhead +pub mod arma2oa; +/// ARK: Survival Evolved +pub mod ase; +/// Alien Swarm: Reactive Drop +pub mod asrd; +/// Avorion +pub mod avorion; +/// Battalion 1944 +pub mod bat1944; +/// BrainBread 2 +pub mod bb2; +/// Battlefield 1942 +pub mod bf1942; +/// Black Mesa +pub mod bm; +/// Ballistic Overkill +pub mod bo; +/// Codename CURE +pub mod ccure; +/// Colony Survival +pub mod cosu; +/// Counter-Strike +pub mod cs; +/// Counter Strike: Condition Zero +pub mod cscz; +/// Counter-Strike: Global Offensive +pub mod csgo; +/// Counter-Strike: Source +pub mod css; +/// Day of Defeat +pub mod dod; +/// Day of Defeat: Source +pub mod dods; +/// Day of Infamy +pub mod doi; +/// Don't Starve Together +pub mod dst; +/// Garry's Mod +pub mod gm; +/// Half-Life 2 Deathmatch +pub mod hl2dm; +/// Half-Life Deathmatch: Source +pub mod hldms; +/// Insurgency +pub mod ins; +/// Insurgency: Modern Infantry Combat +pub mod insmic; +/// Insurgency: Sandstorm +pub mod inss; +/// Left 4 Dead +pub mod l4d; +/// Left 4 Dead 2 +pub mod l4d2; +/// Minecraft +pub mod mc; +/// Operation: Harsh Doorstop +pub mod ohd; +/// Onset +pub mod onset; +/// Project Zomboid +pub mod pz; +/// Risk of Rain 2 +pub mod ror2; +/// Rust +pub mod rust; +/// Sven Co-op +pub mod sc; +/// 7 Days To Die +pub mod sdtd; +/// Serious Sam +pub mod ss; +/// The Forest +pub mod tf; +/// Team Fortress 2 +pub mod tf2; +/// Team Fortress Classic +pub mod tfc; +/// The Ship +pub mod ts; +/// Unturned +pub mod unturned; +/// Unreal Tournament +pub mod ut; +/// V Rising +pub mod vr; diff --git a/src/games/ohd.rs b/src/games/ohd.rs index b024917..f12234e 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27005), SteamApp::OHD.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27005), + SteamApp::OHD.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/onset.rs b/src/games/onset.rs index ce58ba9..f73c073 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(7776), SteamApp::ONSET.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(7776), + SteamApp::ONSET.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/pz.rs b/src/games/pz.rs index 8fda807..14cdcfe 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(16261), SteamApp::PZ.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(16261), + SteamApp::PZ.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ror2.rs b/src/games/ror2.rs index 019010a..e219b92 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::ROR2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::ROR2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/rust.rs b/src/games/rust.rs index cdd1c02..1482bfd 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::RUST.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::RUST.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/sc.rs b/src/games/sc.rs index ea8b029..a7b2215 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::SC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::SC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index c618034..7dd5cc6 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(26900), SteamApp::SDTD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(26900), + SteamApp::SDTD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ss.rs b/src/games/ss.rs index 40e132f..77d5cd9 100644 --- a/src/games/ss.rs +++ b/src/games/ss.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(25601), None) diff --git a/src/games/tf.rs b/src/games/tf.rs index 5605c5f..dbf9b93 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::TF.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::TF.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/tf2.rs b/src/games/tf2.rs index ca83dc4..4920bd2 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TF2.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TF2.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/tfc.rs b/src/games/tfc.rs index 69229cd..a85f97c 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TFC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TFC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ts.rs b/src/games/ts.rs index 3a6daa7..4e6eb3c 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,93 +1,106 @@ -use std::collections::HashMap; -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamApp}; - -#[cfg (feature = "serde")] -use serde::{Serialize, Deserialize}; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct TheShipPlayer { - pub name: String, - pub score: u32, - pub duration: f32, - pub deaths: u32, - pub money: u32 -} - -impl TheShipPlayer { - pub fn new_from_valve_player(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration, - deaths: player.deaths.unwrap(), - money: player.money.unwrap() - } - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: HashMap, - pub mode: u8, - pub witnesses: u8, - pub duration: u8 -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); - - let the_unwrapped_ship = response.info.the_ship.unwrap(); - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players_online, - players_details: response.players.unwrap().iter().map(TheShipPlayer::new_from_valve_player).collect(), - max_players: response.info.players_maximum, - bots: response.info.players_bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap(), - mode: the_unwrapped_ship.mode, - witnesses: the_unwrapped_ship.witnesses, - duration: the_unwrapped_ship.duration - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TS.as_engine(), None, None)?; - - Ok(Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp}, + GDResult, +}; + +use std::collections::HashMap; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct TheShipPlayer { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: u32, + pub money: u32, +} + +impl TheShipPlayer { + pub fn new_from_valve_player(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + deaths: player.deaths.unwrap(), + money: player.money.unwrap(), + } + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: HashMap, + pub mode: u8, + pub witnesses: u8, + pub duration: u8, +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); + + let the_unwrapped_ship = response.info.the_ship.unwrap(); + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players_online, + players_details: response + .players + .unwrap() + .iter() + .map(TheShipPlayer::new_from_valve_player) + .collect(), + max_players: response.info.players_maximum, + bots: response.info.players_bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap(), + mode: the_unwrapped_ship.mode, + witnesses: the_unwrapped_ship.witnesses, + duration: the_unwrapped_ship.duration, + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TS.as_engine(), + None, + None, + )?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/unturned.rs b/src/games/unturned.rs index b971dbf..c6e071f 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::UNTURNED.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::UNTURNED.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ut.rs b/src/games/ut.rs index a22fd79..a7f4ff1 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(7778), None) diff --git a/src/games/vr.rs b/src/games/vr.rs index 91d2105..b391f32 100644 --- a/src/games/vr.rs +++ b/src/games/vr.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::VR.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::VR.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/gamespy/mod.rs b/src/protocols/gamespy/mod.rs index 8ed7a9b..3d98ab9 100644 --- a/src/protocols/gamespy/mod.rs +++ b/src/protocols/gamespy/mod.rs @@ -1,8 +1,7 @@ - /// The implementation. pub mod protocol; /// All types used by the implementation. pub mod types; -pub use types::*; pub use protocol::*; +pub use types::*; diff --git a/src/protocols/gamespy/protocol/mod.rs b/src/protocols/gamespy/protocol/mod.rs index afcffc5..9f32c0a 100644 --- a/src/protocols/gamespy/protocol/mod.rs +++ b/src/protocols/gamespy/protocol/mod.rs @@ -1,3 +1,2 @@ - /// GameSpy 1 pub mod one; diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocol/one.rs index c553804..e348cec 100644 --- a/src/protocols/gamespy/protocol/one.rs +++ b/src/protocols/gamespy/protocol/one.rs @@ -1,11 +1,21 @@ -use std::collections::HashMap; -use crate::bufferer::{Bufferer, Endianess}; -use crate::{GDError, GDResult}; -use crate::protocols::gamespy::{Player, Response}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, UdpSocket}; +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + gamespy::{Player, Response}, + types::TimeoutSettings, + }, + socket::{Socket, UdpSocket}, + GDError, + GDResult, +}; -fn get_server_values(address: &str, port: u16, timeout_settings: Option) -> GDResult> { +use std::collections::HashMap; + +fn get_server_values( + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult> { let mut socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -26,12 +36,12 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option = as_string.split('\\').map(str::to_string).collect(); - for i in 0..splited.len() / 2 { + for i in 0 .. splited.len() / 2 { let position = i * 2; let key = splited[position].clone(); let value = match splited.get(position + 1) { None => "".to_string(), - Some(v) => v.clone() + Some(v) => v.clone(), }; server_values.insert(key, value); @@ -42,7 +52,7 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option = qid.split('.').collect(); @@ -51,22 +61,22 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option (), 2 => part = split[1].parse().map_err(|_| GDError::TypeParse)?, - _ => Err(GDError::PacketBad)? //the queryid can't be splitted in more than 2 elements + _ => Err(GDError::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! - } - else { + return Err(GDError::PacketBad); // wrong query id! + } else { received_query_id = query_id; } match parts.contains(&part) { true => Err(GDError::PacketBad)?, - false => parts.push(part) + false => parts.push(part), } } @@ -86,15 +96,12 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u let kind = split[0]; let id: usize = match split[1].parse() { Ok(v) => v, - Err(_) => return true + Err(_) => return true, }; let early_return = match kind { "team" | "player" | "ping" | "face" | "skin" | "mesh" | "frags" | "ngsecret" | "deaths" | "health" => false, - _x => { - //println!("UNKNOWN {id} {x} {value}"); - true - } + _x => true, // println!("UNKNOWN {id} {x} {value}"); }; if early_return { @@ -116,23 +123,48 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u let new_player = Player { name: match player_data.get("player") { Some(v) => v.clone(), - None => player_data.get("playername").ok_or(GDError::PacketBad)?.clone() + None => { + player_data + .get("playername") + .ok_or(GDError::PacketBad)? + .clone() + } }, - team: player_data.get("team").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, - ping: player_data.get("ping").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + team: player_data + .get("team") + .ok_or(GDError::PacketBad)? + .trim() + .parse() + .map_err(|_| GDError::TypeParse)?, + ping: player_data + .get("ping") + .ok_or(GDError::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(), - frags: player_data.get("frags").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + frags: player_data + .get("frags") + .ok_or(GDError::PacketBad)? + .trim() + .parse() + .map_err(|_| GDError::TypeParse)?, deaths: match player_data.get("deaths") { Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), - None => None + None => None, }, health: match player_data.get("health") { Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), - None => None + None => None, }, - secret: player_data.get("ngsecret").ok_or(GDError::PacketBad)?.to_lowercase().parse().map_err(|_| GDError::TypeParse)?, + secret: player_data + .get("ngsecret") + .ok_or(GDError::PacketBad)? + .to_lowercase() + .parse() + .map_err(|_| GDError::TypeParse)?, }; players.push(new_player); @@ -142,7 +174,10 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u } fn has_password(server_vars: &mut HashMap) -> GDResult { - let password_value = server_vars.remove("password").ok_or(GDError::PacketBad)?.to_lowercase(); + let password_value = server_vars + .remove("password") + .ok_or(GDError::PacketBad)? + .to_lowercase(); if let Ok(has) = password_value.parse::() { return Ok(has); @@ -153,17 +188,27 @@ fn has_password(server_vars: &mut HashMap) -> GDResult { Ok(as_numeral != 0) } -/// If there are parsing problems using the `query` function, you can directly get the server's values using this function. -pub fn query_vars(address: &str, port: u16, timeout_settings: Option) -> GDResult> { +/// If there are parsing problems using the `query` function, you can directly +/// get the server's values using this function. +pub fn query_vars( + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult> { get_server_values(address, port, timeout_settings) } /// Query a server by providing the address, the port and timeout settings. -/// Providing None to the timeout settings results in using the default values. (TimeoutSettings::[default](TimeoutSettings::default)). +/// Providing None to the timeout settings results in using the default values. +/// (TimeoutSettings::[default](TimeoutSettings::default)). pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { let mut server_vars = query_vars(address, port, timeout_settings)?; - let players_maximum = server_vars.remove("maxplayers").ok_or(GDError::PacketBad)?.parse().map_err(|_| GDError::TypeParse)?; + let players_maximum = server_vars + .remove("maxplayers") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::TypeParse)?; let players = extract_players(&mut server_vars, players_maximum)?; @@ -172,15 +217,26 @@ pub fn query(address: &str, port: u16, timeout_settings: Option map: server_vars.remove("mapname").ok_or(GDError::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")), + 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)?, players_maximum, players_online: players.len(), - players_minimum: server_vars.remove("minplayers").unwrap_or_else(|| "0".to_string()).parse().map_err(|_| GDError::TypeParse)?, + players_minimum: server_vars + .remove("minplayers") + .unwrap_or_else(|| "0".to_string()) + .parse() + .map_err(|_| GDError::TypeParse)?, players, - tournament: server_vars.remove("tournament").unwrap_or_else(|| "true".to_string()).to_lowercase().parse().map_err(|_| GDError::TypeParse)?, - unused_entries: server_vars + tournament: server_vars + .remove("tournament") + .unwrap_or_else(|| "true".to_string()) + .to_lowercase() + .parse() + .map_err(|_| GDError::TypeParse)?, + unused_entries: server_vars, }) } diff --git a/src/protocols/minecraft/mod.rs b/src/protocols/minecraft/mod.rs index 8e0718c..da17668 100644 --- a/src/protocols/minecraft/mod.rs +++ b/src/protocols/minecraft/mod.rs @@ -1,9 +1,8 @@ - -/// The implementation. -pub mod protocol; -/// All types used by the implementation. -pub mod types; - -pub use protocol::*; -pub use types::*; -pub use protocol::*; +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index c37c29e..51899f0 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -1,101 +1,98 @@ - -/* -This file has code that has been documented by the NodeJS GameDig library (MIT) from -https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js -*/ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, TypeParse}; -use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, UdpSocket}; -use crate::utils::error_by_expected_size; - -pub struct Bedrock { - socket: UdpSocket -} - -impl Bedrock { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = UdpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_status_request(&mut self) -> GDResult<()> { - self.socket.send(&[ - // Message ID, ID_UNCONNECTED_PING - 0x01, - // Nonce / timestamp - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - // Magic - 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78, - // Client GUID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?; - - Ok(()) - } - - fn get_info(&mut self) -> GDResult { - self.send_status_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0x1c { - return Err(PacketBad); - } - - // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). - if buffer.get_u64()? != 9833440827789222417 { - return Err(PacketBad); - } - - // These 8 bytes are identical to the serverId string we receive in decimal below - buffer.move_position_ahead(8); - - // Verifying the magic value (as we need 16 bytes, cast to two u64 values) - if buffer.get_u64()? != 18374403896610127616 { - return Err(PacketBad); - } - - if buffer.get_u64()? != 8671175388723805693 { - return Err(PacketBad); - } - - let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; - buffer.move_position_ahead(2); - error_by_expected_size(remaining_length, buffer.remaining_length())?; - - let binding = buffer.get_string_utf8_unended()?; - let status: Vec<&str> = binding.split(';').collect(); - - // We must have at least 6 values - if status.len() < 6 { - return Err(PacketBad); - } - - Ok(BedrockResponse { - edition: status[0].to_string(), - 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)?, - id: status.get(6).map(|v| v.to_string()), - map: status.get(7).map(|v| v.to_string()), - game_mode: match status.get(8) { - None => None, - Some(v) => Some(GameMode::from_bedrock(v)?) - }, - server_type: Server::Bedrock - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Bedrock::new(address, port, timeout_settings)?.get_info() - } -} +// This file has code that has been documented by the NodeJS GameDig library +// (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js + +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{BedrockResponse, GameMode, Server}, + types::TimeoutSettings, + }, + socket::{Socket, UdpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, TypeParse}, + GDResult, +}; + +pub struct Bedrock { + socket: UdpSocket, +} + +impl Bedrock { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + 0x01, // Message ID: ID_UNCONNECTED_PING + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Nonce / timestamp + 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, // Magic + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Client GUID + ])?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_status_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0x1c { + return Err(PacketBad); + } + + // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). + if buffer.get_u64()? != 9833440827789222417 { + return Err(PacketBad); + } + + // These 8 bytes are identical to the serverId string we receive in decimal + // below + buffer.move_position_ahead(8); + + // Verifying the magic value (as we need 16 bytes, cast to two u64 values) + if buffer.get_u64()? != 18374403896610127616 { + return Err(PacketBad); + } + + if buffer.get_u64()? != 8671175388723805693 { + return Err(PacketBad); + } + + let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; + buffer.move_position_ahead(2); + error_by_expected_size(remaining_length, buffer.remaining_length())?; + + let binding = buffer.get_string_utf8_unended()?; + let status: Vec<&str> = binding.split(';').collect(); + + // We must have at least 6 values + if status.len() < 6 { + return Err(PacketBad); + } + + Ok(BedrockResponse { + edition: status[0].to_string(), + 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)?, + id: status.get(6).map(|v| v.to_string()), + map: status.get(7).map(|v| v.to_string()), + game_mode: match status.get(8) { + None => None, + Some(v) => Some(GameMode::from_bedrock(v)?), + }, + server_type: Server::Bedrock, + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index e71ecb1..8d55d02 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,127 +1,144 @@ -use serde_json::Value; -use crate::GDResult; -use crate::GDError::{JsonParse, PacketBad}; -use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; - -pub struct Java { - socket: TcpSocket -} - -impl Java { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send(&mut self, data: Vec) -> GDResult<()> { - self.socket.send(&[as_varint(data.len() as i32), data].concat()) - } - - fn receive(&mut self) -> GDResult { - let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - - let _packet_length = get_varint(&mut buffer)? as usize; - //this declared 'packet length' from within the packet might be wrong (?), not checking with it... - - Ok(buffer) - } - - fn send_handshake(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (0) - 0x00, - //Protocol Version (-1 to determine version) - 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, - //Server address (can be anything) - 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, - //Server port (can be anything) - 0x00, 0x00, - //Next state (1 for status) - 0x01].to_vec())?; - - Ok(()) - } - - fn send_status_request(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (0) - 0x00].to_vec())?; - - Ok(()) - } - - fn send_ping_request(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (1) - 0x01].to_vec())?; - - Ok(()) - } - - fn get_info(&mut self) -> GDResult { - self.send_handshake()?; - self.send_status_request()?; - self.send_ping_request()?; - - let mut buffer = self.receive()?; - - if get_varint(&mut buffer)? != 0 { //first var int is the packet id - return Err(PacketBad); - } - - let json_response = get_string(&mut buffer)?; - let value_response: Value = serde_json::from_str(&json_response) - .map_err(|_|JsonParse)?; - - let version_name = value_response["version"]["name"].as_str() - .ok_or(PacketBad)?.to_string(); - let version_protocol = value_response["version"]["protocol"].as_i64() - .ok_or(PacketBad)? as i32; - - let max_players = value_response["players"]["max"].as_u64() - .ok_or(PacketBad)? as u32; - let online_players = value_response["players"]["online"].as_u64() - .ok_or(PacketBad)? as u32; - let sample_players: Option> = match value_response["players"]["sample"].is_null() { - true => None, - false => Some({ - let players_values = value_response["players"]["sample"].as_array() - .ok_or(PacketBad)?; - - let mut players = Vec::with_capacity(players_values.len()); - for player in players_values { - players.push(Player { - name: player["name"].as_str().ok_or(PacketBad)?.to_string(), - id: player["id"].as_str().ok_or(PacketBad)?.to_string() - }) - } - - players - }) - }; - - Ok(JavaResponse { - version_name, - version_protocol, - players_maximum: max_players, - players_online: online_players, - players_sample: sample_players, - description: value_response["description"].to_string(), - favicon: value_response["favicon"].as_str().map(str::to_string), - previews_chat: value_response["previewsChat"].as_bool(), - enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(), - server_type: Server::Java - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Java::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{as_varint, get_string, get_varint, JavaResponse, Player, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + GDError::{JsonParse, PacketBad}, + GDResult, +}; + +use serde_json::Value; + +#[rustfmt::skip] +const PAYLOAD: [u8; 17] = [ + //Packet ID (0) + 0x00, + //Protocol Version (-1 to determine version) + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + //Server address (can be anything) + 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, + //Server port (can be anything) + 0x00, 0x00, + //Next state (1 for status) + 0x01 +]; + +pub struct Java { + socket: TcpSocket, +} + +impl Java { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send(&mut self, data: Vec) -> GDResult<()> { + self.socket + .send(&[as_varint(data.len() as i32), data].concat()) + } + + fn receive(&mut self) -> GDResult { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); + + let _packet_length = get_varint(&mut buffer)? as usize; + // this declared 'packet length' from within the packet might be wrong (?), not + // checking with it... + + Ok(buffer) + } + + fn send_handshake(&mut self) -> GDResult<()> { + self.send(PAYLOAD.to_vec())?; + + Ok(()) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.send( + [0x00] // Packet ID (0) + .to_vec(), + )?; + + Ok(()) + } + + fn send_ping_request(&mut self) -> GDResult<()> { + self.send( + [0x01] // Packet ID (1) + .to_vec(), + )?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_handshake()?; + self.send_status_request()?; + self.send_ping_request()?; + + let mut buffer = self.receive()?; + + if get_varint(&mut buffer)? != 0 { + // first var int is the packet id + return Err(PacketBad); + } + + let json_response = get_string(&mut buffer)?; + let value_response: Value = serde_json::from_str(&json_response).map_err(|_| JsonParse)?; + + let version_name = value_response["version"]["name"] + .as_str() + .ok_or(PacketBad)? + .to_string(); + let version_protocol = value_response["version"]["protocol"] + .as_i64() + .ok_or(PacketBad)? as i32; + + let max_players = value_response["players"]["max"].as_u64().ok_or(PacketBad)? as u32; + let online_players = value_response["players"]["online"] + .as_u64() + .ok_or(PacketBad)? as u32; + let sample_players: Option> = match value_response["players"]["sample"].is_null() { + true => None, + false => { + Some({ + let players_values = value_response["players"]["sample"] + .as_array() + .ok_or(PacketBad)?; + + let mut players = Vec::with_capacity(players_values.len()); + for player in players_values { + players.push(Player { + name: player["name"].as_str().ok_or(PacketBad)?.to_string(), + id: player["id"].as_str().ok_or(PacketBad)?.to_string(), + }) + } + + players + }) + } + }; + + Ok(JavaResponse { + version_name, + version_protocol, + players_maximum: max_players, + players_online: online_players, + players_sample: sample_players, + description: value_response["description"].to_string(), + favicon: value_response["favicon"].as_str().map(str::to_string), + previews_chat: value_response["previewsChat"].as_bool(), + enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(), + server_type: Server::Java, + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index 2423996..e977e44 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,68 +1,65 @@ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyBV1_8 { - socket: TcpSocket -} - -impl LegacyBV1_8 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[0xFE]) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('§').collect(); - 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)?; - - Ok(JavaResponse { - version_name: "Beta 1.8+".to_string(), - version_protocol: -1, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::VB1_8) - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyBV1_8::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyBV1_8 { + socket: TcpSocket, +} + +impl LegacyBV1_8 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE]) } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('§').collect(); + 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)?; + + Ok(JavaResponse { + version_name: "Beta 1.8+".to_string(), + version_protocol: -1, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::VB1_8), + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyBV1_8::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 5c5451c..aed055d 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,73 +1,69 @@ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyV1_4 { - socket: TcpSocket -} - -impl LegacyV1_4 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[0xFE, 0x01]) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - if LegacyV1_6::is_protocol(&mut buffer)? { - return LegacyV1_6::get_response(&mut buffer); - } - - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('§').collect(); - 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)?; - - Ok(JavaResponse { - version_name: "1.4+".to_string(), - version_protocol: -1, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::V1_4) - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyV1_4::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{protocol::legacy_v1_6::LegacyV1_6, JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyV1_4 { + socket: TcpSocket, +} + +impl LegacyV1_4 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE, 0x01]) } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + if LegacyV1_6::is_protocol(&mut buffer)? { + return LegacyV1_6::get_response(&mut buffer); + } + + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('§').collect(); + 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)?; + + Ok(JavaResponse { + version_name: "1.4+".to_string(), + version_protocol: -1, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_4), + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_4::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 4954298..f901b79 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,100 +1,98 @@ -use crate::GDResult; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyV1_6 { - socket: TcpSocket -} - -impl LegacyV1_6 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[ - // Packet ID (FE) - 0xfe, - // Ping payload (01) - 0x01, - // Packet identifier for plugin message - 0xfa, - // Length of 'GameDig' string (7) as unsigned short - 0x00, 0x07, - // 'GameDig' string as UTF-16BE - 0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67])?; - - Ok(()) - } - - pub fn is_protocol(buffer: &mut Bufferer) -> GDResult { - let state = buffer.remaining_data().starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); - - if state { - buffer.move_position_ahead(6); - } - - Ok(state) - } - - pub fn get_response(buffer: &mut Bufferer) -> GDResult { - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('\x00').collect(); - error_by_expected_size(5, split.len())?; - - let version_protocol = split[0].parse() - .map_err(|_| PacketBad)?; - let version_name = split[1].to_string(); - let description = split[2].to_string(); - let online_players = split[3].parse() - .map_err(|_| PacketBad)?; - let max_players = split[4].parse() - .map_err(|_| PacketBad)?; - - Ok(JavaResponse { - version_name, - version_protocol, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::V1_6) - }) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - if !LegacyV1_6::is_protocol(&mut buffer)? { - return Err(ProtocolFormat); - } - - LegacyV1_6::get_response(&mut buffer) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyV1_6::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyV1_6 { + socket: TcpSocket, +} + +impl LegacyV1_6 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + 0xfe, // Packet ID (FE) + 0x01, // Ping payload (01) + 0xfa, // Packet identifier for plugin message + 0x00, 0x07, // Length of 'GameDig' string (7) as unsigned short + 0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, + 0x67, // 'GameDig' string as UTF-16BE + ])?; + + Ok(()) + } + + pub fn is_protocol(buffer: &mut Bufferer) -> GDResult { + let state = buffer + .remaining_data() + .starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); + + if state { + buffer.move_position_ahead(6); + } + + Ok(state) + } + + pub fn get_response(buffer: &mut Bufferer) -> GDResult { + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('\x00').collect(); + error_by_expected_size(5, split.len())?; + + let version_protocol = split[0].parse().map_err(|_| PacketBad)?; + let version_name = split[1].to_string(); + let description = split[2].to_string(); + let online_players = split[3].parse().map_err(|_| PacketBad)?; + let max_players = split[4].parse().map_err(|_| PacketBad)?; + + Ok(JavaResponse { + version_name, + version_protocol, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_6), + }) + } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + if !LegacyV1_6::is_protocol(&mut buffer)? { + return Err(ProtocolFormat); + } + + LegacyV1_6::get_response(&mut buffer) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_6::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 73bcb90..293c739 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,68 +1,82 @@ -use crate::GDError::AutoQuery; -use crate::GDResult; -use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, JavaResponse}; -use crate::protocols::minecraft::protocol::bedrock::Bedrock; -use crate::protocols::minecraft::protocol::java::Java; -use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; -use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; -use crate::protocols::minecraft::protocol::legacy_bv1_8::LegacyBV1_8; -use crate::protocols::types::TimeoutSettings; - -mod java; -mod legacy_v1_4; -mod legacy_v1_6; -mod legacy_bv1_8; -mod bedrock; - -/// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_java(address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { - return Ok(JavaResponse::from_bedrock_response(response)); - } - - if let Ok(response) = query_legacy(address, port, timeout_settings) { - return Ok(response); - } - - Err(AutoQuery) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Java::query(address, port, timeout_settings) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { - return Ok(response); - } - - Err(AutoQuery) -} - -/// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option) -> GDResult { - match group { - LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), - LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), - LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings) - } -} - -/// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Bedrock::query(address, port, timeout_settings) -} +use crate::{ + protocols::minecraft::{ + protocol::{ + bedrock::Bedrock, + java::Java, + legacy_bv1_8::LegacyBV1_8, + legacy_v1_4::LegacyV1_4, + legacy_v1_6::LegacyV1_6, + }, + BedrockResponse, + JavaResponse, + LegacyGroup, + }, + protocols::types::TimeoutSettings, + GDError::AutoQuery, + GDResult, +}; + +mod bedrock; +mod java; +mod legacy_bv1_8; +mod legacy_v1_4; +mod legacy_v1_6; + +/// Queries a Minecraft server with all the protocol variants one by one (Java +/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_java(address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { + return Ok(JavaResponse::from_bedrock_response(response)); + } + + if let Ok(response) = query_legacy(address, port, timeout_settings) { + return Ok(response); + } + + Err(AutoQuery) +} + +/// Query a Java Server. +pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::query(address, port, timeout_settings) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { + return Ok(response); + } + + Err(AutoQuery) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific( + group: LegacyGroup, + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult { + match group { + LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), + LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), + LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings), + } +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::query(address, port, timeout_settings) +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index d596339..8b22581 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -1,204 +1,205 @@ -/* -Although its a lightly modified version, this file contains code -by Jaiden Bernard (2021-2022 - MIT) from -https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs -*/ - -use crate::bufferer::Bufferer; -use crate::GDError::{PacketBad, UnknownEnumCast}; -use crate::GDResult; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of Minecraft Server you want to query. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Server { - /// Java Edition. - Java, - /// Legacy Java. - Legacy(LegacyGroup), - /// Bedrock Edition. - Bedrock, -} - -/// Legacy Java (Versions) Groups. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum LegacyGroup { - /// 1.6 - V1_6, - /// 1.4 - 1.5 - V1_4, - /// Beta 1.8 - 1.3 - VB1_8, -} - -/// Information about a player. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Player { - pub name: String, - pub id: String, -} - -/// A Java query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct JavaResponse { - /// Version name, example: "1.19.2". - pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2). Note that for versions below 1.6 this field is always -1. - pub version_protocol: i32, - /// Number of server capacity. - pub players_maximum: u32, - /// Number of online players. - pub players_online: u32, - /// Some online players (can be missing). - pub players_sample: Option>, - /// Server's description or MOTD. - pub description: String, - /// The favicon (can be missing). - pub favicon: Option, - /// Tells if the chat preview is enabled (can be missing). - pub previews_chat: Option, - /// Tells if secure chat is enforced (can be missing). - pub enforces_secure_chat: Option, - /// Tell's the server type. - pub server_type: Server, -} - -/// A Bedrock Edition query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BedrockResponse { - /// Server's edition. - pub edition: String, - /// Server's name. - pub name: String, - /// Version name, example: "1.19.40". - pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2). - pub version_protocol: String, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u32, - /// Number of players on the server. - pub players_online: u32, - /// Server id. - pub id: Option, - /// Currently running map's name. - pub map: Option, - /// Current game mode. - pub game_mode: Option, - /// Tells the server type. - pub server_type: Server, -} - -impl JavaResponse { - pub fn from_bedrock_response(response: BedrockResponse) -> Self { - Self { - version_name: response.version_name, - version_protocol: 0, - players_maximum: response.players_maximum, - players_online: response.players_online, - players_sample: None, - description: response.name, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Bedrock, - } - } -} - -/// A server's game mode (used only by Bedrock servers. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum GameMode { - Survival, - Creative, - Hardcore, - Spectator, - Adventure, -} - -impl GameMode { - pub fn from_bedrock(value: &&str) -> GDResult { - match *value { - "Survival" => Ok(GameMode::Survival), - "Creative" => Ok(GameMode::Creative), - "Hardcore" => Ok(GameMode::Hardcore), - "Spectator" => Ok(GameMode::Spectator), - "Adventure" => Ok(GameMode::Adventure), - _ => Err(UnknownEnumCast), - } - } -} - -pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { - let mut result = 0; - - let msb: u8 = 0b10000000; - let mask: u8 = !msb; - - for i in 0..5 { - let current_byte = buffer.get_u8()?; - - result |= ((current_byte & mask) as i32) << (7 * i); - - // The 5th byte is only allowed to have the 4 smallest bits set - if i == 4 && (current_byte & 0xf0 != 0) { - return Err(PacketBad); - } - - if (current_byte & msb) == 0 { - break; - } - } - - Ok(result) -} - -pub(crate) fn as_varint(value: i32) -> Vec { - let mut bytes = vec![]; - let mut reading_value = value; - - let msb: u8 = 0b10000000; - let mask: i32 = 0b01111111; - - for _ in 0..5 { - let tmp = (reading_value & mask) as u8; - - reading_value &= !mask; - reading_value = reading_value.rotate_right(7); - - if reading_value != 0 { - bytes.push(tmp | msb); - } else { - bytes.push(tmp); - break; - } - } - - bytes -} - -pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { - let length = get_varint(buffer)? as usize; - let mut text = Vec::with_capacity(length); - - for _ in 0..length { - text.push(buffer.get_u8()?) - } - - String::from_utf8(text).map_err(|_| PacketBad) -} - -#[allow(dead_code)] -pub(crate) fn as_string(value: String) -> Vec { - let mut buf = as_varint(value.len() as i32); - buf.extend(value.as_bytes().to_vec()); - - buf -} +// Although its a lightly modified version, this file contains code +// by Jaiden Bernard (2021-2022 - MIT) from +// https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs + +use crate::{ + bufferer::Bufferer, + GDError::{PacketBad, UnknownEnumCast}, + GDResult, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The type of Minecraft Server you want to query. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Server { + /// Java Edition. + Java, + /// Legacy Java. + Legacy(LegacyGroup), + /// Bedrock Edition. + Bedrock, +} + +/// Legacy Java (Versions) Groups. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum LegacyGroup { + /// 1.6 + V1_6, + /// 1.4 - 1.5 + V1_4, + /// Beta 1.8 - 1.3 + VB1_8, +} + +/// Information about a player. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Player { + pub name: String, + pub id: String, +} + +/// A Java query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct JavaResponse { + /// Version name, example: "1.19.2". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). Note that for versions + /// below 1.6 this field is always -1. + pub version_protocol: i32, + /// Number of server capacity. + pub players_maximum: u32, + /// Number of online players. + pub players_online: u32, + /// Some online players (can be missing). + pub players_sample: Option>, + /// Server's description or MOTD. + pub description: String, + /// The favicon (can be missing). + pub favicon: Option, + /// Tells if the chat preview is enabled (can be missing). + pub previews_chat: Option, + /// Tells if secure chat is enforced (can be missing). + pub enforces_secure_chat: Option, + /// Tell's the server type. + pub server_type: Server, +} + +/// A Bedrock Edition query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BedrockResponse { + /// Server's edition. + pub edition: String, + /// Server's name. + pub name: String, + /// Version name, example: "1.19.40". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). + pub version_protocol: String, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u32, + /// Number of players on the server. + pub players_online: u32, + /// Server id. + pub id: Option, + /// Currently running map's name. + pub map: Option, + /// Current game mode. + pub game_mode: Option, + /// Tells the server type. + pub server_type: Server, +} + +impl JavaResponse { + pub fn from_bedrock_response(response: BedrockResponse) -> Self { + Self { + version_name: response.version_name, + version_protocol: 0, + players_maximum: response.players_maximum, + players_online: response.players_online, + players_sample: None, + description: response.name, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Bedrock, + } + } +} + +/// A server's game mode (used only by Bedrock servers. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GameMode { + Survival, + Creative, + Hardcore, + Spectator, + Adventure, +} + +impl GameMode { + pub fn from_bedrock(value: &&str) -> GDResult { + match *value { + "Survival" => Ok(GameMode::Survival), + "Creative" => Ok(GameMode::Creative), + "Hardcore" => Ok(GameMode::Hardcore), + "Spectator" => Ok(GameMode::Spectator), + "Adventure" => Ok(GameMode::Adventure), + _ => Err(UnknownEnumCast), + } + } +} + +pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { + let mut result = 0; + + let msb: u8 = 0b10000000; + let mask: u8 = !msb; + + for i in 0 .. 5 { + let current_byte = buffer.get_u8()?; + + result |= ((current_byte & mask) as i32) << (7 * i); + + // The 5th byte is only allowed to have the 4 smallest bits set + if i == 4 && (current_byte & 0xf0 != 0) { + return Err(PacketBad); + } + + if (current_byte & msb) == 0 { + break; + } + } + + Ok(result) +} + +pub(crate) fn as_varint(value: i32) -> Vec { + let mut bytes = vec![]; + let mut reading_value = value; + + let msb: u8 = 0b10000000; + let mask: i32 = 0b01111111; + + for _ in 0 .. 5 { + let tmp = (reading_value & mask) as u8; + + reading_value &= !mask; + reading_value = reading_value.rotate_right(7); + + if reading_value != 0 { + bytes.push(tmp | msb); + } else { + bytes.push(tmp); + break; + } + } + + bytes +} + +pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { + let length = get_varint(buffer)? as usize; + let mut text = Vec::with_capacity(length); + + for _ in 0 .. length { + text.push(buffer.get_u8()?) + } + + String::from_utf8(text).map_err(|_| PacketBad) +} + +#[allow(dead_code)] +pub(crate) fn as_string(value: String) -> Vec { + let mut buf = as_varint(value.len() as i32); + buf.extend(value.as_bytes().to_vec()); + + buf +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index e6e4fac..48a0e7e 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1,14 +1,14 @@ - -//! Protocols that are currently implemented. -//! -//! A protocol will be here if it supports multiple entries, if not, its implementation will be -//! in that specific needed place, a protocol can be independently queried. - -/// General types that are used by all protocols. -pub mod types; -/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) -pub mod valve; -/// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) -pub mod minecraft; -/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) -pub mod gamespy; +//! Protocols that are currently implemented. +//! +//! A protocol will be here if it supports multiple entries, if not, its +//! implementation will be in that specific needed place, a protocol can be +//! independently queried. + +/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) +pub mod gamespy; +/// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) +pub mod minecraft; +/// General types that are used by all protocols. +pub mod types; +/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) +pub mod valve; diff --git a/src/protocols/types.rs b/src/protocols/types.rs index b0047fc..0f98f46 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -1,5 +1,5 @@ -use crate::GDError::InvalidInput; -use crate::GDResult; +use crate::{GDError::InvalidInput, GDResult}; + use std::time::Duration; /// Timeout settings for socket operations @@ -10,7 +10,8 @@ pub struct TimeoutSettings { } impl TimeoutSettings { - /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](InvalidInput). + /// Construct new settings, passing None will block indefinitely. Passing + /// zero Duration throws GDError::[InvalidInput](InvalidInput). pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { @@ -28,14 +29,10 @@ impl TimeoutSettings { } /// Get the read timeout. - pub fn get_read(&self) -> Option { - self.read - } + pub fn get_read(&self) -> Option { self.read } /// Get the write timeout. - pub fn get_write(&self) -> Option { - self.write - } + pub fn get_write(&self) -> Option { self.write } } impl Default for TimeoutSettings { @@ -77,10 +74,12 @@ mod tests { let read_duration = Duration::new(0, 0); let write_duration = Duration::from_secs(2); - // Try to create new TimeoutSettings with the zero read duration (this should fail) + // Try to create new TimeoutSettings with the zero read duration (this should + // fail) let result = TimeoutSettings::new(Some(read_duration), Some(write_duration)); - // Verify that the function returned an error and that the error type is InvalidInput + // Verify that the function returned an error and that the error type is + // InvalidInput assert!(result.is_err()); assert_eq!(result.unwrap_err(), InvalidInput); } @@ -91,7 +90,8 @@ mod tests { // Get the default TimeoutSettings values let default_settings = TimeoutSettings::default(); - // Verify that the get_read and get_write methods return the expected default values + // Verify that the get_read and get_write methods return the expected default + // values assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4))); assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4))); } diff --git a/src/protocols/valve/mod.rs b/src/protocols/valve/mod.rs index 44527b4..3d98ab9 100644 --- a/src/protocols/valve/mod.rs +++ b/src/protocols/valve/mod.rs @@ -1,8 +1,7 @@ - -/// The implementation. -pub mod protocol; -/// All types used by the implementation. -pub mod types; - -pub use protocol::*; -pub use types::*; +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index a1eefd3..9e4944b 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,455 +1,512 @@ -use std::collections::HashMap; -use bzip2_rs::decoder::Decoder; -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{BadGame, Decompress, UnknownEnumCast}; -use crate::protocols::types::TimeoutSettings; -use crate::protocols::valve::{Engine, ModData, SteamApp}; -use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, TheShip}; -use crate::socket::{Socket, UdpSocket}; -use crate::utils::u8_lower_upper; - -#[derive(Debug, Clone)] -struct Packet { - pub header: u32, - pub kind: u8, - pub payload: Vec -} - -impl Packet { - fn new(buffer: &mut Bufferer) -> GDResult { - Ok(Self { - header: buffer.get_u32()?, - kind: buffer.get_u8()?, - payload: buffer.remaining_data_vec() - }) - } - - fn challenge(kind: Request, challenge: Vec) -> Self { - let mut initial = Packet::initial(kind); - - Self { - header: initial.header, - kind: initial.kind, - payload: match kind { - Request::Info => { - initial.payload.extend(challenge); - initial.payload - }, - _ => challenge - } - } - } - - fn initial(kind: Request) -> Self { - Self { - header: 4294967295, //FF FF FF FF - kind: kind as u8, - payload: match kind { - Request::Info => String::from("Source Engine Query\0").into_bytes(), - _ => vec![0xFF, 0xFF, 0xFF, 0xFF] - } - } - } - - fn to_bytes(&self) -> Vec { - let mut buf = Vec::from(self.header.to_be_bytes()); - - buf.push(self.kind); - buf.extend(&self.payload); - - buf - } -} - -#[derive(Debug)] -#[allow(dead_code)] //remove this later on -struct SplitPacket { - pub header: u32, - pub id: u32, - pub total: u8, - pub number: u8, - pub size: u16, - pub compressed: bool, - pub decompressed_size: Option, - pub uncompressed_crc32: Option, - payload: Vec -} - -impl SplitPacket { - fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult { - let header = buffer.get_u32()?; - let id = buffer.get_u32()?; - let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine { - Engine::GoldSrc(_) => { - let (lower, upper) = u8_lower_upper(buffer.get_u8()?); - (lower, upper, 0, false, None, None) - } - Engine::Source(_) => { - let total = buffer.get_u8()?; - let number = buffer.get_u8()?; - let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) { //certain apps with protocol = 7 dont have this field - false => buffer.get_u16()?, - true => 1248 - }; - let compressed = ((id >> 31) & 1) == 1; - let (decompressed_size, uncompressed_crc32) = match compressed { - false => (None, None), - true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)) - }; - (total, number, size, compressed, decompressed_size, uncompressed_crc32) - } - }; - - Ok(Self { - header, - id, - total, - number, - size, - compressed, - decompressed_size, - uncompressed_crc32, - payload: buffer.remaining_data_vec() - }) - } - - fn get_payload(&self) -> GDResult> { - if self.compressed { - let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|_| Decompress)?; - - let decompressed_size = self.decompressed_size.unwrap() as usize; - - let mut decompressed_payload = vec![0; decompressed_size]; - - decoder.read(&mut decompressed_payload).map_err(|_| Decompress)?; - - if decompressed_payload.len() != decompressed_size - || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(Decompress) - } - else { - Ok(decompressed_payload) - } - } else { - Ok(self.payload.clone()) - } - } -} - -struct ValveProtocol { - socket: UdpSocket -} - -static PACKET_SIZE: usize = 6144; - -impl ValveProtocol { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = UdpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult { - let data = self.socket.receive(Some(buffer_size))?; - let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); - - let header = buffer.get_u8()?; - buffer.move_position_backward(1); - if header == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?; - let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); - - for _ in 1..main_packet.total { - let new_data = self.socket.receive(Some(buffer_size))?; - buffer = Bufferer::new_with_data(Endianess::Little, &new_data); - let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?; - chunk_packets.push(chunk_packet); - } - - chunk_packets.sort_by(|a, b| a.number.cmp(&b.number)); - - for chunk_packet in chunk_packets { - main_packet.payload.extend(chunk_packet.payload); - } - - let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); - Ok(Packet::new(&mut new_packet_buffer)?) - } - else { - Packet::new(&mut buffer) - } - } - - /// Ask for a specific request only. - fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { - let request_initial_packet = Packet::initial(kind).to_bytes(); - self.socket.send(&request_initial_packet)?; - - let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; - while packet.kind == 0x41 {// 'A' - let challenge = packet.payload.clone(); - let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); - - self.socket.send(&challenge_packet)?; - - packet = self.receive(engine, protocol, PACKET_SIZE)?; - } - - let data = packet.payload; - Ok(Bufferer::new_with_data(Endianess::Little, &data)) - } - - fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { - buffer.get_u8()?; //get the header (useless info) - buffer.get_string_utf8()?; //get the server address (useless info) - let name = buffer.get_string_utf8()?; - let map = buffer.get_string_utf8()?; - let folder = buffer.get_string_utf8()?; - let game = buffer.get_string_utf8()?; - let players = buffer.get_u8()?; - let max_players = buffer.get_u8()?; - let protocol = buffer.get_u8()?; - let server_type = match buffer.get_u8()? { - 68 => Server::Dedicated, //'D' - 76 => Server::NonDedicated, //'L' - 80 => Server::TV, //'P' - _ => Err(UnknownEnumCast)? - }; - let environment_type = match buffer.get_u8()? { - 76 => Environment::Linux, //'L' - 87 => Environment::Windows, //'W' - _ => Err(UnknownEnumCast)? - }; - let has_password = buffer.get_u8()? == 1; - let is_mod = buffer.get_u8()? == 1; - let mod_data = match is_mod { - false => None, - true => Some(ModData { - link: buffer.get_string_utf8()?, - download_link: buffer.get_string_utf8()?, - version: buffer.get_u32()?, - size: buffer.get_u32()?, - multiplayer_only: buffer.get_u8()? == 1, - has_own_dll: buffer.get_u8()? == 1 - }) - }; - let vac_secured = buffer.get_u8()? == 1; - let bots = buffer.get_u8()?; - - Ok(ServerInfo { - protocol, - name, - map, - folder, - game, - appid: 0, //not present in the obsolete response - players_online: players, - players_maximum: max_players, - players_bots: bots, - server_type, - environment_type, - has_password, - vac_secured, - the_ship: None, - version: "".to_string(), //a version field only for the mod - extra_data: None, - is_mod, - mod_data - }) - } - - /// Get the server information's. - fn get_server_info(&mut self, engine: &Engine) -> GDResult { - let mut buffer = self.get_request_data(engine, 0, Request::Info)?; - - if let Engine::GoldSrc(force) = engine { - if *force { - return ValveProtocol::get_goldsrc_server_info(&mut buffer); - } - } - - let protocol = buffer.get_u8()?; - let name = buffer.get_string_utf8()?; - let map = buffer.get_string_utf8()?; - let folder = buffer.get_string_utf8()?; - let game = buffer.get_string_utf8()?; - let mut appid = buffer.get_u16()? as u32; - let players = buffer.get_u8()?; - let max_players = buffer.get_u8()?; - let bots = buffer.get_u8()?; - let server_type = match buffer.get_u8()? { - 100 => Server::Dedicated, //'d' - 108 => Server::NonDedicated, //'l' - 112 => Server::TV, //'p' - _ => Err(UnknownEnumCast)? - }; - let environment_type = match buffer.get_u8()? { - 108 => Environment::Linux, //'l' - 119 => Environment::Windows, //'w' - 109 | 111 => Environment::Mac, //'m' or 'o' - _ => Err(UnknownEnumCast)? - }; - let has_password = buffer.get_u8()? == 1; - let vac_secured = buffer.get_u8()? == 1; - let the_ship = match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(TheShip { - mode: buffer.get_u8()?, - witnesses: buffer.get_u8()?, - duration: buffer.get_u8()? - }) - }; - let version = buffer.get_string_utf8()?; - let extra_data = match buffer.get_u8() { - Err(_) => None, - Ok(value) => Some(ExtraData { - port: match (value & 0x80) > 0 { - false => None, - true => Some(buffer.get_u16()?) - }, - steam_id: match (value & 0x10) > 0 { - false => None, - true => Some(buffer.get_u64()?) - }, - tv_port: match (value & 0x40) > 0 { - false => None, - true => Some(buffer.get_u16()?) - }, - tv_name: match (value & 0x40) > 0 { - false => None, - true => Some(buffer.get_string_utf8()?) - }, - keywords: match (value & 0x20) > 0 { - false => None, - true => Some(buffer.get_string_utf8()?) - }, - game_id: match (value & 0x01) > 0 { - false => None, - true => { - let gid = buffer.get_u64()?; - appid = (gid & ((1 << 24) - 1)) as u32; - - Some(gid) - } - } - }) - }; - - Ok(ServerInfo { - protocol, - name, - map, - folder, - game, - appid, - players_online: players, - players_maximum: max_players, - players_bots: bots, - server_type, - environment_type, - has_password, - vac_secured, - the_ship, - version, - extra_data, - is_mod: false, - mod_data: None - }) - } - - /// Get the server player's. - fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; - - let count = buffer.get_u8()? as usize; - let mut players: Vec = Vec::with_capacity(count); - - for _ in 0..count { - buffer.move_position_ahead(1); //skip the index byte - - players.push(ServerPlayer { - name: buffer.get_string_utf8()?, - score: buffer.get_u32()?, - duration: buffer.get_f32()?, - deaths: match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }, - money: match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }, - }); - } - - Ok(players) - } - - /// Get the server's rules. - fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; - - let count = buffer.get_u16()? as usize; - let mut rules: HashMap = HashMap::with_capacity(count); - - for _ in 0..count { - let name = buffer.get_string_utf8()?; - let value = buffer.get_string_utf8()?; - - rules.insert(name, value); - } - - if *engine == SteamApp::ROR2.as_engine() { - rules.remove("Test"); - } - - Ok(rules) - } -} - -/// Query a server by providing the address, the port, the app, gather and timeout settings. -/// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &str, port: u16, engine: Engine, gather_settings: Option, timeout_settings: Option) -> GDResult { - let response_gather_settings = gather_settings.unwrap_or_default(); - get_response(address, port, engine, response_gather_settings, timeout_settings) -} - -fn get_response(address: &str, port: u16, engine: Engine, gather_settings: GatheringSettings, timeout_settings: Option) -> GDResult { - let mut client = ValveProtocol::new(address, port, timeout_settings)?; - - let info = client.get_server_info(&engine)?; - let protocol = info.protocol; - - if let Engine::Source(Some(appids)) = &engine { - let mut is_specified_id = false; - - if appids.0 == info.appid { - is_specified_id = true; - } else if let Some(dedicated_appid) = appids.1 { - if dedicated_appid == info.appid { - is_specified_id = true; - } - } - - if !is_specified_id { - return Err(BadGame(format!("AppId: {}", info.appid))); - } - } - - Ok(Response { - info, - players: match gather_settings.players { - false => None, - true => Some(client.get_server_players(&engine, protocol)?) - }, - rules: match gather_settings.rules { - false => None, - true => Some(client.get_server_rules(&engine, protocol)?) - } - }) -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + types::TimeoutSettings, + valve::{ + types::{ + Environment, + ExtraData, + GatheringSettings, + Request, + Response, + Server, + ServerInfo, + ServerPlayer, + TheShip, + }, + Engine, + ModData, + SteamApp, + }, + }, + + socket::{Socket, UdpSocket}, + utils::u8_lower_upper, + GDError::{BadGame, Decompress, UnknownEnumCast}, + GDResult, +}; + +use bzip2_rs::decoder::Decoder; + +use std::collections::HashMap; + +#[derive(Debug, Clone)] +struct Packet { + pub header: u32, + pub kind: u8, + pub payload: Vec, +} + +impl Packet { + fn new(buffer: &mut Bufferer) -> GDResult { + Ok(Self { + header: buffer.get_u32()?, + kind: buffer.get_u8()?, + payload: buffer.remaining_data_vec(), + }) + } + + fn challenge(kind: Request, challenge: Vec) -> Self { + let mut initial = Packet::initial(kind); + + Self { + header: initial.header, + kind: initial.kind, + payload: match kind { + Request::Info => { + initial.payload.extend(challenge); + initial.payload + } + _ => challenge, + }, + } + } + + fn initial(kind: Request) -> Self { + Self { + header: 4294967295, // FF FF FF FF + kind: kind as u8, + payload: match kind { + Request::Info => String::from("Source Engine Query\0").into_bytes(), + _ => vec![0xFF, 0xFF, 0xFF, 0xFF], + }, + } + } + + fn to_bytes(&self) -> Vec { + let mut buf = Vec::from(self.header.to_be_bytes()); + + buf.push(self.kind); + buf.extend(&self.payload); + + buf + } +} + +#[derive(Debug)] +#[allow(dead_code)] //remove this later on +struct SplitPacket { + pub header: u32, + pub id: u32, + pub total: u8, + pub number: u8, + pub size: u16, + pub compressed: bool, + pub decompressed_size: Option, + pub uncompressed_crc32: Option, + payload: Vec, +} + +impl SplitPacket { + fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult { + let header = buffer.get_u32()?; + let id = buffer.get_u32()?; + let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine { + Engine::GoldSrc(_) => { + let (lower, upper) = u8_lower_upper(buffer.get_u8()?); + (lower, upper, 0, false, None, None) + } + Engine::Source(_) => { + let total = buffer.get_u8()?; + let number = buffer.get_u8()?; + let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) { + // certain apps with protocol = 7 dont have this field + false => buffer.get_u16()?, + true => 1248, + }; + let compressed = ((id >> 31) & 1) == 1; + let (decompressed_size, uncompressed_crc32) = match compressed { + false => (None, None), + true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)), + }; + ( + total, + number, + size, + compressed, + decompressed_size, + uncompressed_crc32, + ) + } + }; + + Ok(Self { + header, + id, + total, + number, + size, + compressed, + decompressed_size, + uncompressed_crc32, + payload: buffer.remaining_data_vec(), + }) + } + + fn get_payload(&self) -> GDResult> { + if self.compressed { + let mut decoder = Decoder::new(); + decoder.write(&self.payload).map_err(|_| Decompress)?; + + let decompressed_size = self.decompressed_size.unwrap() as usize; + + let mut decompressed_payload = vec![0; decompressed_size]; + + decoder + .read(&mut decompressed_payload) + .map_err(|_| Decompress)?; + + if decompressed_payload.len() != decompressed_size + || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() + { + Err(Decompress) + } else { + Ok(decompressed_payload) + } + } else { + Ok(self.payload.clone()) + } + } +} + +struct ValveProtocol { + socket: UdpSocket, +} + +static PACKET_SIZE: usize = 6144; + +impl ValveProtocol { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult { + let data = self.socket.receive(Some(buffer_size))?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + + let header = buffer.get_u8()?; + buffer.move_position_backward(1); + if header == 0xFE { + // the packet is split + let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?; + let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); + + for _ in 1 .. main_packet.total { + let new_data = self.socket.receive(Some(buffer_size))?; + buffer = Bufferer::new_with_data(Endianess::Little, &new_data); + let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?; + chunk_packets.push(chunk_packet); + } + + chunk_packets.sort_by(|a, b| a.number.cmp(&b.number)); + + for chunk_packet in chunk_packets { + main_packet.payload.extend(chunk_packet.payload); + } + + let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); + Ok(Packet::new(&mut new_packet_buffer)?) + } else { + Packet::new(&mut buffer) + } + } + + /// Ask for a specific request only. + fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { + let request_initial_packet = Packet::initial(kind).to_bytes(); + self.socket.send(&request_initial_packet)?; + + let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; + while packet.kind == 0x41 { + // 'A' + let challenge = packet.payload.clone(); + let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); + + self.socket.send(&challenge_packet)?; + + packet = self.receive(engine, protocol, PACKET_SIZE)?; + } + + let data = packet.payload; + Ok(Bufferer::new_with_data(Endianess::Little, &data)) + } + + fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { + buffer.get_u8()?; //get the header (useless info) + buffer.get_string_utf8()?; //get the server address (useless info) + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let protocol = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { + 68 => Server::Dedicated, //'D' + 76 => Server::NonDedicated, //'L' + 80 => Server::TV, //'P' + _ => Err(UnknownEnumCast)?, + }; + let environment_type = match buffer.get_u8()? { + 76 => Environment::Linux, //'L' + 87 => Environment::Windows, //'W' + _ => Err(UnknownEnumCast)?, + }; + let has_password = buffer.get_u8()? == 1; + let is_mod = buffer.get_u8()? == 1; + let mod_data = match is_mod { + false => None, + true => { + Some(ModData { + link: buffer.get_string_utf8()?, + download_link: buffer.get_string_utf8()?, + version: buffer.get_u32()?, + size: buffer.get_u32()?, + multiplayer_only: buffer.get_u8()? == 1, + has_own_dll: buffer.get_u8()? == 1, + }) + } + }; + let vac_secured = buffer.get_u8()? == 1; + let bots = buffer.get_u8()?; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid: 0, // not present in the obsolete response + players_online: players, + players_maximum: max_players, + players_bots: bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship: None, + version: "".to_string(), // a version field only for the mod + extra_data: None, + is_mod, + mod_data, + }) + } + + /// Get the server information's. + fn get_server_info(&mut self, engine: &Engine) -> GDResult { + let mut buffer = self.get_request_data(engine, 0, Request::Info)?; + + if let Engine::GoldSrc(force) = engine { + if *force { + return ValveProtocol::get_goldsrc_server_info(&mut buffer); + } + } + + let protocol = buffer.get_u8()?; + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let mut appid = buffer.get_u16()? as u32; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let bots = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { + 100 => Server::Dedicated, //'d' + 108 => Server::NonDedicated, //'l' + 112 => Server::TV, //'p' + _ => Err(UnknownEnumCast)?, + }; + let environment_type = match buffer.get_u8()? { + 108 => Environment::Linux, //'l' + 119 => Environment::Windows, //'w' + 109 | 111 => Environment::Mac, //'m' or 'o' + _ => Err(UnknownEnumCast)?, + }; + let has_password = buffer.get_u8()? == 1; + let vac_secured = buffer.get_u8()? == 1; + let the_ship = match *engine == SteamApp::TS.as_engine() { + false => None, + true => { + Some(TheShip { + mode: buffer.get_u8()?, + witnesses: buffer.get_u8()?, + duration: buffer.get_u8()?, + }) + } + }; + let version = buffer.get_string_utf8()?; + let extra_data = match buffer.get_u8() { + Err(_) => None, + Ok(value) => { + Some(ExtraData { + port: match (value & 0x80) > 0 { + false => None, + true => Some(buffer.get_u16()?), + }, + steam_id: match (value & 0x10) > 0 { + false => None, + true => Some(buffer.get_u64()?), + }, + tv_port: match (value & 0x40) > 0 { + false => None, + true => Some(buffer.get_u16()?), + }, + tv_name: match (value & 0x40) > 0 { + false => None, + true => Some(buffer.get_string_utf8()?), + }, + keywords: match (value & 0x20) > 0 { + false => None, + true => Some(buffer.get_string_utf8()?), + }, + game_id: match (value & 0x01) > 0 { + false => None, + true => { + let gid = buffer.get_u64()?; + appid = (gid & ((1 << 24) - 1)) as u32; + + Some(gid) + } + }, + }) + } + }; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid, + players_online: players, + players_maximum: max_players, + players_bots: bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship, + version, + extra_data, + is_mod: false, + mod_data: None, + }) + } + + /// Get the server player's. + fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; + + let count = buffer.get_u8()? as usize; + let mut players: Vec = Vec::with_capacity(count); + + for _ in 0 .. count { + buffer.move_position_ahead(1); //skip the index byte + + players.push(ServerPlayer { + name: buffer.get_string_utf8()?, + score: buffer.get_u32()?, + duration: buffer.get_f32()?, + deaths: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?), + }, + money: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?), + }, + }); + } + + Ok(players) + } + + /// Get the server's rules. + fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; + + let count = buffer.get_u16()? as usize; + let mut rules: HashMap = HashMap::with_capacity(count); + + for _ in 0 .. count { + let name = buffer.get_string_utf8()?; + let value = buffer.get_string_utf8()?; + + rules.insert(name, value); + } + + if *engine == SteamApp::ROR2.as_engine() { + rules.remove("Test"); + } + + Ok(rules) + } +} + +/// Query a server by providing the address, the port, the app, gather and +/// timeout settings. Providing None to the settings results in using the +/// default values for them +/// (GatherSettings::[default](GatheringSettings::default), +/// TimeoutSettings::[default](TimeoutSettings::default)). +pub fn query( + address: &str, + port: u16, + engine: Engine, + gather_settings: Option, + timeout_settings: Option, +) -> GDResult { + let response_gather_settings = gather_settings.unwrap_or_default(); + get_response( + address, + port, + engine, + response_gather_settings, + timeout_settings, + ) +} + +fn get_response( + address: &str, + port: u16, + engine: Engine, + gather_settings: GatheringSettings, + timeout_settings: Option, +) -> GDResult { + let mut client = ValveProtocol::new(address, port, timeout_settings)?; + + let info = client.get_server_info(&engine)?; + let protocol = info.protocol; + + if let Engine::Source(Some(appids)) = &engine { + let mut is_specified_id = false; + + if appids.0 == info.appid { + is_specified_id = true; + } else if let Some(dedicated_appid) = appids.1 { + if dedicated_appid == info.appid { + is_specified_id = true; + } + } + + if !is_specified_id { + return Err(BadGame(format!("AppId: {}", info.appid))); + } + } + + Ok(Response { + info, + players: match gather_settings.players { + false => None, + true => Some(client.get_server_players(&engine, protocol)?), + }, + rules: match gather_settings.rules { + false => None, + true => Some(client.get_server_rules(&engine, protocol)?), + }, + }) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 681de23..5515ca6 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,444 +1,441 @@ -use std::collections::HashMap; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of the server. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Server { - Dedicated, - NonDedicated, - TV, -} - -/// The Operating System that the server is on. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Environment { - Linux, - Windows, - Mac, -} - -/// A query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct Response { - pub info: ServerInfo, - pub players: Option>, - pub rules: Option>, -} - -/// General server information's. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ServerInfo { - /// Protocol used by the server. - pub protocol: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub map: String, - /// Name of the folder containing the game files. - pub folder: String, - /// The name of the game. - pub game: String, - /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. - pub appid: u32, - /// Number of players on the server. - pub players_online: u8, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u8, - /// Number of bots on the server. - pub players_bots: u8, - /// Dedicated, NonDedicated or SourceTV - pub server_type: Server, - /// The Operating System that the server is on. - pub environment_type: Environment, - /// Indicates whether the server requires a password. - pub has_password: bool, - /// Indicates whether the server uses VAC. - pub vac_secured: bool, - /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data - pub the_ship: Option, - /// Version of the game installed on the server. - pub version: String, - /// Some extra data that the server might provide or not. - pub extra_data: Option, - /// GoldSrc only: Indicates whether the hosted game is a mod. - pub is_mod: bool, - /// GoldSrc only: If the game is a mod, provide additional data. - pub mod_data: Option, -} - -/// A server player. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct ServerPlayer { - /// Player's name. - pub name: String, - /// General score. - pub score: u32, - /// How long a player has been in the server (seconds). - pub duration: f32, - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count - pub deaths: Option, //the_ship - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount - pub money: Option, //the_ship -} - -/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TheShip { - pub mode: u8, - pub witnesses: u8, - pub duration: u8, -} - -/// Some extra data that the server might provide or not. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ExtraData { - /// The server's game port number. - pub port: Option, - /// Server's SteamID. - pub steam_id: Option, - /// SourceTV's port. - pub tv_port: Option, - /// SourceTV's name. - pub tv_name: Option, - /// Keywords that describe the server according to it. - pub keywords: Option, - /// The server's 64-bit GameID. - pub game_id: Option, -} - -/// Data related to GoldSrc Mod response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ModData { - pub link: String, - pub download_link: String, - pub version: u32, - pub size: u32, - pub multiplayer_only: bool, - pub has_own_dll: bool, -} - -pub(crate) type ExtractedData = ( - Option, - Option, - Option, - Option, - Option, -); - -pub(crate) fn get_optional_extracted_data( - data: Option, -) -> ExtractedData { - match data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords), - } -} - -/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). -#[derive(Eq, PartialEq, Copy, Clone)] -#[repr(u8)] -pub(crate) enum Request { - /// Known as `A2S_INFO` - Info = 0x54, - /// Known as `A2S_PLAYERS` - Players = 0x55, - /// Known as `A2S_RULES` - Rules = 0x56, -} - -/// Supported steam apps -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum SteamApp { - /// Counter-Strike - CS, - /// Team Fortress Classic - TFC, - /// Day of Defeat - DOD, - /// Counter-Strike: Condition Zero - CSCZ, - /// Counter-Strike: Source - CSS, - /// Day of Defeat: Source - DODS, - /// Half-Life 2 Deathmatch - HL2DM, - /// Half-Life Deathmatch: Source - HLDMS, - /// Team Fortress 2 - TF2, - /// Left 4 Dead - L4D, - /// Left 4 Dead - L4D2, - /// Alien Swarm - ALIENS, - /// Counter-Strike: Global Offensive - CSGO, - /// The Ship - TS, - /// Garry's Mod - GM, - /// Age of Chivalry - AOC, - /// Insurgency: Modern Infantry Combat - INSMIC, - /// ARMA 2: Operation Arrowhead - ARMA2OA, - /// Project Zomboid - PZ, - /// Insurgency - INS, - /// Sven Co-op - SC, - /// 7 Days To Die - SDTD, - /// Rust - RUST, - /// Vallistic Overkill - BO, - /// Don't Starve Together - DST, - /// BrainBread 2 - BB2, - /// Codename CURE - CCURE, - /// Black Mesa - BM, - /// Colony Survival - COSU, - /// Avorion - AVORION, - /// Day of Infamy - DOI, - /// The Forest - TF, - /// Unturned - UNTURNED, - /// ARK: Survival Evolved - ASE, - /// Battalion 1944 - BAT1944, - /// Insurgency: Sandstorm - INSS, - /// Alien Swarm: Reactive Drop - ASRD, - /// Risk of Rain 2 - ROR2, - /// Operation: Harsh Doorstop - OHD, - /// Onset - ONSET, - /// V Rising - VR, -} - -impl SteamApp { - /// Get the specified app as engine. - pub fn as_engine(&self) -> Engine { - match self { - SteamApp::CS => Engine::GoldSrc(false), //10 - SteamApp::TFC => Engine::GoldSrc(false), //20 - SteamApp::DOD => Engine::GoldSrc(false), //30 - SteamApp::CSCZ => Engine::GoldSrc(false), //80 - SteamApp::CSS => Engine::new_source(240), - SteamApp::DODS => Engine::new_source(300), - SteamApp::HL2DM => Engine::new_source(320), - SteamApp::HLDMS => Engine::new_source(360), - SteamApp::TF2 => Engine::new_source(440), - SteamApp::L4D => Engine::new_source(500), - SteamApp::L4D2 => Engine::new_source(550), - SteamApp::ALIENS => Engine::new_source(630), - SteamApp::CSGO => Engine::new_source(730), - SteamApp::TS => Engine::new_source(2400), - SteamApp::GM => Engine::new_source(4000), - SteamApp::AOC => Engine::new_source(17510), - SteamApp::INSMIC => Engine::new_source(17700), - SteamApp::ARMA2OA => Engine::new_source(33930), - SteamApp::PZ => Engine::new_source(108600), - SteamApp::INS => Engine::new_source(222880), - SteamApp::SC => Engine::GoldSrc(false), //225840 - SteamApp::SDTD => Engine::new_source(251570), - SteamApp::RUST => Engine::new_source(252490), - SteamApp::BO => Engine::new_source(296300), - SteamApp::DST => Engine::new_source(322320), - SteamApp::BB2 => Engine::new_source(346330), - SteamApp::CCURE => Engine::new_source(355180), - SteamApp::BM => Engine::new_source(362890), - SteamApp::COSU => Engine::new_source(366090), - SteamApp::AVORION => Engine::new_source(445220), - SteamApp::DOI => Engine::new_source(447820), - SteamApp::TF => Engine::new_source(556450), - SteamApp::UNTURNED => Engine::new_source(304930), - SteamApp::ASE => Engine::new_source(346110), - SteamApp::BAT1944 => Engine::new_source(489940), - SteamApp::INSS => Engine::new_source(581320), - SteamApp::ASRD => Engine::new_source(563560), - SteamApp::ROR2 => Engine::new_source(632360), - SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), - SteamApp::ONSET => Engine::new_source(1105810), - SteamApp::VR => Engine::new_source(1604030), - } - } -} - -/// Engine type. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Engine { - /// A Source game, the argument represents the possible steam app ids, if its **None**, let - /// the query find it, if its **Some**, the query fails if the response id is not the first - /// one, which is the game app id, or the other one, which is the dedicated server app id. - Source(Option<(u32, Option)>), - /// A GoldSrc game, the argument indicates whether to enforce - /// requesting the obsolete A2S_INFO response or not. - GoldSrc(bool), -} - -impl Engine { - pub fn new_source(appid: u32) -> Self { - Engine::Source(Some((appid, None))) - } - - pub fn new_source_with_dedicated(appid: u32, dedicated_appid: u32) -> Self { - Engine::Source(Some((appid, Some(dedicated_appid)))) - } -} - -/// What data to gather, purely used only with the query function. -pub struct GatheringSettings { - pub players: bool, - pub rules: bool, -} - -impl Default for GatheringSettings { - /// Default values are true for both the players and the rules. - fn default() -> Self { - Self { - players: true, - rules: true, - } - } -} - -/// Generic response types that are used by many games, they are the protocol ones, but without the -/// unnecessary bits (example: the **The Ship**-only fields). -pub mod game { - use super::{Server, ServerPlayer}; - use crate::protocols::valve::types::get_optional_extracted_data; - use std::collections::HashMap; - - #[cfg(feature = "serde")] - use serde::{Deserialize, Serialize}; - - /// A player's details. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[derive(Debug, Clone, PartialEq, PartialOrd)] - pub struct Player { - /// Player's name. - pub name: String, - /// Player's score. - pub score: u32, - /// How long a player has been in the server (seconds). - pub duration: f32, - } - - impl Player { - pub fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration, - } - } - } - - /// The query response. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[derive(Debug, Clone, PartialEq)] - pub struct Response { - /// Protocol used by the server. - pub protocol: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub map: String, - /// The name of the game. - pub game: String, - /// Server's app id. - pub appid: u32, - /// Number of players on the server. - pub players_online: u8, - /// Details about the server's players (not all players necessarily). - pub players_details: Vec, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u8, - /// Number of bots on the server. - pub players_bots: u8, - /// Dedicated, NonDedicated or SourceTV - pub server_type: Server, - /// Indicates whether the server requires a password. - pub has_password: bool, - /// Indicated whether the server uses VAC. - pub vac_secured: bool, - /// Version of the game installed on the server. - pub version: String, - /// The server's reported connection port. - pub port: Option, - /// Server's SteamID. - pub steam_id: Option, - /// SourceTV's connection port. - pub tv_port: Option, - /// SourceTV's name. - pub tv_name: Option, - /// Keywords that describe the server according to it. - pub keywords: Option, - /// Server's rules. - pub rules: HashMap, - } - - impl Response { - pub fn new_from_valve_response(response: super::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = - get_optional_extracted_data(response.info.extra_data); - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - appid: response.info.appid, - players_online: response.info.players_online, - players_details: response - .players - .unwrap_or_default() - .iter() - .map(Player::from_valve_response) - .collect(), - players_maximum: response.info.players_maximum, - players_bots: response.info.players_bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap_or_default(), - } - } - } -} +use std::collections::HashMap; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The type of the server. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Server { + Dedicated, + NonDedicated, + TV, +} + +/// The Operating System that the server is on. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Environment { + Linux, + Windows, + Mac, +} + +/// A query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + pub info: ServerInfo, + pub players: Option>, + pub rules: Option>, +} + +/// General server information's. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ServerInfo { + /// Protocol used by the server. + pub protocol: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub map: String, + /// Name of the folder containing the game files. + pub folder: String, + /// The name of the game. + pub game: String, + /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. + pub appid: u32, + /// Number of players on the server. + pub players_online: u8, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u8, + /// Number of bots on the server. + pub players_bots: u8, + /// Dedicated, NonDedicated or SourceTV + pub server_type: Server, + /// The Operating System that the server is on. + pub environment_type: Environment, + /// Indicates whether the server requires a password. + pub has_password: bool, + /// Indicates whether the server uses VAC. + pub vac_secured: bool, + /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data + pub the_ship: Option, + /// Version of the game installed on the server. + pub version: String, + /// Some extra data that the server might provide or not. + pub extra_data: Option, + /// GoldSrc only: Indicates whether the hosted game is a mod. + pub is_mod: bool, + /// GoldSrc only: If the game is a mod, provide additional data. + pub mod_data: Option, +} + +/// A server player. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct ServerPlayer { + /// Player's name. + pub name: String, + /// General score. + pub score: u32, + /// How long a player has been in the server (seconds). + pub duration: f32, + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count + pub deaths: Option, // the_ship + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount + pub money: Option, // the_ship +} + +/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TheShip { + pub mode: u8, + pub witnesses: u8, + pub duration: u8, +} + +/// Some extra data that the server might provide or not. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ExtraData { + /// The server's game port number. + pub port: Option, + /// Server's SteamID. + pub steam_id: Option, + /// SourceTV's port. + pub tv_port: Option, + /// SourceTV's name. + pub tv_name: Option, + /// Keywords that describe the server according to it. + pub keywords: Option, + /// The server's 64-bit GameID. + pub game_id: Option, +} + +/// Data related to GoldSrc Mod response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ModData { + pub link: String, + pub download_link: String, + pub version: u32, + pub size: u32, + pub multiplayer_only: bool, + pub has_own_dll: bool, +} + +pub(crate) type ExtractedData = ( + Option, + Option, + Option, + Option, + Option, +); + +pub(crate) fn get_optional_extracted_data(data: Option) -> ExtractedData { + match data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords), + } +} + +/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). +#[derive(Eq, PartialEq, Copy, Clone)] +#[repr(u8)] +pub(crate) enum Request { + /// Known as `A2S_INFO` + Info = 0x54, + /// Known as `A2S_PLAYERS` + Players = 0x55, + /// Known as `A2S_RULES` + Rules = 0x56, +} + +/// Supported steam apps +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum SteamApp { + /// Counter-Strike + CS, + /// Team Fortress Classic + TFC, + /// Day of Defeat + DOD, + /// Counter-Strike: Condition Zero + CSCZ, + /// Counter-Strike: Source + CSS, + /// Day of Defeat: Source + DODS, + /// Half-Life 2 Deathmatch + HL2DM, + /// Half-Life Deathmatch: Source + HLDMS, + /// Team Fortress 2 + TF2, + /// Left 4 Dead + L4D, + /// Left 4 Dead + L4D2, + /// Alien Swarm + ALIENS, + /// Counter-Strike: Global Offensive + CSGO, + /// The Ship + TS, + /// Garry's Mod + GM, + /// Age of Chivalry + AOC, + /// Insurgency: Modern Infantry Combat + INSMIC, + /// ARMA 2: Operation Arrowhead + ARMA2OA, + /// Project Zomboid + PZ, + /// Insurgency + INS, + /// Sven Co-op + SC, + /// 7 Days To Die + SDTD, + /// Rust + RUST, + /// Vallistic Overkill + BO, + /// Don't Starve Together + DST, + /// BrainBread 2 + BB2, + /// Codename CURE + CCURE, + /// Black Mesa + BM, + /// Colony Survival + COSU, + /// Avorion + AVORION, + /// Day of Infamy + DOI, + /// The Forest + TF, + /// Unturned + UNTURNED, + /// ARK: Survival Evolved + ASE, + /// Battalion 1944 + BAT1944, + /// Insurgency: Sandstorm + INSS, + /// Alien Swarm: Reactive Drop + ASRD, + /// Risk of Rain 2 + ROR2, + /// Operation: Harsh Doorstop + OHD, + /// Onset + ONSET, + /// V Rising + VR, +} + +impl SteamApp { + /// Get the specified app as engine. + pub fn as_engine(&self) -> Engine { + match self { + SteamApp::CS => Engine::GoldSrc(false), // 10 + SteamApp::TFC => Engine::GoldSrc(false), // 20 + SteamApp::DOD => Engine::GoldSrc(false), // 30 + SteamApp::CSCZ => Engine::GoldSrc(false), // 80 + SteamApp::CSS => Engine::new_source(240), + SteamApp::DODS => Engine::new_source(300), + SteamApp::HL2DM => Engine::new_source(320), + SteamApp::HLDMS => Engine::new_source(360), + SteamApp::TF2 => Engine::new_source(440), + SteamApp::L4D => Engine::new_source(500), + SteamApp::L4D2 => Engine::new_source(550), + SteamApp::ALIENS => Engine::new_source(630), + SteamApp::CSGO => Engine::new_source(730), + SteamApp::TS => Engine::new_source(2400), + SteamApp::GM => Engine::new_source(4000), + SteamApp::AOC => Engine::new_source(17510), + SteamApp::INSMIC => Engine::new_source(17700), + SteamApp::ARMA2OA => Engine::new_source(33930), + SteamApp::PZ => Engine::new_source(108600), + SteamApp::INS => Engine::new_source(222880), + SteamApp::SC => Engine::GoldSrc(false), // 225840 + SteamApp::SDTD => Engine::new_source(251570), + SteamApp::RUST => Engine::new_source(252490), + SteamApp::BO => Engine::new_source(296300), + SteamApp::DST => Engine::new_source(322320), + SteamApp::BB2 => Engine::new_source(346330), + SteamApp::CCURE => Engine::new_source(355180), + SteamApp::BM => Engine::new_source(362890), + SteamApp::COSU => Engine::new_source(366090), + SteamApp::AVORION => Engine::new_source(445220), + SteamApp::DOI => Engine::new_source(447820), + SteamApp::TF => Engine::new_source(556450), + SteamApp::UNTURNED => Engine::new_source(304930), + SteamApp::ASE => Engine::new_source(346110), + SteamApp::BAT1944 => Engine::new_source(489940), + SteamApp::INSS => Engine::new_source(581320), + SteamApp::ASRD => Engine::new_source(563560), + SteamApp::ROR2 => Engine::new_source(632360), + SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), + SteamApp::ONSET => Engine::new_source(1105810), + SteamApp::VR => Engine::new_source(1604030), + } + } +} + +/// Engine type. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Engine { + /// A Source game, the argument represents the possible steam app ids, if + /// its **None**, let the query find it, if its **Some**, the query + /// fails if the response id is not the first one, which is the game app + /// id, or the other one, which is the dedicated server app id. + Source(Option<(u32, Option)>), + /// A GoldSrc game, the argument indicates whether to enforce + /// requesting the obsolete A2S_INFO response or not. + GoldSrc(bool), +} + +impl Engine { + pub fn new_source(appid: u32) -> Self { Engine::Source(Some((appid, None))) } + + pub fn new_source_with_dedicated(appid: u32, dedicated_appid: u32) -> Self { + Engine::Source(Some((appid, Some(dedicated_appid)))) + } +} + +/// What data to gather, purely used only with the query function. +pub struct GatheringSettings { + pub players: bool, + pub rules: bool, +} + +impl Default for GatheringSettings { + /// Default values are true for both the players and the rules. + fn default() -> Self { + Self { + players: true, + rules: true, + } + } +} + +/// Generic response types that are used by many games, they are the protocol +/// ones, but without the unnecessary bits (example: the **The Ship**-only +/// fields). +pub mod game { + use super::{Server, ServerPlayer}; + use crate::protocols::valve::types::get_optional_extracted_data; + use std::collections::HashMap; + + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + /// A player's details. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, PartialOrd)] + pub struct Player { + /// Player's name. + pub name: String, + /// Player's score. + pub score: u32, + /// How long a player has been in the server (seconds). + pub duration: f32, + } + + impl Player { + pub fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + } + } + } + + /// The query response. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq)] + pub struct Response { + /// Protocol used by the server. + pub protocol: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub map: String, + /// The name of the game. + pub game: String, + /// Server's app id. + pub appid: u32, + /// Number of players on the server. + pub players_online: u8, + /// Details about the server's players (not all players necessarily). + pub players_details: Vec, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u8, + /// Number of bots on the server. + pub players_bots: u8, + /// Dedicated, NonDedicated or SourceTV + pub server_type: Server, + /// Indicates whether the server requires a password. + pub has_password: bool, + /// Indicated whether the server uses VAC. + pub vac_secured: bool, + /// Version of the game installed on the server. + pub version: String, + /// The server's reported connection port. + pub port: Option, + /// Server's SteamID. + pub steam_id: Option, + /// SourceTV's connection port. + pub tv_port: Option, + /// SourceTV's name. + pub tv_name: Option, + /// Keywords that describe the server according to it. + pub keywords: Option, + /// Server's rules. + pub rules: HashMap, + } + + impl Response { + pub fn new_from_valve_response(response: super::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + appid: response.info.appid, + players_online: response.info.players_online, + players_details: response + .players + .unwrap_or_default() + .iter() + .map(Player::from_valve_response) + .collect(), + players_maximum: response.info.players_maximum, + players_bots: response.info.players_bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap_or_default(), + } + } + } +} diff --git a/src/socket.rs b/src/socket.rs index 493b3e3..37e78b6 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1,165 +1,168 @@ -use crate::protocols::types::TimeoutSettings; -use crate::utils::address_and_port_as_string; -use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; -use crate::GDResult; -use std::io::{Read, Write}; -use std::net; - -const DEFAULT_PACKET_SIZE: usize = 1024; - -pub trait Socket { - fn new(address: &str, port: u16) -> GDResult - where - Self: Sized; - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; - - fn send(&mut self, data: &[u8]) -> GDResult<()>; - fn receive(&mut self, size: Option) -> GDResult>; -} - -pub struct TcpSocket { - socket: net::TcpStream, -} - -impl Socket for TcpSocket { - fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port); - - Ok(Self { - socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?, - }) - } - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new - self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error - - Ok(()) - } - - fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.write(data).map_err(|_| PacketSend)?; - Ok(()) - } - - fn receive(&mut self, size: Option) -> GDResult> { - let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); - self.socket - .read_to_end(&mut buf) - .map_err(|_| PacketReceive)?; - - Ok(buf) - } -} - -pub struct UdpSocket { - socket: net::UdpSocket, - complete_address: String, -} - -impl Socket for UdpSocket { - fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port); - let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; - - Ok(Self { - socket, - complete_address, - }) - } - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new - self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error - - Ok(()) - } - - fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket - .send_to(data, &self.complete_address) - .map_err(|_| PacketSend)?; - Ok(()) - } - - fn receive(&mut self, size: Option) -> GDResult> { - let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; - let (number_of_bytes_received, _) = - self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; - - Ok(buf[..number_of_bytes_received].to_vec()) - } -} - -#[cfg(test)] -mod tests { - use std::thread; - - use super::*; - - #[test] - fn test_tcp_socket_send_and_receive() { - // Spawn a thread to run the server - let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let bound_address = listener.local_addr().unwrap(); - let server_thread = thread::spawn(move || { - let (mut stream, _) = listener.accept().unwrap(); - let mut buf = [0; 1024]; - stream.read(&mut buf).unwrap(); - stream.write(&buf).unwrap(); - }); - - // Create a TCP socket and send a message to the server - let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); - let message = b"hello, world!"; - socket.send(message).unwrap(); - - // Receive the response from the server - let received_message: Vec = socket - .receive(None) - .unwrap() - // Iterate over the buffer and remove 0s that are alone in the buffer - // just added to pass default size - .into_iter() - .filter(|&x| x != 0) - .collect(); - - server_thread.join().expect("server thread panicked"); - - assert_eq!(message, &received_message[..]); - } - - #[test] - fn test_udp_socket_send_and_receive() { - // Spawn a thread to run the server - let socket = net::UdpSocket::bind("127.0.0.1:0").unwrap(); - let bound_address = socket.local_addr().unwrap(); - let server_thread = thread::spawn(move || { - let mut buf = [0; 1024]; - let (_, src_addr) = socket.recv_from(&mut buf).unwrap(); - socket.send_to(&buf, src_addr).unwrap(); - }); - - // Create a UDP socket and send a message to the server - let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); - let message = b"hello, world!"; - socket.send(message).unwrap(); - - // Receive the response from the server - let received_message: Vec = socket - .receive(None) - .unwrap() - // Iterate over the buffer and remove 0s that are alone in the buffer - // just added to pass default size - .into_iter() - .filter(|&x| x != 0) - .collect(); - - server_thread.join().expect("server thread panicked"); - - assert_eq!(message, &received_message[..]); - } -} +use crate::{ + protocols::types::TimeoutSettings, + utils::address_and_port_as_string, + GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}, + GDResult, +}; + +use std::{ + io::{Read, Write}, + net, +}; + +const DEFAULT_PACKET_SIZE: usize = 1024; + +pub trait Socket { + fn new(address: &str, port: u16) -> GDResult + where Self: Sized; + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; + + fn send(&mut self, data: &[u8]) -> GDResult<()>; + fn receive(&mut self, size: Option) -> GDResult>; +} + +pub struct TcpSocket { + socket: net::TcpStream, +} + +impl Socket for TcpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = address_and_port_as_string(address, port); + + Ok(Self { + socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?, + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or_default(); + self.socket.set_read_timeout(settings.get_read()).unwrap(); // unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); // checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket.write(data).map_err(|_| PacketSend)?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); + self.socket + .read_to_end(&mut buf) + .map_err(|_| PacketReceive)?; + + Ok(buf) + } +} + +pub struct UdpSocket { + socket: net::UdpSocket, + complete_address: String, +} + +impl Socket for UdpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = address_and_port_as_string(address, port); + let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; + + Ok(Self { + socket, + complete_address, + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or_default(); + self.socket.set_read_timeout(settings.get_read()).unwrap(); // unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); // checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket + .send_to(data, &self.complete_address) + .map_err(|_| PacketSend)?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; + let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; + + Ok(buf[.. number_of_bytes_received].to_vec()) + } +} + +#[cfg(test)] +mod tests { + use std::thread; + + use super::*; + + #[test] + fn test_tcp_socket_send_and_receive() { + // Spawn a thread to run the server + let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let bound_address = listener.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut buf = [0; 1024]; + stream.read(&mut buf).unwrap(); + stream.write(&buf).unwrap(); + }); + + // Create a TCP socket and send a message to the server + let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } + + #[test] + fn test_udp_socket_send_and_receive() { + // Spawn a thread to run the server + let socket = net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let bound_address = socket.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let mut buf = [0; 1024]; + let (_, src_addr) = socket.recv_from(&mut buf).unwrap(); + socket.send_to(&buf, src_addr).unwrap(); + }); + + // Create a UDP socket and send a message to the server + let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } +} diff --git a/src/utils.rs b/src/utils.rs index e55b052..42a0033 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,39 +1,41 @@ -use std::cmp::Ordering; -use crate::GDResult; -use crate::GDError::{PacketOverflow, PacketUnderflow}; - -pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { - match size.cmp(&expected) { - Ordering::Greater => Err(PacketOverflow), - Ordering::Less => Err(PacketUnderflow), - Ordering::Equal => Ok(()) - } -} - -pub fn address_and_port_as_string(address: &str, port: u16) -> String { - format!("{}:{}", address, port) -} - -pub fn u8_lower_upper(n: u8) -> (u8, u8) { - (n & 15, n >> 4) -} - -#[cfg(test)] -mod tests { - #[test] - fn address_and_port_as_string() { - assert_eq!(super::address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); - } - - #[test] - fn u8_lower_upper() { - assert_eq!(super::u8_lower_upper(171), (11, 10)); - } - - #[test] - fn error_by_expected_size() { - assert!(super::error_by_expected_size(69, 69).is_ok()); - assert!(super::error_by_expected_size(69, 68).is_err()); - assert!(super::error_by_expected_size(69, 70).is_err()); - } -} +use crate::{ + GDError::{PacketOverflow, PacketUnderflow}, + GDResult, +}; + +use std::cmp::Ordering; + +pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { + match size.cmp(&expected) { + Ordering::Greater => Err(PacketOverflow), + Ordering::Less => Err(PacketUnderflow), + Ordering::Equal => Ok(()), + } +} + +pub fn address_and_port_as_string(address: &str, port: u16) -> String { format!("{}:{}", address, port) } + +pub fn u8_lower_upper(n: u8) -> (u8, u8) { (n & 15, n >> 4) } + +#[cfg(test)] +mod tests { + #[test] + fn address_and_port_as_string() { + assert_eq!( + super::address_and_port_as_string("192.168.0.1", 27015), + "192.168.0.1:27015" + ); + } + + #[test] + fn u8_lower_upper() { + assert_eq!(super::u8_lower_upper(171), (11, 10)); + } + + #[test] + fn error_by_expected_size() { + assert!(super::error_by_expected_size(69, 69).is_ok()); + assert!(super::error_by_expected_size(69, 68).is_err()); + assert!(super::error_by_expected_size(69, 70).is_err()); + } +}