use byteorder::LittleEndian; use crate::buffer::{Buffer, Utf8Decoder}; use crate::protocols::quake::types::Response; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; use crate::utils::retry_on_timeout; use crate::GDErrorKind::{PacketBad, TypeParse}; use crate::{GDErrorKind, GDResult}; use std::collections::HashMap; use std::net::SocketAddr; use std::slice::Iter; pub trait QuakeClient { type Player; fn get_send_header<'a>() -> &'a str; fn get_response_header<'a>() -> &'a str; fn parse_player_string(data: Iter<&str>) -> GDResult; } /// Send request and return result buffer. /// This function will retry fetch on timeouts. fn get_data( address: &SocketAddr, timeout_settings: &Option, ) -> GDResult> { let mut socket = UdpSocket::new(address, timeout_settings)?; retry_on_timeout( TimeoutSettings::get_retries_or_default(timeout_settings), move || get_data_impl::(&mut socket), ) } /// Send request and return result buffer (without retry logic). fn get_data_impl(socket: &mut UdpSocket) -> GDResult> { socket.send( &[ &[0xFF, 0xFF, 0xFF, 0xFF], Client::get_send_header().as_bytes(), &[0x00], ] .concat(), )?; let data = socket.receive(None)?; let mut bufferer = Buffer::::new(&data); if bufferer.read::()? != u32::MAX { return Err(PacketBad.context("Expected 4294967295")); } let response_header = Client::get_response_header().as_bytes(); if !bufferer.remaining_bytes().starts_with(response_header) { Err(GDErrorKind::PacketBad)?; } bufferer.move_cursor(response_header.len() as isize)?; Ok(bufferer.remaining_bytes().to_vec()) } fn get_server_values(bufferer: &mut Buffer) -> GDResult> { let data = bufferer.read_string::(Some([0x0A]))?; let mut data_split = data.split('\\').collect::>(); if let Some(first) = data_split.first() { if first == &"" { data_split.remove(0); } } let values = data_split.chunks(2); let mut vars: HashMap = HashMap::new(); for data in values { let key = data.first(); let value = data.get(1); if let Some(k) = key { if let Some(v) = value { vars.insert((*k).to_string(), (*v).to_string()); } } } Ok(vars) } fn get_players(bufferer: &mut Buffer) -> GDResult> { let mut players: Vec = Vec::new(); // 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::(Some([0x0A]))?; let data_split = data.split(' ').collect::>(); let data_iter = data_split.iter(); players.push(Client::parse_player_string(data_iter)?); } Ok(players) } pub fn client_query( address: &SocketAddr, timeout_settings: Option, ) -> GDResult> { let data = get_data::(address, &timeout_settings)?; let mut bufferer = Buffer::::new(&data); let mut server_vars = get_server_values(&mut bufferer)?; let players = get_players::(&mut bufferer)?; Ok(Response { name: server_vars .remove("hostname") .or(server_vars.remove("sv_hostname")) .ok_or(GDErrorKind::PacketBad)?, map: server_vars .remove("mapname") .or(server_vars.remove("map")) .ok_or(GDErrorKind::PacketBad)?, players_online: players.len() as u8, players_maximum: server_vars .remove("maxclients") .or(server_vars.remove("sv_maxclients")) .ok_or(GDErrorKind::PacketBad)? .parse() .map_err(|e| TypeParse.context(e))?, players, game_version: server_vars .remove("version") .or(server_vars.remove("*version")), unused_entries: server_vars, }) } pub fn remove_wrapping_quotes<'a>(string: &&'a str) -> &'a str { match string.starts_with('\"') && string.ends_with('\"') { false => string, true => &string[1 .. string.len() - 1], } }