use anyhow::{bail, ensure, Context, Result};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncReadExt};
pub const MIN_SIZE: usize = 10;
pub const MAX_PACKET: usize = 4096;
pub const MAX_BODY_OUT: usize = MAX_PACKET - MIN_SIZE;
pub const MAX_RESPONSE_BYTES: usize = 1024 * 1024;
pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
pub const SERVERDATA_AUTH: i32 = 3;
pub const SERVERDATA_AUTH_RESPONSE: i32 = 2;
pub const SERVERDATA_EXECCOMMAND: i32 = 2;
pub const SERVERDATA_RESPONSE_VALUE: i32 = 0;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Packet {
pub id: i32,
pub kind: i32,
pub body: Vec<u8>,
}
impl Packet {
pub fn new(id: i32, kind: i32, body: impl Into<Vec<u8>>) -> Self {
Self {
id,
kind,
body: body.into(),
}
}
pub fn encode(&self) -> Vec<u8> {
let size = (8 + self.body.len() + 2) as i32;
let mut out = Vec::with_capacity(size as usize + 4);
out.extend_from_slice(&size.to_le_bytes());
out.extend_from_slice(&self.id.to_le_bytes());
out.extend_from_slice(&self.kind.to_le_bytes());
out.extend_from_slice(&self.body);
out.extend_from_slice(&[0, 0]);
out
}
pub fn text(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
}
pub async fn read_packet<R: AsyncRead + Unpin>(
reader: &mut R,
deadline: Duration,
max: usize,
) -> Result<Option<Packet>> {
tokio::time::timeout(deadline, async {
let mut size = [0u8; 4];
match reader.read_exact(&mut size).await {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e.into()),
}
let size = i32::from_le_bytes(size);
ensure!(
size >= MIN_SIZE as i32,
"RCON packet size {size} is below {MIN_SIZE}"
);
ensure!(
size as usize <= max,
"RCON packet size {size} exceeds {max}"
);
let mut rest = vec![0u8; size as usize];
reader.read_exact(&mut rest).await?;
let id = i32::from_le_bytes(rest[0..4].try_into()?);
let kind = i32::from_le_bytes(rest[4..8].try_into()?);
let mut body = rest[8..].to_vec();
if body.last() != Some(&0) {
bail!("RCON packet body is not NUL-terminated");
}
while body.last() == Some(&0) {
body.pop();
}
Ok(Some(Packet { id, kind, body }))
})
.await
.context("RCON read deadline")?
}
pub fn chunks(output: &[u8]) -> Vec<Vec<u8>> {
if output.is_empty() {
return vec![Vec::new()];
}
output.chunks(MAX_BODY_OUT).map(<[u8]>::to_vec).collect()
}
pub fn body_from_text(text: &str) -> Vec<u8> {
text.bytes().filter(|b| *b != 0).collect()
}
pub fn same_secret(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
}