#![feature(async_closure)]
use std::sync::Arc;
use tokio::net::{UdpSocket, ToSocketAddrs};
use std::io::{Result, ErrorKind, Error, Write};
use hex::FromHex;
use crate::model::{ShortQuery, LongQuery, packet};
use std::time::{SystemTime, UNIX_EPOCH};
use byteorder::{WriteBytesExt, BigEndian};
use rand::Rng;
use std::str;
use std::collections::HashMap;
use tokio::sync::Mutex;
#[cfg(test)]
mod tests;
pub mod model;
mod utils;
pub struct Client<A: ToSocketAddrs> {
socket: Arc<UdpSocket>,
remote: A,
}
impl<A: ToSocketAddrs> Client<A> {
pub async fn new(remote: A) -> Result<Self> {
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await?);
Ok(Client {
socket,
remote,
})
}
pub fn remote(&self) -> &A {
&self.remote
}
pub fn set_remote(&mut self, remote: A) {
self.remote = remote;
}
pub async fn short_query(&self) -> Result<ShortQuery> {
let mut random = rand::thread_rng();
let offline_msg_data = Vec::from_hex("00ffff00fefefefefdfdfdfd12345678").expect("Failed to read binary string!");
{
let mut buf: Vec<u8> = vec![0x01];
buf.write_i64::<BigEndian>(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64)?;
buf.extend(&offline_msg_data);
buf.write_u64::<BigEndian>(random.gen::<u64>())?;
self.socket.send_to(buf.as_slice(), &self.remote).await?;
}; let mut buf = [0u8; u16::MAX as usize];
let len = self.socket.recv(&mut buf).await?;
let data: Vec<String> = String::from_utf8_lossy(&buf[offline_msg_data.len()+19..=len])
.split(';').map(String::from).collect();
let mut gamemode = None;
let mut motd = vec![data[1].clone()];
if data.len() > 7 {
motd.push(data[7].clone());
gamemode = Some(data[8].clone())
}
Ok(ShortQuery {
game_edition: data[0].clone(),
motd,
protocol_version: data[2].parse().unwrap(),
game_version: data[3].clone(),
player_count: data[4].parse().unwrap(),
max_player_count: data[5].parse().unwrap(),
server_uid: data[6].clone(),
game_mode: gamemode,
game_mode_integer: None,
port: None,
port_v6: None
})
}
pub async fn long_query(&self) -> Result<LongQuery> {
let mut random = rand::thread_rng();
let ses_id: i32 = random.gen();
let challenge_token = self.gen_challenge_token(ses_id).await?;
{
let mut buf: Vec<u8> = Vec::new();
buf.write_u16::<BigEndian>(packet::MAGIC)?;
buf.write_u8(packet::STAT)?;
buf.write_i32::<BigEndian>(ses_id & 0x0F0F0F0F)?;
buf.write_i32::<BigEndian>(challenge_token)?;
buf.write_all([0x00].repeat(4).as_slice())?;
self.socket.send_to(buf.as_slice(), &self.remote).await?;
};
let mut buf = [0u8; u16::MAX as usize];
let len = self.socket.recv(&mut buf).await?;
match buf[0] {
packet::STAT => {
let data = &buf[16..=len];
let mut reg_data = &buf[16..=len];
let players: Mutex<Vec<String>> = Mutex::new(Vec::new());
let raw_data: Mutex<HashMap<&str, String>> = Mutex::new(HashMap::new());
let player_index = utils::slice_index(data, &packet::PLAYER_KEY);
if let Some(pi) = player_index {
reg_data = &data[0..=pi];
};
let a = async || -> Result<()> {
let mut arr = reg_data.split(|byte| byte == &0x00u8).collect::<Vec<&[u8]>>();
if arr.len() % 2 != 0 {
arr.pop();
}
let mut i: usize = 1;
for k in arr.iter().step_by(2) {
raw_data
.lock().await
.insert(str::from_utf8(*k).expect("Unable to decode key string"),
str::from_utf8(arr[i]).expect("Unable to decode value string").to_string());
i += 2;
}
Ok(())
};
let b = async || -> Result<()> {
if let Some(pi) = player_index {
let tmp = &data[pi+packet::PLAYER_KEY.len()..data.len()-3];
players.lock().await.extend(tmp.split(|byte| byte == &0x00u8)
.map(|arr| str::from_utf8(arr).expect("Failure decoding string!").to_string()));
};
Ok(())
};
tokio::try_join!(a(), b())?;
let reader = raw_data.lock().await;
let players = players.lock().await.to_vec();
Ok(LongQuery {
server_software: reader.get("server_engine").expect("Failed to find server_engine").clone(),
plugins: reader.get("plugins").expect("Failed to find plugins").clone(),
version: reader.get("version").expect("Failed to find version").clone(),
whitelist: reader.get("whitelist").expect("Failed to find whitelist").clone(),
players,
player_count: reader.get("numplayers").expect("Failed to find numplayers").parse().expect("Invalid Player Count!"),
max_players: reader.get("maxplayers").expect("Failed to find maxplayers").parse().expect("Invalid Max Player Count!"),
game_name: reader.get("game_id").expect("Failed to find gamename").clone(),
game_mode: reader.get("gametype").expect("Failed to find gametype").clone(),
map_name: reader.get("map").expect("Failed to find map").clone(),
host_name: reader.get("hostname").expect("Failed to find server_engine").clone(),
host_ip: reader.get("hostip").expect("Failed to find hostip").clone(),
host_port: reader.get("hostport").expect("Failed to find server_engine").parse().expect("Invalid Host Port!")
})
},
_ => Err(Error::new(ErrorKind::InvalidData, "Unexpected packet was received while awaiting 0x00 STAT"))
}
}
pub async fn gen_challenge_token(&self, sid: i32) -> Result<i32> {
let mut buf: Vec<u8> = Vec::new();
buf.write_u16::<BigEndian>(packet::MAGIC)?;
buf.write_u8(packet::HANDSHAKE)?;
buf.write_i32::<BigEndian>(sid & 0x0F0F0F0F)?;
self.socket.send_to(buf.as_slice(), &self.remote).await?;
drop(buf);
let mut buf = [0u8; (u16::MAX >> 2) as usize];
let len = self.socket.recv(&mut buf).await?;
match buf[0] {
packet::HANDSHAKE => {
Ok(String::from_utf8_lossy(&buf[5..len-1]).parse().expect("Invalid Challenge Token Received"))
},
_ => Err(Error::new(ErrorKind::InvalidData, "Wrong packet received perhaps an already opened session? (expected 0x01 Handshake)"))
}
}
}