[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

@ -16,6 +16,9 @@ Games:
- [Just Cause 2: Multiplayer](https://store.steampowered.com/app/259080/Just_Cause_2_Multiplayer_Mod/) support.
- [Warsow](https://warsow.net/) support.
Internal:
- Buffer reader rewrite, resulting in more data checks and better code quality (thanks [@cainthebest](https://github.com/cainthebest)).
### Breaking...
Protocols:
- Quake 2: Renamed the players frags field to score to be more inline with the other protocols.

548
src/buffer.rs Normal file
View file

@ -0,0 +1,548 @@
use crate::GDError::{PacketBad, PacketUnderflow};
use crate::GDResult;
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use std::{convert::TryInto, marker::PhantomData};
/// A struct representing a buffer with a specific byte order.
///
/// It's comprised of a byte slice that it reads from, a cursor to keep track of
/// the current position within the byte slice, and a `PhantomData` marker to
/// bind it to a specific byte order (BigEndian or LittleEndian).
///
/// The byte order is defined by the `B: ByteOrder` generic parameter.
pub(crate) struct Buffer<'a, B: ByteOrder> {
/// The byte slice that the buffer reads from.
data: &'a [u8],
/// The cursor marking our current position in the buffer.
cursor: usize,
/// A phantom field used to bind the `Buffer` to a specific `ByteOrder`.
_marker: PhantomData<B>,
}
impl<'a, B: ByteOrder> Buffer<'a, B> {
/// Creates and returns a new `Buffer` with the given data.
///
/// The cursor is set to the start of the buffer (position 0) upon
/// initialization.
///
/// # Arguments
///
/// * `data` - A byte slice that the buffer will read from.
pub(crate) fn new(data: &'a [u8]) -> Self {
Self {
data,
cursor: 0,
_marker: PhantomData,
}
}
pub(crate) fn current_position(&self) -> usize { self.cursor }
/// Returns the length of the remaining bytes from the current cursor
/// position.
pub(crate) fn remaining_length(&self) -> usize { self.data.len() - self.cursor }
/// Returns the length of the buffer data.
pub(crate) fn data_length(&self) -> usize { self.data.len() }
// Added for legacy support just for the refactoring
// Not Tested
pub(crate) fn remaining_bytes(&self) -> &[u8] { &self.data[self.cursor ..] }
/// Moves the cursor forward or backward by a specified offset.
///
/// # Arguments
///
/// * `offset` - The amount to move the cursor. Use a negative value to move
/// backwards.
///
/// # Errors
///
/// Returns a `BufferError` if the attempted move would position the cursor
/// out of bounds.
pub(crate) fn move_cursor(&mut self, offset: isize) -> GDResult<()> {
// Compute the new cursor position by adding the offset to the current cursor
// position. The checked_add method is used for safe addition,
// preventing overflow and underflow.
let new_cursor = (self.cursor as isize).checked_add(offset);
match new_cursor {
// If the addition was not successful (i.e., it resulted in an overflow or underflow),
// return an error indicating that the cursor is out of bounds.
None => Err(PacketBad),
// If the new cursor position is either less than zero (i.e., before the start of the buffer)
// or greater than the remaining length of the buffer (i.e., past the end of the buffer),
// return an error indicating that the cursor is out of bounds.
Some(x) if x < 0 || x as usize > self.data_length() => Err(PacketBad),
// If the new cursor position is within the bounds of the buffer, update the cursor
// position and return Ok.
Some(x) => {
self.cursor = x as usize;
Ok(())
}
}
}
/// Reads a value of type `T` from the buffer, and advances the cursor by
/// the size of `T`.
///
/// # Type Parameters
///
/// * `T` - The type of value to be read from the buffer. This type must
/// implement the `BufferRead` trait with the same byte order as the
/// buffer.
///
/// # Errors
///
/// Returns a `BufferError` if there is not enough data remaining in the
/// buffer to read a value of type `T`.
pub(crate) fn read<T: Sized + BufferRead<B>>(&mut self) -> GDResult<T> {
// Get the size of `T` in bytes.
let size = std::mem::size_of::<T>();
// Calculate remaining length of the buffer.
let remaining = self.remaining_length();
// If the size of `T` is larger than the remaining length, return an error
// because we don't have enough data left to read.
if size > remaining {
return Err(PacketUnderflow);
}
// Slice the data array from the current cursor position for `size` amount of
// bytes.
let bytes = &self.data[self.cursor .. self.cursor + size];
// Move the cursor forward by `size`.
self.cursor += size;
// Use the `read_from_buffer` function of the `BufferRead` implementation for
// `T` to convert the bytes into an instance of `T`.
T::read_from_buffer(bytes)
}
/// Reads a string from the buffer using a specified `StringDecoder`, until
/// an optional delimiter.
///
/// # Type Parameters
///
/// * `D` - The type of string decoder to use. This type must implement the
/// `StringDecoder` trait with the same byte order as the buffer.
///
/// # Arguments
///
/// * `until` - An optional delimiter. If provided, the method will read
/// until this
/// delimiter is encountered. If not provided, the method will read until
/// the default delimiter of the decoder.
///
/// # Errors
///
/// Returns a `BufferError` if there is an error decoding the string.
pub(crate) fn read_string<D: StringDecoder>(&mut self, until: Option<D::Delimiter>) -> GDResult<String> {
// Slice the data array from the current cursor position to the end.
let data_slice = &self.data[self.cursor ..];
// Use the provided delimiter if one was given, or default to the
// delimiter specified by the StringDecoder.
let delimiter = until.unwrap_or(D::DELIMITER);
// Invoke the decode_string function of the provided StringDecoder,
// passing in the remaining data slice, the mutable reference to the
// cursor, and the delimiter.
let result = D::decode_string(data_slice, &mut self.cursor, delimiter)?;
// If decoding was successful, return the decoded string. The cursor
// position has been updated within the decode_string call to reflect
// the new position after reading.
Ok(result)
}
}
/// A trait that provides an interface to switch endianness.
///
/// The trait `SwitchEndian` is used for types that have a specific
/// byte order (endianness) and can switch to another byte order.
/// The type of the switched endianness is determined by the associated
/// type `Output`.
///
/// The associated type `Output` must implement the `ByteOrder` trait.
pub(crate) trait SwitchEndian {
type Output: ByteOrder;
}
/// An implementation of `SwitchEndian` for `LittleEndian`.
///
/// The switched endianness type is `BigEndian`.
impl SwitchEndian for LittleEndian {
type Output = BigEndian;
}
/// An implementation of `SwitchEndian` for `BigEndian`.
///
/// The switched endianness type is `LittleEndian`.
impl SwitchEndian for BigEndian {
type Output = LittleEndian;
}
impl<'a, B: SwitchEndian + ByteOrder> Buffer<'a, B> {
/// Switches the byte order of a chunk in the buffer.
///
/// This method consumes the buffer and returns a new buffer
/// with a chunk of the original buffer's data, starting from the
/// original cursor position and of the given size, where the byte
/// order is switched according to the implementation
/// of `SwitchEndian` for `B`.
///
/// Note: The method also advances the cursor of the original buffer
/// by `size`.
///
/// # Parameters
///
/// * `size`: The size of the chunk to be taken from the original buffer.
pub(crate) fn switch_endian_chunk(&mut self, size: usize) -> GDResult<Buffer<'a, B::Output>> {
let old_cursor = self.cursor;
self.move_cursor(size as isize)?;
Ok(Buffer {
data: &self.data[old_cursor .. old_cursor + size],
cursor: 0,
_marker: PhantomData,
})
}
}
/// A trait defining a protocol for reading values of a certain type from a
/// buffer.
///
/// Implementors of this trait provide a method for reading their type from a
/// byte buffer with a specific byte order.
pub(crate) trait BufferRead<B: ByteOrder>: Sized {
fn read_from_buffer(data: &[u8]) -> GDResult<Self>;
}
/// Macro to implement the `BufferRead` trait for byte types.
///
/// This macro generates an implementation of the `BufferRead` trait for a
/// specified byte type. The implementation will read a single byte from the
/// buffer and convert it to the target type using the provided map function.
///
/// # Arguments
///
/// * `$type` - The target type to implement `BufferRead` for.
/// * `$map_func` - The function to map a byte to the target type.
macro_rules! impl_buffer_read_byte {
($type:ty, $map_func:expr) => {
impl<B: ByteOrder> BufferRead<B> for $type {
fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
// Use the `first` method to get the first byte from the data array.
data.first()
// Apply the $map_func function to convert the raw byte to the $type.
.map($map_func)
// If the data array is empty (and thus `first` returns None),
// `ok_or_else` will return a BufferError.
.ok_or_else(|| PacketBad)
}
}
};
}
/// Macro to implement the `BufferRead` trait for multi-byte types.
///
/// This macro generates an implementation of the `BufferRead` trait for a
/// specified multi-byte type. The implementation will read the appropriate
/// number of bytes from the buffer and convert them to the target type using
/// the provided read function.
///
/// # Arguments
///
/// * `$type` - The target type to implement `BufferRead` for.
/// * `$read_func` - The function to read the bytes into the target type.
macro_rules! impl_buffer_read {
($type:ty, $read_func:ident) => {
impl<B: ByteOrder> BufferRead<B> for $type {
fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
// Convert the byte slice into an array of the appropriate type.
let array = data.try_into().map_err(|_| {
// If conversion fails, return an error indicating the required and provided
// lengths.
PacketBad
})?;
// Use the provided function to read the data from the array into the given
// type.
Ok(B::$read_func(array))
}
}
};
}
impl_buffer_read_byte!(u8, |&b| b);
impl_buffer_read_byte!(i8, |&b| b as i8);
impl_buffer_read!(u16, read_u16);
impl_buffer_read!(i16, read_i16);
impl_buffer_read!(u32, read_u32);
impl_buffer_read!(i32, read_i32);
impl_buffer_read!(u64, read_u64);
impl_buffer_read!(i64, read_i64);
impl_buffer_read!(f32, read_f32);
impl_buffer_read!(f64, read_f64);
/// A trait defining a protocol for decoding strings from a buffer.
///
/// This trait should be implemented by types that can decode strings from a
/// byte buffer with a specific byte order and delimiter.
pub(crate) trait StringDecoder {
/// The type of the delimiter used by the decoder.
type Delimiter: AsRef<[u8]>;
/// The default delimiter used by the decoder.
const DELIMITER: Self::Delimiter;
/// Decodes a string from the provided byte slice, and updates the cursor
/// position accordingly.
///
/// # Arguments
///
/// * `data` - The byte slice to decode the string from.
/// * `cursor` - The current position in the byte slice.
/// * `delimiter` - The delimiter to use for decoding the string.
///
/// # Errors
///
/// Returns a `BufferError` if there is an error decoding the string.
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String>;
}
/// A decoder for UTF-8 encoded strings.
///
/// This decoder uses a single null byte (`0x00`) as the default delimiter.
pub(crate) struct Utf8Decoder;
impl StringDecoder for Utf8Decoder {
type Delimiter = [u8; 1];
const DELIMITER: Self::Delimiter = [0x00];
/// Decodes a UTF-8 string from the given data, updating the cursor position
/// accordingly.
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String> {
// Find the position of the delimiter in the data. If the delimiter is not
// found, the length of the data is returned.
let position = data
// Create an iterator over the data.
.iter()
// Find the position of the delimiter
.position(|&b| b == delimiter.as_ref()[0])
// If the delimiter is not found, use the whole data slice.
.unwrap_or(data.len());
// Convert the data until the found position into a UTF-8 string.
let result = std::str::from_utf8(
// Take a slice of data until the position.
&data[.. position]
)
// If the data cannot be converted into a UTF-8 string, return an error
.map_err(|_| PacketBad)?
// Convert the resulting &str into a String
.to_owned();
// Update the cursor position
// The +1 is to skip the delimiter
*cursor += position + 1;
Ok(result)
}
}
/// A decoder for UTF-16 encoded strings.
///
/// This decoder uses a pair of null bytes (`0x00, 0x00`) as the default
/// delimiter.
///
/// # Type Parameters
///
/// * `B` - The byte order to use when decoding the string.
pub(crate) struct Utf16Decoder<B: ByteOrder> {
_marker: PhantomData<B>,
}
impl<B: ByteOrder> StringDecoder for Utf16Decoder<B> {
type Delimiter = [u8; 2];
const DELIMITER: Self::Delimiter = [0x00, 0x00];
/// Decodes a UTF-16 string from the given data, updating the cursor
/// position accordingly.
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String> {
// Try to find the position of the delimiter in the data
let position = data
// Split the data into 2-byte chunks (as UTF-16 uses 2 bytes per character)
.chunks_exact(2)
// Find the position of the delimiter
.position(|chunk| chunk == delimiter.as_ref())
// If the delimiter is not found, use the whole data, otherwise use the position of the delimiter
.map_or(data.len(), |pos| pos * 2);
// Create a buffer of u16 values to hold the decoded characters
let mut paired_buf: Vec<u16> = vec![0; position / 2];
// Decode the data into the buffer
B::read_u16_into(&data[.. position], &mut paired_buf);
// Convert the buffer of u16 values into a String
let result = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?;
// Update the cursor position
// The +2 accounts for the delimiter
*cursor += position + 2;
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use byteorder::BigEndian;
#[test]
fn test_new_buffer() {
let data: &[u8] = &[1, 2, 3, 4];
let buffer = Buffer::<LittleEndian>::new(data);
assert_eq!(buffer.data, data);
assert_eq!(buffer.cursor, 0);
}
#[test]
fn test_remaining_length() {
let data: &[u8] = &[1, 2, 3, 4];
let mut buffer = Buffer::<LittleEndian>::new(data);
assert_eq!(buffer.remaining_length(), 4);
buffer.cursor = 2;
assert_eq!(buffer.remaining_length(), 2);
}
#[test]
fn test_move_cursor() {
let data: &[u8] = &[1, 2, 3, 4];
let mut buffer = Buffer::<LittleEndian>::new(data);
// Test moving forward
assert!(buffer.move_cursor(2).is_ok());
assert_eq!(buffer.cursor, 2);
// Test moving backward
assert!(buffer.move_cursor(-1).is_ok());
assert_eq!(buffer.cursor, 1);
// Test moving beyond data limits
assert!(buffer.move_cursor(5).is_err());
assert!(buffer.move_cursor(-2).is_err());
}
#[test]
fn test_switch_endian_chunk_le_be() {
let data = [0x01, 0x02, 0x03, 0x04];
let mut buffer = Buffer::<LittleEndian>::new(&data[..]);
let switched_buffer = buffer.switch_endian_chunk(2).unwrap();
assert_eq!(switched_buffer.data, [0x01, 0x02]);
assert_eq!(switched_buffer.cursor, 0);
assert_eq!(buffer.remaining_bytes(), [0x03, 0x04]);
assert_eq!(buffer.cursor, 2);
}
#[test]
fn test_switch_endian_chunk_be_le() {
let data = [0x01, 0x02, 0x03, 0x04];
let mut buffer = Buffer::<BigEndian>::new(&data[..]);
let switched_buffer = buffer.switch_endian_chunk(2).unwrap();
assert_eq!(switched_buffer.data, [0x01, 0x02]);
assert_eq!(switched_buffer.cursor, 0);
assert_eq!(buffer.remaining_bytes(), [0x03, 0x04]);
assert_eq!(buffer.cursor, 2);
}
#[test]
fn test_buffer_read_u8() {
let data: &[u8] = &[1, 2, 3, 4];
let mut buffer = Buffer::<LittleEndian>::new(data);
let result: Result<u8, _> = buffer.read();
assert_eq!(result.unwrap(), 1);
assert_eq!(buffer.cursor, 1);
}
#[test]
fn test_buffer_read_u16() {
let data: &[u8] = &[1, 2, 3, 4];
let mut buffer = Buffer::<LittleEndian>::new(data);
let result: Result<u16, _> = buffer.read();
assert_eq!(result.unwrap(), 0x0201);
assert_eq!(buffer.cursor, 2);
}
#[test]
fn test_buffer_read_u16_big_endian() {
let data: &[u8] = &[1, 2, 3, 4];
let mut buffer = Buffer::<BigEndian>::new(data);
let result: Result<u16, _> = buffer.read();
assert_eq!(result.unwrap(), 0x0102);
assert_eq!(buffer.cursor, 2);
}
#[test]
fn test_decode_string_utf8() {
let data: &[u8] = b"Hello\0World\0";
let mut cursor = 0;
let delimiter = [0x00];
let result = Utf8Decoder::decode_string(data, &mut cursor, delimiter);
assert_eq!(result.unwrap(), "Hello");
assert_eq!(cursor, 6);
}
#[test]
fn test_decode_string_utf16_le() {
let data: &[u8] = &[0x48, 0x00, 0x65, 0x00, 0x00, 0x00];
let mut cursor = 0;
let delimiter = [0x00, 0x00];
let result = Utf16Decoder::<LittleEndian>::decode_string(data, &mut cursor, delimiter);
assert_eq!(result.unwrap(), "He");
assert_eq!(cursor, 6);
}
#[test]
fn test_decode_string_utf16_be() {
let data: &[u8] = &[0x00, 0x48, 0x00, 0x65, 0x00, 0x00];
let mut cursor = 0;
let delimiter = [0x00, 0x00];
let result = Utf16Decoder::<BigEndian>::decode_string(data, &mut cursor, delimiter);
assert_eq!(result.unwrap(), "He");
assert_eq!(cursor, 6);
}
#[test]
fn test_buffer_underflow_error() {
let data: &[u8] = &[1, 2];
let mut buffer = Buffer::<LittleEndian>::new(data);
let result: Result<u32, _> = buffer.read();
assert_eq!(result.unwrap_err(), PacketUnderflow);
}
}

View file

@ -1,308 +0,0 @@
use crate::{
GDError::{PacketBad, PacketUnderflow},
GDResult,
};
use byteorder::{BigEndian, ByteOrder, LittleEndian};
pub enum Endianess {
Little,
Big,
}
pub struct Bufferer {
data: Vec<u8>,
endianess: Endianess,
position: usize,
}
impl Bufferer {
pub fn new_with_data(endianess: Endianess, data: &[u8]) -> Self {
Bufferer {
data: data.to_vec(),
endianess,
position: 0,
}
}
fn check_size(&self, by: usize) -> bool { by > self.remaining_length() }
pub fn get_u8(&mut self) -> GDResult<u8> {
if self.check_size(1) {
return Err(PacketUnderflow);
}
let value = self.data[self.position];
self.move_position_ahead(1);
Ok(value)
}
pub fn get_u16(&mut self) -> GDResult<u16> {
if self.check_size(2) {
return Err(PacketUnderflow);
}
let value = match self.endianess {
Endianess::Little => LittleEndian::read_u16(self.remaining_data()),
Endianess::Big => BigEndian::read_u16(self.remaining_data()),
};
self.move_position_ahead(2);
Ok(value)
}
pub fn get_u32(&mut self) -> GDResult<u32> {
if self.check_size(4) {
return Err(PacketUnderflow);
}
let value = match self.endianess {
Endianess::Little => LittleEndian::read_u32(self.remaining_data()),
Endianess::Big => BigEndian::read_u32(self.remaining_data()),
};
self.move_position_ahead(4);
Ok(value)
}
pub fn get_f32(&mut self) -> GDResult<f32> {
if self.check_size(4) {
return Err(PacketUnderflow);
}
let value = match self.endianess {
Endianess::Little => LittleEndian::read_f32(self.remaining_data()),
Endianess::Big => BigEndian::read_f32(self.remaining_data()),
};
self.move_position_ahead(4);
Ok(value)
}
pub fn get_u64(&mut self) -> GDResult<u64> {
if self.check_size(8) {
return Err(PacketUnderflow);
}
let value = match self.endianess {
Endianess::Little => LittleEndian::read_u64(self.remaining_data()),
Endianess::Big => BigEndian::read_u64(self.remaining_data()),
};
self.move_position_ahead(8);
Ok(value)
}
fn get_string_utf8_until(&mut self, until: u8) -> GDResult<String> {
let sub_buf = self.remaining_data();
if sub_buf.is_empty() {
return Err(PacketUnderflow);
}
let first_null_position = sub_buf.iter().position(|&x| x == until).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)
}
pub fn get_string_utf8(&mut self) -> GDResult<String> { self.get_string_utf8_until(0) }
pub fn get_string_utf8_newline(&mut self) -> GDResult<String> { self.get_string_utf8_until(10) }
pub fn get_string_utf8_optional(&mut self) -> GDResult<String> {
match self.get_string_utf8() {
Ok(data) => Ok(data),
Err(e) => {
match e {
PacketUnderflow => Ok(String::new()),
x => Err(x),
}
}
}
}
pub fn get_string_utf8_unended(&mut self) -> GDResult<String> {
let sub_buf = self.remaining_data();
if sub_buf.is_empty() {
return Err(PacketUnderflow);
}
let value = std::str::from_utf8(sub_buf)
.map_err(|_| PacketBad)?
.to_string();
self.move_position_ahead(value.len());
Ok(value)
}
pub fn get_string_utf16(&mut self) -> GDResult<String> {
let sub_buf = self.remaining_data();
if sub_buf.is_empty() {
return Err(PacketUnderflow);
}
let paired_buf: Vec<u16> = sub_buf
.chunks_exact(2)
.map(|pair| {
match self.endianess {
Endianess::Little => LittleEndian::read_u16(pair),
Endianess::Big => BigEndian::read_u16(pair),
}
})
.collect();
let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?;
self.move_position_ahead(value.len() * 2);
Ok(value)
}
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 remaining_data(&self) -> &[u8] { &self.data[self.position ..] }
pub fn remaining_length(&self) -> usize { self.data.len() - self.position }
pub fn is_remaining_empty(&self) -> bool { self.remaining_length() == 0 }
pub fn as_endianess(&self, endianess: Endianess) -> Self {
Bufferer {
data: self.data.clone(),
endianess,
position: self.position,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_u8() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72]);
assert_eq!(buffer.get_u8().unwrap(), 72);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u8().is_err());
}
#[test]
fn get_u16_le() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 79]);
assert_eq!(buffer.get_u16().unwrap(), 20296);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u16().is_err());
}
#[test]
fn get_u16_be() {
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[29, 72]);
assert_eq!(buffer.get_u16().unwrap(), 7496);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u16().is_err());
}
#[test]
fn get_u32_le() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]);
assert_eq!(buffer.get_u32().unwrap(), 1686117704);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u32().is_err());
}
#[test]
fn get_u32_be() {
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]);
assert_eq!(buffer.get_u32().unwrap(), 1209892964);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u32().is_err());
}
#[test]
fn get_f32_le() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]);
assert_eq!(buffer.get_f32().unwrap(), 1.8906345e22);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_f32().is_err());
}
#[test]
fn get_f32_be() {
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]);
assert_eq!(buffer.get_f32().unwrap(), 161281.56);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_f32().is_err());
}
#[test]
fn get_u64_le() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 99, 69, 4, 2, 0]);
assert_eq!(buffer.get_u64().unwrap(), 567646022016328);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u64().is_err());
}
#[test]
fn get_u64_be() {
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 99, 69, 4, 2, 0]);
assert_eq!(buffer.get_u64().unwrap(), 5196450708903428608);
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_u64().is_err());
}
#[test]
fn get_string_utf8() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111, 0, 72]);
assert_eq!(buffer.get_string_utf8().unwrap(), "Hello");
assert_eq!(buffer.remaining_length(), 1);
assert!(buffer.get_string_utf8().is_err());
}
#[test]
fn get_string_utf8_unended() {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111]);
assert_eq!(buffer.get_string_utf8_unended().unwrap(), "Hello");
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_string_utf8_unended().is_err());
}
#[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],
);
assert_eq!(buffer.get_string_utf16().unwrap(), "Hello");
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_string_utf16().is_err());
}
#[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],
);
assert_eq!(buffer.get_string_utf16().unwrap(), "Hello");
assert_eq!(buffer.remaining_length(), 0);
assert!(buffer.get_string_utf16().is_err());
}
}

View file

@ -1,7 +1,9 @@
use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::types::{CommonResponse, TimeoutSettings};
use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol};
use crate::protocols::GenericResponse;
use crate::GDResult;
use byteorder::LittleEndian;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, SocketAddr};
@ -68,31 +70,33 @@ pub fn query_with_timeout(
&SocketAddr::new(*address, port.unwrap_or(5478)),
timeout_settings,
)?;
let mut buffer = client.get_request_data(
let data = client.get_request_data(
&Engine::GoldSrc(true),
0,
0x46,
String::from("LSQ").into_bytes(),
)?;
let protocol = buffer.get_u8()?;
let name = buffer.get_string_utf8()?;
let map = buffer.get_string_utf8()?;
let active_mod = buffer.get_string_utf8()?;
let game_mode = buffer.get_string_utf8()?;
let description = buffer.get_string_utf8()?;
let version = buffer.get_string_utf8()?;
buffer.move_position_ahead(2);
let players_online = buffer.get_u8()?;
let players_maximum = 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;
buffer.move_position_ahead(1); //average fps
let round = buffer.get_u8()?;
let rounds_maximum = buffer.get_u8()?;
let time_left = buffer.get_u16()?;
let mut buffer = Buffer::<LittleEndian>::new(&data);
let protocol = buffer.read::<u8>()?;
let name = buffer.read_string::<Utf8Decoder>(None)?;
let map = buffer.read_string::<Utf8Decoder>(None)?;
let active_mod = buffer.read_string::<Utf8Decoder>(None)?;
let game_mode = buffer.read_string::<Utf8Decoder>(None)?;
let description = buffer.read_string::<Utf8Decoder>(None)?;
let version = buffer.read_string::<Utf8Decoder>(None)?;
buffer.move_cursor(2)?;
let players_online = buffer.read::<u8>()?;
let players_maximum = buffer.read::<u8>()?;
let server_type = Server::from_gldsrc(buffer.read::<u8>()?)?;
let environment_type = Environment::from_gldsrc(buffer.read::<u8>()?)?;
let has_password = buffer.read::<u8>()? == 1;
let vac_secured = buffer.read::<u8>()? == 1;
buffer.move_cursor(1)?; //average fps
let round = buffer.read::<u8>()?;
let rounds_maximum = buffer.read::<u8>()?;
let time_left = buffer.read::<u16>()?;
Ok(Response {
protocol,

View file

@ -1,9 +1,10 @@
use crate::bufferer::{Bufferer, Endianess};
use crate::buffer::{Buffer, Utf8Decoder};
use crate::protocols::gamespy::common::has_password;
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings};
use crate::protocols::GenericResponse;
use crate::{GDError, GDResult};
use byteorder::BigEndian;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, SocketAddr};
@ -59,16 +60,16 @@ impl CommonResponse for Response {
}
fn parse_players_and_teams(packet: Vec<u8>) -> GDResult<Vec<Player>> {
let mut buf = Bufferer::new_with_data(Endianess::Big, &packet);
let mut buf = Buffer::<BigEndian>::new(&packet);
let count = buf.get_u16()?;
let count = buf.read::<u16>()?;
let mut players = Vec::with_capacity(count as usize);
while !buf.is_remaining_empty() {
while buf.remaining_length() != 0 {
players.push(Player {
name: buf.get_string_utf8()?,
steam_id: buf.get_string_utf8()?,
ping: buf.get_u16()?,
name: buf.read_string::<Utf8Decoder>(None)?,
steam_id: buf.read_string::<Utf8Decoder>(None)?,
ping: buf.read::<u16>()?,
})
}

View file

@ -41,7 +41,7 @@ pub mod protocols;
#[cfg(feature = "services")]
pub mod services;
mod bufferer;
mod buffer;
mod socket;
mod utils;

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

View file

@ -1,9 +1,15 @@
use crate::bufferer::{Bufferer, Endianess};
use crate::socket::{Socket, UdpSocket};
use crate::valve_master_server::{Region, SearchFilters};
use crate::{GDError, GDResult};
use crate::{
buffer::Buffer,
socket::{Socket, UdpSocket},
valve_master_server::{Region, SearchFilters},
GDError,
GDResult,
};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use byteorder::BigEndian;
/// The default master ip, which is the one for Source.
pub fn default_master_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(208, 64, 201, 194)), 27011) // hl2master.steampowered.com
@ -63,21 +69,22 @@ impl ValveMasterServer {
self.socket.send(&payload)?;
let received_data = self.socket.receive(Some(1400))?;
let mut buf = Bufferer::new_with_data(Endianess::Big, &received_data);
let mut buf = Buffer::<BigEndian>::new(&received_data);
if buf.get_u32()? != 4294967295 || buf.get_u16()? != 26122 {
if buf.read::<u32>()? != 4294967295 || buf.read::<u16>()? != 26122 {
return Err(GDError::PacketBad);
}
let mut ips: Vec<(IpAddr, u16)> = Vec::new();
while !buf.is_remaining_empty() {
while !buf.remaining_length() == 0 {
let ip = IpAddr::V4(Ipv4Addr::new(
buf.get_u8()?,
buf.get_u8()?,
buf.get_u8()?,
buf.get_u8()?,
buf.read::<u8>()?,
buf.read::<u8>()?,
buf.read::<u8>()?,
buf.read::<u8>()?,
));
let port = buf.get_u16()?;
let port = buf.read::<u16>()?;
ips.push((ip, port));
}