use std::{
io::{Read, Write},
net::{TcpStream, ToSocketAddrs},
};
pub const MAX_PACKET_SIZE: usize = 4096;
const HEADER_SIZE: usize = 10;
#[derive(Debug)]
pub enum RconError {
AuthenticationFail,
ParsingError,
IOError(std::io::Error),
}
impl From<std::io::Error> for RconError {
fn from(value: std::io::Error) -> Self {
Self::IOError(value)
}
}
#[derive(Debug, Clone, Copy)]
#[repr(i32)]
pub enum PacketType {
Response = 0,
Command = 2,
Login = 3,
}
impl TryFrom<i32> for PacketType {
type Error = RconError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Response),
2 => Ok(Self::Command),
3 => Ok(Self::Login),
_ => Err(RconError::ParsingError),
}
}
}
#[derive(Debug)]
pub struct Packet {
pub request_id: i32,
pub packet_type: PacketType,
pub body: String,
}
impl Packet {
pub fn encode(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = vec![];
let length = (self.body.len() + HEADER_SIZE) as i32;
bytes.extend_from_slice(&length.to_le_bytes());
bytes.extend_from_slice(&self.request_id.to_le_bytes());
bytes.extend_from_slice(&(self.packet_type as i32).to_le_bytes());
bytes.extend_from_slice(self.body.as_bytes());
bytes.extend_from_slice(&[0, 0]);
bytes
}
pub fn decode(bytes: &[u8]) -> Result<Self, RconError> {
let size = i32::from_le_bytes(
bytes[0..4]
.try_into()
.map_err(|_| RconError::ParsingError)?,
);
let id = i32::from_le_bytes(
bytes[4..8]
.try_into()
.map_err(|_| RconError::ParsingError)?,
);
let msg_type = i32::from_le_bytes(
bytes[8..12]
.try_into()
.map_err(|_| RconError::ParsingError)?,
);
let mut body = String::new();
let body_len: usize = (size - HEADER_SIZE as i32)
.try_into()
.map_err(|_| RconError::ParsingError)?;
if body_len > 0 {
let body_bytes = std::str::from_utf8(&bytes[12..12 + body_len])
.map_err(|_| RconError::ParsingError)?;
body = body_bytes.to_string();
}
Ok(Self {
request_id: id,
packet_type: PacketType::try_from(msg_type)?,
body,
})
}
}
pub struct Client {
stream: TcpStream,
last_id: i32,
}
impl Client {
pub fn connect<A: ToSocketAddrs>(addr: A, password: &str) -> Result<Self, RconError> {
let mut client = Self {
stream: TcpStream::connect(addr)?,
last_id: 0,
};
let resp = client.send_packet(PacketType::Login, password)?;
if resp.request_id == -1 {
return Err(RconError::AuthenticationFail);
}
Ok(client)
}
fn send_packet(&mut self, packet_type: PacketType, body: &str) -> Result<Packet, RconError> {
let packet = Packet {
request_id: self.next_id(),
packet_type,
body: body.to_string(),
};
self.stream.write_all(&packet.encode())?;
let mut resp_bytes = [0u8; MAX_PACKET_SIZE];
self.stream.read(&mut resp_bytes)?;
Packet::decode(&resp_bytes)
}
pub fn send(&mut self, command: &str) -> Result<String, RconError> {
Ok(self.send_packet(PacketType::Command, command)?.body)
}
fn next_id(&mut self) -> i32 {
let id = self.last_id;
self.last_id += 1;
id
}
}