rcon_client/
client.rs

1use crate::errors::RCONError;
2use crate::types::{ExecuteResponse, RCONRequest, RCONResponse};
3use crate::{AuthRequest, AuthResponse};
4use bytes::BufMut;
5use std::io::{Read, Write};
6use std::net::TcpStream;
7use std::time::Duration;
8
9const DEFAULT_READ_TIMEOUT: u64 = 30;
10const DEFAULT_WRITE_TIMEOUT: u64 = 30;
11
12/// Configuration for RCON client
13#[derive(Default)]
14pub struct RCONConfig {
15    /// URL to server listening RCON
16    /// example: `0.0.0.0:25575` or `donkey-engine.host:1337`
17    pub url: String,
18    /// Timeout in secs for commands sending to server
19    /// Default: 30 secs
20    pub write_timeout: Option<u64>,
21    /// Timeout in secs for response waiting from server
22    /// Default: 30 secs
23    pub read_timeout: Option<u64>,
24}
25
26/// Simple RCON client
27#[derive(Debug)]
28pub struct RCONClient {
29    pub url: String,
30    pub(self) socket: TcpStream,
31}
32
33/// RCON client
34impl RCONClient {
35    /// Create new connection
36    pub fn new(config: RCONConfig) -> Result<Self, RCONError> {
37        let socket = TcpStream::connect(&config.url).map_err(|err| {
38            RCONError::TcpConnectionError(format!("TCP connection error: {}", err))
39        })?;
40
41        socket
42            .set_write_timeout(Some(Duration::new(
43                config.write_timeout.unwrap_or(DEFAULT_WRITE_TIMEOUT),
44                0,
45            )))
46            .map_err(|err| {
47                RCONError::TcpConnectionError(format!("Cannot set socket write_timeout: {}", err))
48            })?;
49        socket
50            .set_read_timeout(Some(Duration::new(
51                config.read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT),
52                0,
53            )))
54            .map_err(|err| {
55                RCONError::TcpConnectionError(format!("Cannot set socket read_timeout: {}", err))
56            })?;
57
58        Ok(Self {
59            url: config.url,
60            socket,
61        })
62    }
63
64    /// Auth on game server
65    pub fn auth(&mut self, auth: AuthRequest) -> Result<AuthResponse, RCONError> {
66        let response = execute(
67            &mut self.socket,
68            auth.id as i32,
69            auth.request_type as i32,
70            auth.password,
71        )?;
72        Ok(AuthResponse {
73            id: response.response_id as isize,
74            response_type: response.response_type as u8,
75        })
76    }
77
78    /// Execute request
79    pub fn execute(&mut self, data: RCONRequest) -> Result<RCONResponse, RCONError> {
80        let response = execute(
81            &mut self.socket,
82            data.id as i32,
83            data.request_type as i32,
84            data.body,
85        )?;
86        Ok(RCONResponse {
87            id: response.response_id as isize,
88            response_type: response.response_type as u8,
89            body: response.response_body,
90        })
91    }
92}
93
94/// Make TCP request
95fn execute(
96    socket: &mut TcpStream,
97    id: i32,
98    request_type: i32,
99    data: String,
100) -> Result<ExecuteResponse, RCONError> {
101    // Make request
102    let request_length = (data.len() + 10) as i32;
103    let mut request_buffer: Vec<u8> = Vec::with_capacity(request_length as usize);
104    request_buffer.put_slice(&request_length.to_le_bytes());
105    request_buffer.put_slice(&(id).to_le_bytes());
106    request_buffer.put_slice(&(request_type).to_le_bytes());
107    request_buffer.put_slice(data.as_bytes());
108    request_buffer.put_slice(&[0x00, 0x00]);
109    socket
110        .write(&request_buffer[..])
111        .map_err(|err| RCONError::TcpConnectionError(format!("TCP request error {}", err)))?;
112
113    // Await response
114    let mut response_buffer = [0u8; 4];
115    socket
116        .read_exact(&mut response_buffer)
117        .map_err(|err| RCONError::TcpConnectionError(format!("TCP response error {}", err)))?;
118    let response_length = i32::from_le_bytes(response_buffer);
119    socket
120        .read_exact(&mut response_buffer)
121        .map_err(|err| RCONError::TcpConnectionError(format!("TCP response error {}", err)))?;
122    let response_id = i32::from_le_bytes(response_buffer);
123    socket
124        .read_exact(&mut response_buffer)
125        .map_err(|err| RCONError::TcpConnectionError(format!("TCP response error {}", err)))?;
126    let response_type = i32::from_le_bytes(response_buffer);
127
128    // Read response body with minimal changes
129    let response_body_length = response_length - 10;
130    let mut response_body_buffer = Vec::with_capacity(response_body_length as usize);
131    let mut temp_buffer = vec![0; response_body_length as usize];
132    let mut read_so_far = 0;
133
134    while read_so_far < response_body_length as usize {
135        match socket.read(&mut temp_buffer[read_so_far..]) {
136            Ok(0) => break, // No more data
137            Ok(n) => read_so_far += n,
138            Err(e) => {
139                eprintln!("Error reading response body: {}", e);
140                break; // Preserve data read so far
141            },
142        }
143    }
144
145    // Append only the read data to the response body buffer
146    response_body_buffer.extend_from_slice(&temp_buffer[..read_so_far]);
147
148    let response_body = String::from_utf8(response_body_buffer)
149        .map_err(|err| RCONError::TypeError(format!("TypeError: {}", err)))?;
150
151    // Attempt to read terminating nulls without throwing an error on failure
152    let mut terminating_nulls = [0u8; 2];
153    match socket.read_exact(&mut terminating_nulls) {
154        Ok(_) => {
155            // Successfully read the terminating nulls, you can add additional logic here if needed
156        },
157        Err(e) => {
158            // Log the error but do not throw it
159            eprintln!("Non-fatal error reading terminating nulls: {}", e);
160        },
161    }
162
163    Ok(ExecuteResponse {
164        response_id,
165        response_type,
166        response_body,
167    })
168}