netget/server/rcon/
wire.rs1use anyhow::{bail, ensure, Context, Result};
4use std::time::Duration;
5use tokio::io::{AsyncRead, AsyncReadExt};
6
7pub const MIN_SIZE: usize = 10;
9pub const MAX_PACKET: usize = 4096;
11pub const MAX_BODY_OUT: usize = MAX_PACKET - MIN_SIZE;
13pub const MAX_RESPONSE_BYTES: usize = 1024 * 1024;
15pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
17
18pub const SERVERDATA_AUTH: i32 = 3;
19pub const SERVERDATA_AUTH_RESPONSE: i32 = 2;
20pub const SERVERDATA_EXECCOMMAND: i32 = 2;
21pub const SERVERDATA_RESPONSE_VALUE: i32 = 0;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Packet {
25 pub id: i32,
26 pub kind: i32,
27 pub body: Vec<u8>,
28}
29
30impl Packet {
31 pub fn new(id: i32, kind: i32, body: impl Into<Vec<u8>>) -> Self {
32 Self {
33 id,
34 kind,
35 body: body.into(),
36 }
37 }
38 pub fn encode(&self) -> Vec<u8> {
39 let size = (8 + self.body.len() + 2) as i32;
40 let mut out = Vec::with_capacity(size as usize + 4);
41 out.extend_from_slice(&size.to_le_bytes());
42 out.extend_from_slice(&self.id.to_le_bytes());
43 out.extend_from_slice(&self.kind.to_le_bytes());
44 out.extend_from_slice(&self.body);
45 out.extend_from_slice(&[0, 0]);
46 out
47 }
48 pub fn text(&self) -> String {
49 String::from_utf8_lossy(&self.body).into_owned()
50 }
51}
52
53pub async fn read_packet<R: AsyncRead + Unpin>(
56 reader: &mut R,
57 deadline: Duration,
58 max: usize,
59) -> Result<Option<Packet>> {
60 tokio::time::timeout(deadline, async {
61 let mut size = [0u8; 4];
62 match reader.read_exact(&mut size).await {
63 Ok(_) => {}
64 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
65 Err(e) => return Err(e.into()),
66 }
67 let size = i32::from_le_bytes(size);
68 ensure!(
69 size >= MIN_SIZE as i32,
70 "RCON packet size {size} is below {MIN_SIZE}"
71 );
72 ensure!(
73 size as usize <= max,
74 "RCON packet size {size} exceeds {max}"
75 );
76 let mut rest = vec![0u8; size as usize];
77 reader.read_exact(&mut rest).await?;
78 let id = i32::from_le_bytes(rest[0..4].try_into()?);
79 let kind = i32::from_le_bytes(rest[4..8].try_into()?);
80 let mut body = rest[8..].to_vec();
81 if body.last() != Some(&0) {
83 bail!("RCON packet body is not NUL-terminated");
84 }
85 while body.last() == Some(&0) {
86 body.pop();
87 }
88 Ok(Some(Packet { id, kind, body }))
89 })
90 .await
91 .context("RCON read deadline")?
92}
93
94pub fn chunks(output: &[u8]) -> Vec<Vec<u8>> {
96 if output.is_empty() {
97 return vec![Vec::new()];
98 }
99 output.chunks(MAX_BODY_OUT).map(<[u8]>::to_vec).collect()
100}
101
102pub fn body_from_text(text: &str) -> Vec<u8> {
104 text.bytes().filter(|b| *b != 0).collect()
105}
106
107pub fn same_secret(a: &[u8], b: &[u8]) -> bool {
109 if a.len() != b.len() {
110 return false;
111 }
112 a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
113}