1#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
2#![warn(missing_docs)]
3
4use std::{error::Error, fmt::{self, Debug, Display, Formatter}, io::{self, Read, Write}, mem::size_of, net::{TcpStream, ToSocketAddrs}, sync::atomic::{AtomicBool, AtomicI32, Ordering::SeqCst}};
33
34use arrayvec::ArrayVec;
35
36pub const DEFAULT_RCON_PORT: u16 = 25575;
40
41pub const MAX_OUTGOING_PAYLOAD_LEN: usize = 1446; pub const MAX_INCOMING_PAYLOAD_LEN: usize = 4096; const HEADER_LEN: usize = 10;
55
56const LOGIN_TYPE: i32 = 3;
57
58const COMMAND_TYPE: i32 = 2;
59
60#[derive(Debug)]
64pub struct RconClient {
65
66 stream: TcpStream,
67 next_id: AtomicI32,
68 logged_in: AtomicBool
69
70}
71
72impl RconClient {
73
74 pub fn connect<A: ToSocketAddrs>(server_addr: A) -> io::Result<RconClient> {
84 let stream = TcpStream::connect(server_addr)?;
85 stream.set_nonblocking(false)?;
86 stream.set_read_timeout(None)?;
87 Ok(RconClient { stream, next_id: AtomicI32::new(0), logged_in: AtomicBool::new(false) })
88 }
89
90 pub fn is_logged_in(&self) -> bool {
106 self.logged_in.load(SeqCst)
107 }
108
109 fn send_log_in(&self, password: &str) -> Result<(), LogInError> {
110 if self.is_logged_in() {
111 Err(LogInError::AlreadyLoggedIn)?
112 }
113 let SendResponse { good_auth, payload: _ } = self.send(LogInPacket, password)?;
114 if good_auth {
115 Ok(())
116 } else {
117 Err(LogInError::BadPassword)
118 }
119 }
120
121 fn get_next_id(&self) -> i32 {
122 let mut id = self.next_id.fetch_add(1, SeqCst);
123 if id == -1 { id = self.next_id.fetch_add(1, SeqCst)
125 }
126 id
127 }
128
129 fn send<K: PacketKind>(&self, kind: K, payload: &str) -> Result<SendResponse, SendError> {
130 let _ = kind;
131 if payload.len() > MAX_OUTGOING_PAYLOAD_LEN {
132 Err(SendError::PayloadTooLong)?
133 }
134
135 const I32_LEN: usize = size_of::<i32>();
136
137 let out_len = i32::try_from(HEADER_LEN + payload.len()).expect("payload is too long");
138 let out_id = self.get_next_id();
139
140 let mut stream = &self.stream;
141 let mut out_buf: ArrayVec<u8, {I32_LEN + HEADER_LEN + MAX_OUTGOING_PAYLOAD_LEN}> = ArrayVec::new();
144 out_buf.write_all(&out_len.to_le_bytes())?;
145 out_buf.write_all(&out_id.to_le_bytes())?;
146 out_buf.write_all(&K::TYPE.to_le_bytes())?;
147 out_buf.write_all(payload.as_bytes())?;
148 out_buf.write_all(b"\0\0")?; debug_assert_eq!(out_buf.len(), I32_LEN + HEADER_LEN + payload.len());
150 stream.write_all(&mut out_buf)?;
151 stream.flush()?;
152
153 let mut in_len_bytes = [0; I32_LEN];
154 let mut in_id_bytes = [0; I32_LEN];
155 stream.read_exact(&mut in_len_bytes)?;
156 let in_len = i32::from_le_bytes(in_len_bytes);
157 stream.read_exact(&mut in_id_bytes)?;
158 let in_id = i32::from_le_bytes(in_id_bytes);
159 stream.read_exact(&mut [0; I32_LEN])?;
160 let payload_len = usize::try_from(in_len).expect("payload is too long") - HEADER_LEN;
161 let mut payload_buf = vec![0; payload_len];
162 stream.read_exact(&mut payload_buf)?;
163 stream.read_exact(&mut [0; 2])?; let good_auth = if in_id == -1 {
166 false
167 } else if in_id == out_id {
168 true
169 } else {
170 Err(io::Error::new(io::ErrorKind::InvalidData, K::INVLID_RESPONSE_ID_ERROR))?
171 };
172
173 if K::ACCEPTS_LONG_RESPONSES && payload_len >= MAX_INCOMING_PAYLOAD_LEN {
174 const CAP_COMMAND: &'static str = "seed";
175 let cap_len = i32::try_from(HEADER_LEN + CAP_COMMAND.len()).expect("cap payload is somehow too long");
176 let cap_id = self.get_next_id();
177 let mut cap_buf: ArrayVec<u8, {I32_LEN + HEADER_LEN + CAP_COMMAND.len()}> = ArrayVec::new();
178 cap_buf.write_all(&cap_len.to_le_bytes())?;
179 cap_buf.write_all(&cap_id.to_le_bytes())?;
180 cap_buf.write_all(&K::TYPE.to_le_bytes())?;
181 cap_buf.write_all(CAP_COMMAND.as_bytes())?;
182 cap_buf.write_all(b"\0\0")?;
183 debug_assert_eq!(cap_buf.len(), I32_LEN + HEADER_LEN + CAP_COMMAND.len());
184 stream.write_all(&mut cap_buf)?;
185 stream.flush()?;
186
187 loop {
188 stream.read_exact(&mut in_len_bytes)?;
189 let inner_in_len = i32::from_le_bytes(in_len_bytes);
190 stream.read_exact(&mut in_id_bytes)?;
191 let inner_in_id = i32::from_le_bytes(in_id_bytes);
192 stream.read_exact(&mut [0; I32_LEN])?;
193 let inner_payload_len = usize::try_from(inner_in_len).expect("payload is too long") - HEADER_LEN;
194 let mut inner_payload_buf = vec![0; inner_payload_len];
195 stream.read_exact(&mut inner_payload_buf)?;
196 stream.read_exact(&mut [0; 2])?;
197
198 if inner_in_id == cap_id {
199 break
200 } else if inner_in_id == in_id {
201 payload_buf.append(&mut inner_payload_buf);
202 } else if inner_in_id == -1 {
203 Err(io::Error::new(io::ErrorKind::InvalidData, "client became deauthenticated between packets"))?
204 } else {
205 Err(io::Error::new(io::ErrorKind::InvalidData, K::INVLID_RESPONSE_ID_ERROR))?
206 }
207 }
208 }
209
210 let payload = String::from_utf8(payload_buf).expect("response payload is not ASCII");
211 Ok(SendResponse { good_auth, payload })
212 }
213
214 pub fn log_in(&self, password: &str) -> Result<(), LogInError> {
226 self.send_log_in(password)?;
227 self.logged_in.store(true, SeqCst);
228 Ok(())
229 }
230
231 pub fn send_command(&self, command: &str) -> Result<String, CommandError> {
251 if !self.is_logged_in() {
252 Err(CommandError::NotLoggedIn)?
253 }
254 let SendResponse { good_auth, payload } = self.send(CommandPacket, command)?;
255 if good_auth {
256 Ok(payload)
257 } else {
258 Err(CommandError::NotLoggedIn)
259 }
260 }
261
262}
263
264trait PacketKind {
265
266 const ACCEPTS_LONG_RESPONSES: bool;
267
268 const TYPE: i32;
269
270 const INVLID_RESPONSE_ID_ERROR: &'static str;
271
272}
273
274struct LogInPacket;
275
276impl PacketKind for LogInPacket {
277
278 const ACCEPTS_LONG_RESPONSES: bool = false;
279
280 const TYPE: i32 = LOGIN_TYPE;
281
282 const INVLID_RESPONSE_ID_ERROR: &'static str = "response packet id mismatched with login packet id";
283
284}
285
286struct CommandPacket;
287
288impl PacketKind for CommandPacket {
289
290 const ACCEPTS_LONG_RESPONSES: bool = true;
291
292 const TYPE: i32 = COMMAND_TYPE;
293
294 const INVLID_RESPONSE_ID_ERROR: &'static str = "response packet id mismatched with command packet id";
295
296}
297
298#[derive(Debug)]
299struct SendResponse {
300
301 good_auth: bool,
302 payload: String
303
304}
305
306#[derive(Debug)]
308pub enum LogInError {
309
310 IO(io::Error),
312 PasswordTooLong,
314 AlreadyLoggedIn,
316 BadPassword
318
319}
320
321impl From<io::Error> for LogInError {
322
323 fn from(e: io::Error) -> Self {
324 LogInError::IO(e)
325 }
326
327}
328
329impl From<SendError> for LogInError {
330
331 fn from(e: SendError) -> Self {
332 match e {
333 SendError::IO(e) => LogInError::IO(e),
334 SendError::PayloadTooLong => LogInError::PasswordTooLong
335 }
336 }
337
338}
339
340impl Display for LogInError {
341
342 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
343 match self {
344 LogInError::IO(e) => Display::fmt(e, f),
345 LogInError::PasswordTooLong => write!(f, "password must be no longer than {} bytes", MAX_OUTGOING_PAYLOAD_LEN),
346 LogInError::AlreadyLoggedIn => write!(f, "tried to log in when already logged in"),
347 LogInError::BadPassword => write!(f, "tried to log in with incorrect password")
348 }
349 }
350
351}
352
353impl Error for LogInError {}
354
355#[derive(Debug)]
357pub enum CommandError {
358
359 IO(io::Error),
361 CommandTooLong,
363 NotLoggedIn
365
366}
367
368impl From<io::Error> for CommandError {
369
370 fn from(e: io::Error) -> Self {
371 CommandError::IO(e)
372 }
373
374}
375
376impl From<SendError> for CommandError {
377
378 fn from(e: SendError) -> Self {
379 match e {
380 SendError::IO(e) => CommandError::IO(e),
381 SendError::PayloadTooLong => CommandError::CommandTooLong
382 }
383 }
384
385}
386
387impl Display for CommandError {
388
389 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
390 match self {
391 CommandError::IO(e) => Display::fmt(e, f),
392 CommandError::CommandTooLong => write!(f, "command must be no longer than {} bytes", MAX_OUTGOING_PAYLOAD_LEN),
393 CommandError::NotLoggedIn => write!(f, "tried to send a command before logging in")
394 }
395 }
396
397}
398
399impl Error for CommandError {}
400
401#[derive(Debug)]
402enum SendError {
403
404 IO(io::Error),
405 PayloadTooLong
406
407}
408
409impl From<io::Error> for SendError {
410
411 fn from(e: io::Error) -> Self {
412 SendError::IO(e)
413 }
414
415}