#![warn(missing_docs)]
use std::convert::TryInto;
use std::error::Error;
use std::fmt;
use std::string;
use std::{
io,
io::{Read, Write},
};
const MAX_INCOMING_SIZE: i32 = 4110;
const MAX_OUTGOING_PAYLOAD: usize = 1446;
#[derive(Debug)]
pub enum RCONError {
AuthFail,
PayloadTooLarge(usize),
BadSize(i32),
BadPacketType(i32),
BadPayload(string::FromUtf8Error),
IDMismatch,
IO(io::Error),
}
impl From<io::Error> for RCONError {
fn from(error: io::Error) -> Self {
Self::IO(error)
}
}
impl From<string::FromUtf8Error> for RCONError {
fn from(error: string::FromUtf8Error) -> Self {
Self::BadPayload(error)
}
}
impl fmt::Display for RCONError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AuthFail => {
write!(f, "Authentication failed")
}
Self::PayloadTooLarge(size) => {
write!(
f,
"The size of the supplied payload {} exceeds the maximum payload size of {}",
size, MAX_OUTGOING_PAYLOAD
)
},
Self::BadSize(size) => {
write!(
f,
"The size of the incoming packet {} exceeds the maximum size of {}",
size, MAX_INCOMING_SIZE
)
}
Self::BadPacketType(packet_type) => {
write!(
f,
"The type {} of the incoming packet is not a valid type for an incoming packet",
packet_type
)
}
Self::BadPayload(_) => write!(f, "Bad payload"),
Self::IDMismatch => {
write!(f, "The ID of the packet received from the server does not match the ID of the sent packet")
}
Self::IO(_) => write!(f, "IO error"),
}
}
}
impl Error for RCONError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::IO(err) => Some(err),
Self::BadPayload(err) => Some(err),
_ => None,
}
}
}
type Result<T> = std::result::Result<T, RCONError>;
#[repr(i32)]
enum PacketType {
AuthFail = -1,
Response = 0,
Command = 2,
Login = 3,
}
#[derive(Debug, PartialEq)]
pub struct Packet {
id: i32,
packet_type: i32,
pub payload: String,
}
impl Packet {
fn new(packet_type: PacketType, payload: String) -> Result<Self> {
if payload.len() <= MAX_OUTGOING_PAYLOAD {
Ok(Self {
id: rand::random(),
packet_type: packet_type as i32,
payload,
})
} else {
Err(RCONError::PayloadTooLarge(payload.len()))
}
}
fn size(&self) -> Result<i32> {
if self.payload.len() <= MAX_OUTGOING_PAYLOAD {
Ok(self.payload.len() as i32 + 10)
} else {
Err(RCONError::PayloadTooLarge(self.payload.len()))
}
}
fn auth_success_packet(id: i32) -> Self {
Self {
id,
packet_type: PacketType::Command as i32,
payload: String::from(""),
}
}
fn send<T: Write>(self, writer: &mut T) -> Result<()> {
writer.write(&self.size()?.to_le_bytes())?;
writer.write(&self.id.to_le_bytes())?;
writer.write(&self.packet_type.to_le_bytes())?;
writer.write(self.payload.as_bytes())?;
writer.write(&[0x0, 0x0])?;
Ok(())
}
pub fn receive<T: Read>(reader: &mut T) -> Result<Self> {
let mut buf = [0; 4];
reader.read_exact(&mut buf)?;
let size = i32::from_le_bytes(buf);
if size < 10 || size > MAX_INCOMING_SIZE {
return Err(RCONError::BadSize(size));
}
reader.read_exact(&mut buf)?;
let id = i32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let packet_type = i32::from_le_bytes(buf);
let mut payload = vec![0; (size - 10).try_into().unwrap()];
reader.read_exact(&mut payload)?;
let payload = String::from_utf8(payload)?;
reader.read_exact(&mut [0; 2])?;
match packet_type {
-1 => {
Err(RCONError::AuthFail)
},
0 | 2 => {
Ok(Self {
id,
packet_type,
payload,
})
}
other_type => Err(RCONError::BadPacketType(other_type))
}
}
}
pub struct Connection<T> {
connection: T,
}
impl<T: Read + Write> Connection<T> {
pub fn connect(mut connection: T, password: String) -> Result<Self> {
let packet = Packet::new(PacketType::Login, password)?;
let id = packet.id;
packet.send(&mut connection)?;
if Packet::receive(&mut connection)? == Packet::auth_success_packet(id) {
Ok(Self { connection })
} else {
Err(RCONError::AuthFail)
}
}
pub fn command(&mut self, command: String) -> Result<Packet> {
let packet = Packet::new(PacketType::Command, command)?;
let id = packet.id;
packet.send(&mut self.connection)?;
let resp = Packet::receive(&mut self.connection)?;
if resp.id == id {
Ok(resp)
} else {
Err(RCONError::IDMismatch)
}
}
}