[Crate] Refactor: Buffer (#62)

* merge: local -> fresh pr

* fix: utf16 bugs + clean up

* docs: buffer

* [feat/buffer] Replaced errors, partial valve protocol ported

* fix: change buffer name + add endian switch

* chore: update module

* refactor: valve_master

* refactor: mc bedrock

* refactor: mc java

* refactor: mc types

* refactor: mc legacy 1.8

* refactor: mc legacy 1.4

* refactor: valve

* refactor: valve types

* refactor: quake

* refactor: mc legacy 1.6

* refactor: gamespy 1

* fix: make switch endian move cursor

* fix: reset cursor on switch

* chore: add switch endian tests

* chore: remove todo comment

* chore: clean up buffer generic types

* refactor: prop len when switching in mc bedrock

* fix: tests and current pos fn

* refactor: ffow

* refactor: jc2mp

* refactor: gs 3

* refactor: gs 2

* fix: mc bedrock prop on move + move data

* fix: mc java lifetime error

* fix: mc legacy 1.6 using pub not pub crate

* fix: quake client lifetime

* fix: quake 2 clippy warning

* fix: valve lifetime issue

* fix: buffer test

* chore: format to keep ci happy

* fix: buffer move_cursor

* fix: quake client

* feat: GameSpy 1 small optimization

* fix: incomplete gamespy 3 fix

* fix: gamespy 3 fix

* fix: minecraft java

* fix: minecraft bedrock

* feat: update the CHANGELOG to mention the buffer rewrite and thank @cainthebest for it

* fix: minecraft legacy 1.6

---------

Co-authored-by: CosminPerRam <cosmin.p@live.com>
This commit is contained in:
Cain 2023-07-18 09:46:53 +01:00 committed by GitHub
parent a8342296d6
commit 66cc39eb26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 859 additions and 568 deletions

View file

@ -1,6 +1,9 @@
use byteorder::LittleEndian;
use crate::buffer::Utf8Decoder;
use crate::protocols::gamespy::common::has_password;
use crate::{
bufferer::{Bufferer, Endianess},
buffer::Buffer,
protocols::{
gamespy::one::{Player, Response},
types::TimeoutSettings,
@ -29,9 +32,9 @@ fn get_server_values(
while !is_finished {
let data = socket.receive(None)?;
let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data);
let mut bufferer = Buffer::<LittleEndian>::new(&data);
let mut as_string = bufferer.get_string_utf8_unended()?;
let mut as_string = bufferer.read_string::<Utf8Decoder>(None)?;
as_string.remove(0);
let splited: Vec<String> = as_string.split('\\').map(str::to_string).collect();
@ -47,8 +50,7 @@ fn get_server_values(
server_values.insert(key, value);
}
is_finished = server_values.contains_key("final");
server_values.remove("final");
is_finished = server_values.remove("final").is_some();
let query_data = server_values.get("queryid");

View file

@ -1,4 +1,6 @@
use crate::bufferer::{Bufferer, Endianess};
use byteorder::{BigEndian, LittleEndian};
use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::gamespy::common::has_password;
use crate::protocols::gamespy::three::{Player, Response, Team};
use crate::protocols::types::TimeoutSettings;
@ -73,19 +75,19 @@ impl GameSpy3 {
})
}
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Bufferer> {
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Vec<u8>> {
let received = self.socket.receive(size.or(Some(PACKET_SIZE)))?;
let mut buf = Bufferer::new_with_data(Endianess::Big, &received);
let mut buf = Buffer::<BigEndian>::new(&received);
if buf.get_u8()? != kind {
if buf.read::<u8>()? != kind {
return Err(GDError::PacketBad);
}
if buf.get_u32()? != THIS_SESSION_ID {
if buf.read::<u32>()? != THIS_SESSION_ID {
return Err(GDError::PacketBad);
}
Ok(buf)
Ok(buf.remaining_bytes().to_vec())
}
fn make_initial_handshake(&mut self) -> GDResult<Option<i32>> {
@ -100,9 +102,10 @@ impl GameSpy3 {
.to_bytes(),
)?;
let mut buf = self.receive(Some(16), 9)?;
let data = self.receive(Some(16), 9)?;
let mut buf = Buffer::<LittleEndian>::new(&data);
let challenge_as_string = buf.get_string_utf8()?;
let challenge_as_string = buf.read_string::<Utf8Decoder>(None)?;
let challenge = challenge_as_string
.parse()
.map_err(|_| GDError::TypeParse)?;
@ -135,21 +138,22 @@ impl GameSpy3 {
let mut expected_number_of_packets: Option<usize> = None;
while expected_number_of_packets.is_none() || values.len() != expected_number_of_packets.unwrap() {
let mut buf = self.receive(None, 0)?;
let received_data = self.receive(None, 0)?;
let mut buf = Buffer::<BigEndian>::new(&received_data);
if self.single_packets {
buf.move_position_ahead(11);
return Ok(vec![buf.remaining_data().to_vec()]);
buf.move_cursor(11)?;
return Ok(vec![buf.remaining_bytes().to_vec()]);
}
if buf.get_string_utf8()? != "splitnum" {
if buf.read_string::<Utf8Decoder>(None)? != "splitnum" {
return Err(GDError::PacketBad);
}
let id = buf.get_u8()?;
let id = buf.read::<u8>()?;
let is_last = (id & 0x80) > 0;
let packet_id = (id & 0x7f) as usize;
buf.move_position_ahead(1); //unknown byte regarding packet no.
buf.move_cursor(1)?; //unknown byte regarding packet no.
if is_last {
expected_number_of_packets = Some(packet_id + 1);
@ -159,7 +163,7 @@ impl GameSpy3 {
values.push(Vec::new());
}
values[packet_id] = buf.remaining_data().to_vec();
values[packet_id] = buf.remaining_bytes().to_vec();
}
if values.iter().any(|v| v.is_empty()) {
@ -173,19 +177,19 @@ impl GameSpy3 {
pub(crate) fn data_to_map(packet: &[u8]) -> GDResult<(HashMap<String, String>, Vec<u8>)> {
let mut vars = HashMap::new();
let mut buf = Bufferer::new_with_data(Endianess::Big, packet);
while !buf.is_remaining_empty() {
let key = buf.get_string_utf8()?;
let mut buf = Buffer::<BigEndian>::new(packet);
while buf.remaining_length() != 0 {
let key = buf.read_string::<Utf8Decoder>(None)?;
if key.is_empty() {
break;
}
let value = buf.get_string_utf8_optional()?;
let value = buf.read_string::<Utf8Decoder>(None)?;
vars.insert(key, value);
}
Ok((vars, buf.remaining_data().to_vec()))
Ok((vars, buf.remaining_bytes().to_vec()))
}
/// If there are parsing problems using the `query` function, you can directly
@ -212,16 +216,16 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
let mut teams_data: Vec<HashMap<String, String>> = vec![HashMap::new()];
for packet in packets {
let mut buf = Bufferer::new_with_data(Endianess::Little, &packet);
let mut buf = Buffer::<LittleEndian>::new(&packet);
while !buf.is_remaining_empty() {
if buf.get_u8()? < 3 {
while buf.remaining_length() != 0 {
if buf.read::<u8>()? < 3 {
continue;
}
buf.move_position_backward(1);
buf.move_cursor(1)?;
let field = buf.get_string_utf8()?;
let field = buf.read_string::<Utf8Decoder>(None)?;
if field.is_empty() {
continue;
}
@ -248,15 +252,15 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
}
};
let mut offset = buf.get_u8()? as usize;
let mut offset = buf.read::<u8>()? as usize;
let data = match field_type.is_none() {
true => &mut players_data,
false => &mut teams_data,
};
while !buf.is_remaining_empty() {
let item = buf.get_string_utf8()?;
while buf.remaining_length() != 0 {
let item = buf.read_string::<Utf8Decoder>(None)?;
if item.is_empty() {
break;
}

View file

@ -1,8 +1,9 @@
use crate::bufferer::{Bufferer, Endianess};
use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::gamespy::two::{Player, Response, Team};
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket};
use crate::{GDError, GDResult};
use byteorder::BigEndian;
use std::collections::HashMap;
use std::net::SocketAddr;
@ -28,12 +29,12 @@ macro_rules! table_extract_parse {
};
}
fn data_as_table(data: &mut Bufferer) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
if data.get_u8()? != 0 {
fn data_as_table(data: &mut Buffer<BigEndian>) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
if data.read::<u8>()? != 0 {
Err(GDError::PacketBad)?
}
let rows = data.get_u8()? as usize;
let rows = data.read::<u8>()? as usize;
if rows == 0 {
return Ok((HashMap::new(), 0));
@ -41,21 +42,28 @@ fn data_as_table(data: &mut Bufferer) -> GDResult<(HashMap<String, Vec<String>>,
let mut column_heads = Vec::new();
let mut current_column = data.get_string_utf8()?;
let mut current_column = data.read_string::<Utf8Decoder>(None)?;
while !current_column.is_empty() {
column_heads.push(current_column);
current_column = data.get_string_utf8()?;
current_column = data.read_string::<Utf8Decoder>(None)?
}
let columns = column_heads.len();
let mut table = HashMap::with_capacity(columns);
for head in &column_heads {
table.insert(head.clone(), Vec::new()); // TODO: This doesn't look good nor it is performant, fix later
// TODO: This doesn't look good nor it is performant, fix later
// By using &column_heads in the for loop instead of cloning column_heads, you
// avoid creating an unnecessary copy. However, column_heads is a
// Vec<String> and head is a &String (a reference to a string). Hence, to use
// head as a key to the HashMap, we still need to call clone(). This is because
// HashMap takes ownership of its keys and we cannot give it a reference to a
// local variable (head) that will be dropped at the end of the function.
table.insert(head.clone(), Vec::new());
}
for _ in 0 .. rows {
for column in column_heads.iter() {
let value = data.get_string_utf8()?;
let value = data.read_string::<Utf8Decoder>(None)?;
table.get_mut(column).ok_or(GDError::PacketBad)?.push(value);
}
}
@ -71,36 +79,33 @@ impl GameSpy2 {
Ok(Self { socket })
}
fn request_data(&mut self) -> GDResult<Bufferer> {
fn request_data(&mut self) -> GDResult<(Vec<u8>, usize)> {
self.socket
.send(&[0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF])?;
let received = self.socket.receive(None)?;
let mut buf = Bufferer::new_with_data(Endianess::Big, &received);
if buf.get_u8()? != 0 {
let mut buf = Buffer::<BigEndian>::new(&received);
if buf.read::<u8>()? != 0 || buf.read::<u32>()? != 1 {
return Err(GDError::PacketBad);
}
if buf.get_u32()? != 1 {
return Err(GDError::PacketBad);
}
Ok(buf)
let buf_index = buf.current_position();
Ok((received, buf_index))
}
}
fn get_server_vars(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
fn get_server_vars(bufferer: &mut Buffer<BigEndian>) -> GDResult<HashMap<String, String>> {
let mut values = HashMap::new();
let mut done_processing_vars = false;
while !done_processing_vars && !bufferer.is_remaining_empty() {
let key = bufferer.get_string_utf8()?;
let value = bufferer.get_string_utf8_optional()?;
while !done_processing_vars && bufferer.remaining_length() != 0 {
let key = bufferer.read_string::<Utf8Decoder>(None)?;
let value = bufferer.read_string::<Utf8Decoder>(None)?;
if key.is_empty() {
if value.is_empty() {
bufferer.move_position_backward(1);
bufferer.move_cursor(-1)?;
done_processing_vars = true;
}
@ -113,7 +118,7 @@ fn get_server_vars(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>>
Ok(values)
}
fn get_teams(bufferer: &mut Bufferer) -> GDResult<Vec<Team>> {
fn get_teams(bufferer: &mut Buffer<BigEndian>) -> GDResult<Vec<Team>> {
let mut teams = Vec::new();
let (table, entries) = data_as_table(bufferer)?;
@ -128,7 +133,7 @@ fn get_teams(bufferer: &mut Bufferer) -> GDResult<Vec<Team>> {
Ok(teams)
}
fn get_players(bufferer: &mut Bufferer) -> GDResult<Vec<Player>> {
fn get_players(bufferer: &mut Buffer<BigEndian>) -> GDResult<Vec<Player>> {
let mut players = Vec::new();
let (table, entries) = data_as_table(bufferer)?;
@ -147,10 +152,13 @@ fn get_players(bufferer: &mut Bufferer) -> GDResult<Vec<Player>> {
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
let mut client = GameSpy2::new(address, timeout_settings)?;
let mut data = client.request_data()?;
let (data, buf_index) = client.request_data()?;
let mut server_vars = get_server_vars(&mut data)?;
let players = get_players(&mut data)?;
let mut buffer = Buffer::<BigEndian>::new(&data);
buffer.move_cursor(buf_index as isize)?;
let mut server_vars = get_server_vars(&mut buffer)?;
let players = get_players(&mut buffer)?;
let players_online = match server_vars.remove("numplayers") {
None => players.len(),
@ -171,7 +179,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?,
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?,
has_password: server_vars.remove("password").ok_or(GDError::PacketBad)? == "1",
teams: get_teams(&mut data)?,
teams: get_teams(&mut buffer)?,
players_maximum: server_vars
.remove("maxplayers")
.ok_or(GDError::PacketBad)?

View file

@ -1,8 +1,7 @@
// 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},
buffer::{Buffer, Utf8Decoder},
protocols::{
minecraft::{BedrockResponse, GameMode, Server},
types::TimeoutSettings,
@ -12,8 +11,11 @@ use crate::{
GDError::{PacketBad, TypeParse},
GDResult,
};
use std::net::SocketAddr;
use byteorder::LittleEndian;
pub struct Bedrock {
socket: UdpSocket,
}
@ -40,35 +42,36 @@ impl Bedrock {
fn get_info(&mut self) -> GDResult<BedrockResponse> {
self.send_status_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
let received = self.socket.receive(None)?;
let mut buffer = Buffer::<LittleEndian>::new(&received);
if buffer.get_u8()? != 0x1c {
if buffer.read::<u8>()? != 0x1c {
return Err(PacketBad);
}
// Checking for our nonce directly from a u64 (as the nonce is 8 bytes).
if buffer.get_u64()? != 9833440827789222417 {
if buffer.read::<u64>()? != 9833440827789222417 {
return Err(PacketBad);
}
// These 8 bytes are identical to the serverId string we receive in decimal
// below
buffer.move_position_ahead(8);
buffer.move_cursor(8)?;
// Verifying the magic value (as we need 16 bytes, cast to two u64 values)
if buffer.get_u64()? != 18374403896610127616 {
if buffer.read::<u64>()? != 18374403896610127616 {
return Err(PacketBad);
}
if buffer.get_u64()? != 8671175388723805693 {
if buffer.read::<u64>()? != 8671175388723805693 {
return Err(PacketBad);
}
let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize;
buffer.move_position_ahead(2);
let remaining_length = buffer.switch_endian_chunk(2)?.read::<u16>()? as usize;
error_by_expected_size(remaining_length, buffer.remaining_length())?;
let binding = buffer.get_string_utf8_unended()?;
let binding = buffer.read_string::<Utf8Decoder>(None)?;
let status: Vec<&str> = binding.split(';').collect();
// We must have at least 6 values

View file

@ -1,5 +1,5 @@
use crate::{
bufferer::{Bufferer, Endianess},
buffer::Buffer,
protocols::{
minecraft::{as_varint, get_string, get_varint, JavaResponse, Player, Server},
types::TimeoutSettings,
@ -8,8 +8,10 @@ use crate::{
GDError::{JsonParse, PacketBad},
GDResult,
};
use std::net::SocketAddr;
use byteorder::LittleEndian;
use serde_json::Value;
#[rustfmt::skip]
@ -43,14 +45,15 @@ impl Java {
.send(&[as_varint(data.len() as i32), data].concat())
}
fn receive(&mut self) -> GDResult<Bufferer> {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
fn receive(&mut self) -> GDResult<Vec<u8>> {
let data = &self.socket.receive(None)?;
let mut buffer = Buffer::<LittleEndian>::new(data);
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)
Ok(buffer.remaining_bytes().to_vec())
}
fn send_handshake(&mut self) -> GDResult<()> {
@ -82,7 +85,8 @@ impl Java {
self.send_status_request()?;
self.send_ping_request()?;
let mut buffer = self.receive()?;
let socket_data = self.receive()?;
let mut buffer = Buffer::<LittleEndian>::new(&socket_data);
if get_varint(&mut buffer)? != 0 {
// first var int is the packet id

View file

@ -1,5 +1,5 @@
use crate::{
bufferer::{Bufferer, Endianess},
buffer::{Buffer, Utf16Decoder},
protocols::{
minecraft::{JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
@ -9,8 +9,11 @@ use crate::{
GDError::{PacketBad, ProtocolFormat},
GDResult,
};
use std::net::SocketAddr;
use byteorder::BigEndian;
pub struct LegacyBV1_8 {
socket: TcpSocket,
}
@ -29,16 +32,16 @@ impl LegacyBV1_8 {
self.send_initial_request()?;
let data = self.socket.receive(None)?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.get_u8()? != 0xFF {
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
let length = buffer.read::<u16>()? * 2;
error_by_expected_size((length + 3) as usize, data.len())?;
let packet_string = buffer.get_string_utf16()?;
let packet_string = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;

View file

@ -1,5 +1,7 @@
use byteorder::BigEndian;
use crate::{
bufferer::{Bufferer, Endianess},
buffer::{Buffer, Utf16Decoder},
protocols::{
minecraft::{protocol::legacy_v1_6::LegacyV1_6, JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
@ -29,20 +31,20 @@ impl LegacyV1_4 {
self.send_initial_request()?;
let data = self.socket.receive(None)?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.get_u8()? != 0xFF {
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
let length = buffer.read::<u16>()? * 2;
error_by_expected_size((length + 3) as usize, data.len())?;
if LegacyV1_6::is_protocol(&mut buffer)? {
return LegacyV1_6::get_response(&mut buffer);
}
let packet_string = buffer.get_string_utf16()?;
let packet_string = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;

View file

@ -1,5 +1,7 @@
use byteorder::BigEndian;
use crate::{
bufferer::{Bufferer, Endianess},
buffer::{Buffer, Utf16Decoder},
protocols::{
minecraft::{JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
@ -36,29 +38,34 @@ impl LegacyV1_6 {
Ok(())
}
pub fn is_protocol(buffer: &mut Bufferer) -> GDResult<bool> {
pub(crate) fn is_protocol(buffer: &mut Buffer<BigEndian>) -> GDResult<bool> {
let state = buffer
.remaining_data()
.remaining_bytes()
.starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]);
if state {
buffer.move_position_ahead(6);
buffer.move_cursor(6)?;
}
Ok(state)
}
pub fn get_response(buffer: &mut Bufferer) -> GDResult<JavaResponse> {
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)?;
pub(crate) fn get_response(buffer: &mut Buffer<BigEndian>) -> GDResult<JavaResponse> {
// This is a specific order!
let version_protocol = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)?
.parse()
.map_err(|_| PacketBad)?;
let version_name = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let description = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
let online_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)?
.parse()
.map_err(|_| PacketBad)?;
let max_players = buffer
.read_string::<Utf16Decoder<BigEndian>>(None)?
.parse()
.map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name,
@ -78,13 +85,13 @@ impl LegacyV1_6 {
self.send_initial_request()?;
let data = self.socket.receive(None)?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
let mut buffer = Buffer::<BigEndian>::new(&data);
if buffer.get_u8()? != 0xFF {
if buffer.read::<u8>()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
let length = buffer.read::<u16>()? * 2;
error_by_expected_size((length + 3) as usize, data.len())?;
if !LegacyV1_6::is_protocol(&mut buffer)? {

View file

@ -3,7 +3,7 @@
// https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs
use crate::{
bufferer::Bufferer,
buffer::Buffer,
protocols::{
types::{CommonPlayer, CommonResponse, GenericPlayer},
GenericResponse,
@ -12,6 +12,7 @@ use crate::{
GDResult,
};
use byteorder::ByteOrder;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -179,14 +180,14 @@ impl GameMode {
}
}
pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult<i32> {
pub(crate) fn get_varint<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<i32> {
let mut result = 0;
let msb: u8 = 0b10000000;
let mask: u8 = !msb;
for i in 0 .. 5 {
let current_byte = buffer.get_u8()?;
let current_byte = buffer.read::<u8>()?;
result |= ((current_byte & mask) as i32) << (7 * i);
@ -227,12 +228,12 @@ pub(crate) fn as_varint(value: i32) -> Vec<u8> {
bytes
}
pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult<String> {
pub(crate) fn get_string<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<String> {
let length = get_varint(buffer)? as usize;
let mut text = Vec::with_capacity(length);
for _ in 0 .. length {
text.push(buffer.get_u8()?)
text.push(buffer.read::<u8>()?)
}
String::from_utf8(text).map_err(|_| PacketBad)

View file

@ -1,4 +1,6 @@
use crate::bufferer::{Bufferer, Endianess};
use byteorder::LittleEndian;
use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::quake::types::Response;
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket};
@ -15,10 +17,7 @@ pub(crate) trait QuakeClient {
fn parse_player_string(data: Iter<&str>) -> GDResult<Self::Player>;
}
fn get_data<Client: QuakeClient>(
address: &SocketAddr,
timeout_settings: Option<TimeoutSettings>,
) -> GDResult<Bufferer> {
fn get_data<Client: QuakeClient>(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Vec<u8>> {
let mut socket = UdpSocket::new(address)?;
socket.apply_timeout(timeout_settings)?;
@ -32,24 +31,24 @@ fn get_data<Client: QuakeClient>(
)?;
let data = socket.receive(None)?;
let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data);
let mut bufferer = Buffer::<LittleEndian>::new(&data);
if bufferer.get_u32()? != 4294967295 {
if bufferer.read::<u32>()? != 4294967295 {
return Err(GDError::PacketBad);
}
let response_header = Client::get_response_header().as_bytes();
if !bufferer.remaining_data().starts_with(response_header) {
if !bufferer.remaining_bytes().starts_with(response_header) {
Err(GDError::PacketBad)?
}
bufferer.move_position_ahead(response_header.len());
bufferer.move_cursor(response_header.len() as isize)?;
Ok(bufferer)
Ok(bufferer.remaining_bytes().to_vec()) //TODO: Maybe fix?
}
fn get_server_values(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
let data = bufferer.get_string_utf8_newline()?;
fn get_server_values(bufferer: &mut Buffer<LittleEndian>) -> GDResult<HashMap<String, String>> {
let data = bufferer.read_string::<Utf8Decoder>(Some([0x0A]))?;
let mut data_split = data.split('\\').collect::<Vec<&str>>();
if let Some(first) = data_split.first() {
if first == &"" {
@ -74,11 +73,14 @@ fn get_server_values(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String
Ok(vars)
}
fn get_players<Client: QuakeClient>(bufferer: &mut Bufferer) -> GDResult<Vec<Client::Player>> {
fn get_players<Client: QuakeClient>(bufferer: &mut Buffer<LittleEndian>) -> GDResult<Vec<Client::Player>> {
let mut players: Vec<Client::Player> = Vec::new();
while !bufferer.is_remaining_empty() && bufferer.remaining_data() != [0x00] {
let data = bufferer.get_string_utf8_newline()?;
// this needs to be looked at again as theres no way to check if the buffer has
// a remaining null byte the original code was:
// while !bufferer.is_remaining_empty() && bufferer.remaining_data() != [0x00]
while !bufferer.remaining_length() == 0 {
let data = bufferer.read_string::<Utf8Decoder>(Some([0x0A]))?;
let data_split = data.split(' ').collect::<Vec<&str>>();
let data_iter = data_split.iter();
@ -92,7 +94,8 @@ pub(crate) fn client_query<Client: QuakeClient>(
address: &SocketAddr,
timeout_settings: Option<TimeoutSettings>,
) -> GDResult<Response<Client::Player>> {
let mut bufferer = get_data::<Client>(address, timeout_settings)?;
let data = get_data::<Client>(address, timeout_settings)?;
let mut bufferer = Buffer::<LittleEndian>::new(&data);
let mut server_vars = get_server_values(&mut bufferer)?;
let players = get_players::<Client>(&mut bufferer)?;

View file

@ -56,10 +56,7 @@ impl QuakeClient for QuakeTwo {
None => Err(GDError::PacketBad)?,
Some(v) => remove_wrapping_quotes(v).to_string(),
},
address: match data.next() {
None => None,
Some(v) => Some(remove_wrapping_quotes(v).to_string()),
},
address: data.next().map(|v| remove_wrapping_quotes(v).to_string()),
})
}
}

View file

@ -1,5 +1,5 @@
use crate::{
bufferer::{Bufferer, Endianess},
buffer::Buffer,
protocols::{
types::TimeoutSettings,
valve::{
@ -27,7 +27,9 @@ use crate::{
use bzip2_rs::decoder::Decoder;
use crate::buffer::Utf8Decoder;
use crate::protocols::valve::Packet;
use byteorder::LittleEndian;
use std::collections::HashMap;
use std::net::SocketAddr;
@ -46,26 +48,27 @@ struct SplitPacket {
}
impl SplitPacket {
fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult<Self> {
let header = buffer.get_u32()?;
let id = buffer.get_u32()?;
fn new(engine: &Engine, protocol: u8, buffer: &mut Buffer<LittleEndian>) -> GDResult<Self> {
let header = buffer.read()?; //buffer.get_u32()?;
let id = buffer.read()?;
let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine {
Engine::GoldSrc(_) => {
let (lower, upper) = u8_lower_upper(buffer.get_u8()?);
let (lower, upper) = u8_lower_upper(buffer.read()?);
(lower, upper, 0, false, None, None)
}
Engine::Source(_) => {
let total = buffer.get_u8()?;
let number = buffer.get_u8()?;
let total = buffer.read()?;
let number = buffer.read()?;
let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) {
// certain apps with protocol = 7 dont have this field
false => buffer.get_u16()?,
false => buffer.read()?,
true => 1248,
};
let compressed = ((id >> 31) & 1) == 1;
let compressed = ((id >> 31) & 1u32) == 1u32;
let (decompressed_size, uncompressed_crc32) = match compressed {
false => (None, None),
true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)),
true => (Some(buffer.read()?), Some(buffer.read()?)),
};
(
total,
@ -87,7 +90,7 @@ impl SplitPacket {
compressed,
decompressed_size,
uncompressed_crc32,
payload: buffer.remaining_data().to_vec(),
payload: buffer.remaining_bytes().to_vec(),
})
}
@ -133,10 +136,10 @@ impl ValveProtocol {
fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult<Packet> {
let data = self.socket.receive(Some(buffer_size))?;
let mut buffer = Bufferer::new_with_data(Endianess::Little, &data);
let mut buffer = Buffer::<LittleEndian>::new(&data);
let header = buffer.get_u8()?;
buffer.move_position_backward(1);
let header: u8 = buffer.read()?;
buffer.move_cursor(-1)?;
if header == 0xFE {
// the packet is split
let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?;
@ -144,7 +147,7 @@ impl ValveProtocol {
for _ in 1 .. main_packet.total {
let new_data = self.socket.receive(Some(buffer_size))?;
buffer = Bufferer::new_with_data(Endianess::Little, &new_data);
buffer = Buffer::<LittleEndian>::new(&new_data);
let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?;
chunk_packets.push(chunk_packet);
}
@ -155,25 +158,21 @@ impl ValveProtocol {
main_packet.payload.extend(chunk_packet.payload);
}
let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?);
let payload = main_packet.get_payload()?; // Creating a non-temporary value here
let mut new_packet_buffer = Buffer::<LittleEndian>::new(&payload); // Using the non-temporary value here
Ok(Packet::new_from_bufferer(&mut new_packet_buffer)?)
} else {
Packet::new_from_bufferer(&mut buffer)
}
}
pub fn get_kind_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult<Bufferer> {
self.get_request_data(engine, protocol, kind as u8, kind.get_default_payload())
pub fn get_kind_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult<Vec<u8>> {
let data = self.get_request_data(engine, protocol, kind as u8, kind.get_default_payload())?;
Ok(data)
}
/// Ask for a specific request only.
pub fn get_request_data(
&mut self,
engine: &Engine,
protocol: u8,
kind: u8,
payload: Vec<u8>,
) -> GDResult<Bufferer> {
pub fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: u8, payload: Vec<u8>) -> GDResult<Vec<u8>> {
let request_initial_packet = Packet::new(kind, payload).to_bytes();
self.socket.send(&request_initial_packet)?;
@ -182,7 +181,7 @@ impl ValveProtocol {
// 'A'
let challenge = packet.payload;
const INFO: u8 = Request::Info as u8; // hmm, this could be unwanted and problematic
const INFO: u8 = Request::Info as u8;
let challenge_packet = Packet::new(
kind,
match kind {
@ -197,48 +196,47 @@ impl ValveProtocol {
packet = self.receive(engine, protocol, PACKET_SIZE)?;
}
let data = packet.payload;
Ok(Bufferer::new_with_data(Endianess::Little, &data))
Ok(packet.payload)
}
fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult<ServerInfo> {
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()? {
fn get_goldsrc_server_info(buffer: &mut Buffer<LittleEndian>) -> GDResult<ServerInfo> {
let _header: u8 = buffer.read()?; //get the header (useless info)
let _address: String = buffer.read_string::<Utf8Decoder>(None)?; //get the server address (useless info)
let name = buffer.read_string::<Utf8Decoder>(None)?;
let map = buffer.read_string::<Utf8Decoder>(None)?;
let folder = buffer.read_string::<Utf8Decoder>(None)?;
let game = buffer.read_string::<Utf8Decoder>(None)?;
let players = buffer.read()?;
let max_players = buffer.read()?;
let protocol = buffer.read()?;
let server_type = match buffer.read::<u8>()? {
68 => Server::Dedicated, //'D'
76 => Server::NonDedicated, //'L'
80 => Server::TV, //'P'
_ => Err(UnknownEnumCast)?,
};
let environment_type = match buffer.get_u8()? {
let environment_type = match buffer.read::<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 has_password = buffer.read::<u8>()? == 1;
let is_mod = buffer.read::<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,
link: buffer.read_string::<Utf8Decoder>(None)?,
download_link: buffer.read_string::<Utf8Decoder>(None)?,
version: buffer.read()?,
size: buffer.read()?,
multiplayer_only: buffer.read::<u8>()? == 1,
has_own_dll: buffer.read::<u8>()? == 1,
})
}
};
let vac_secured = buffer.get_u8()? == 1;
let bots = buffer.get_u8()?;
let vac_secured = buffer.read::<u8>()? == 1;
let bots = buffer.read::<u8>()?;
Ok(ServerInfo {
protocol,
@ -264,7 +262,8 @@ impl ValveProtocol {
/// Get the server information's.
fn get_server_info(&mut self, engine: &Engine) -> GDResult<ServerInfo> {
let mut buffer = self.get_kind_request_data(engine, 0, Request::Info)?;
let data = self.get_kind_request_data(engine, 0, Request::Info)?;
let mut buffer = Buffer::<LittleEndian>::new(&data);
if let Engine::GoldSrc(force) = engine {
if *force {
@ -272,58 +271,58 @@ impl ValveProtocol {
}
}
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 = Server::from_gldsrc(buffer.get_u8()?)?;
let environment_type = Environment::from_gldsrc(buffer.get_u8()?)?;
let has_password = buffer.get_u8()? == 1;
let vac_secured = buffer.get_u8()? == 1;
let protocol = buffer.read()?;
let name = buffer.read_string::<Utf8Decoder>(None)?;
let map = buffer.read_string::<Utf8Decoder>(None)?;
let folder = buffer.read_string::<Utf8Decoder>(None)?;
let game = buffer.read_string::<Utf8Decoder>(None)?;
let mut appid = buffer.read::<u16>()? as u32;
let players = buffer.read()?;
let max_players = buffer.read()?;
let bots = buffer.read()?;
let server_type = Server::from_gldsrc(buffer.read()?)?;
let environment_type = Environment::from_gldsrc(buffer.read()?)?;
let has_password = buffer.read::<u8>()? == 1;
let vac_secured = buffer.read::<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()?,
mode: buffer.read()?,
witnesses: buffer.read()?,
duration: buffer.read()?,
})
}
};
let version = buffer.get_string_utf8()?;
let extra_data = match buffer.get_u8() {
let version = buffer.read_string::<Utf8Decoder>(None)?;
let extra_data = match buffer.read::<u8>() {
Err(_) => None,
Ok(value) => {
Some(ExtraData {
port: match (value & 0x80) > 0 {
false => None,
true => Some(buffer.get_u16()?),
true => Some(buffer.read()?),
},
steam_id: match (value & 0x10) > 0 {
false => None,
true => Some(buffer.get_u64()?),
true => Some(buffer.read()?),
},
tv_port: match (value & 0x40) > 0 {
false => None,
true => Some(buffer.get_u16()?),
true => Some(buffer.read()?),
},
tv_name: match (value & 0x40) > 0 {
false => None,
true => Some(buffer.get_string_utf8()?),
true => Some(buffer.read_string::<Utf8Decoder>(None)?),
},
keywords: match (value & 0x20) > 0 {
false => None,
true => Some(buffer.get_string_utf8()?),
true => Some(buffer.read_string::<Utf8Decoder>(None)?),
},
game_id: match (value & 0x01) > 0 {
false => None,
true => {
let gid = buffer.get_u64()?;
let gid = buffer.read()?;
appid = (gid & ((1 << 24) - 1)) as u32;
Some(gid)
@ -357,25 +356,26 @@ impl ValveProtocol {
/// Get the server player's.
fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult<Vec<ServerPlayer>> {
let mut buffer = self.get_kind_request_data(engine, protocol, Request::Players)?;
let data = self.get_kind_request_data(engine, protocol, Request::Players)?;
let mut buffer = Buffer::<LittleEndian>::new(&data);
let count = buffer.get_u8()? as usize;
let count = buffer.read::<u8>()? as usize;
let mut players: Vec<ServerPlayer> = Vec::with_capacity(count);
for _ in 0 .. count {
buffer.move_position_ahead(1); //skip the index byte
buffer.move_cursor(1)?; //skip the index byte
players.push(ServerPlayer {
name: buffer.get_string_utf8()?,
score: buffer.get_u32()?,
duration: buffer.get_f32()?,
name: buffer.read_string::<Utf8Decoder>(None)?,
score: buffer.read()?,
duration: buffer.read()?,
deaths: match *engine == SteamApp::TS.as_engine() {
false => None,
true => Some(buffer.get_u32()?),
true => Some(buffer.read()?),
},
money: match *engine == SteamApp::TS.as_engine() {
false => None,
true => Some(buffer.get_u32()?),
true => Some(buffer.read()?),
},
});
}
@ -385,14 +385,15 @@ impl ValveProtocol {
/// Get the server's rules.
fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult<HashMap<String, String>> {
let mut buffer = self.get_kind_request_data(engine, protocol, Request::Rules)?;
let data = self.get_kind_request_data(engine, protocol, Request::Rules)?;
let mut buffer = Buffer::<LittleEndian>::new(&data);
let count = buffer.get_u16()? as usize;
let count = buffer.read::<u16>()? as usize;
let mut rules: HashMap<String, String> = HashMap::with_capacity(count);
for _ in 0 .. count {
let name = buffer.get_string_utf8()?;
let value = buffer.get_string_utf8()?;
let name = buffer.read_string::<Utf8Decoder>(None)?;
let value = buffer.read_string::<Utf8Decoder>(None)?;
rules.insert(name, value);
}

View file

@ -3,7 +3,8 @@ use std::collections::HashMap;
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
use crate::GDError::UnknownEnumCast;
use crate::GDResult;
use crate::{bufferer::Bufferer, protocols::GenericResponse};
use crate::{buffer::Buffer, protocols::GenericResponse};
use byteorder::LittleEndian;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -209,11 +210,11 @@ impl Packet {
}
}
pub fn new_from_bufferer(buffer: &mut Bufferer) -> GDResult<Self> {
pub fn new_from_bufferer(buffer: &mut Buffer<LittleEndian>) -> GDResult<Self> {
Ok(Self {
header: buffer.get_u32()?,
kind: buffer.get_u8()?,
payload: buffer.remaining_data().to_vec(),
header: buffer.read::<u32>()?,
kind: buffer.read::<u8>()?,
payload: buffer.remaining_bytes().to_vec(),
})
}