minecraft_client_rs/
client.rs

1use std::error::Error;
2use std::fmt;
3use std::io::prelude::*;
4use std::net::{Shutdown, TcpStream};
5use std::sync::atomic::{AtomicI32, Ordering};
6
7use crate::message;
8
9const MAX_MESSAGE_SIZE: usize = 4110; // https://wiki.vg/Rcon#Fragmentation
10
11#[derive(Debug)]
12struct RequestIDMismatchError;
13
14impl Error for RequestIDMismatchError {}
15
16impl fmt::Display for RequestIDMismatchError {
17	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18		write!(f, "received error response from server")
19	}
20}
21
22pub struct Client {
23	conn: TcpStream,
24	last_id: AtomicI32,
25}
26
27impl Client {
28	pub fn new(hostport: String) -> Result<Client, Box<dyn Error>> {
29		let conn = TcpStream::connect(hostport)?;
30		Ok(Client{
31			conn: conn,
32			last_id: AtomicI32::new(0),
33		})
34	}
35
36	pub fn close(&mut self) -> Result<(), Box<dyn Error>> {
37		self.conn.shutdown(Shutdown::Both)?;
38		Ok(())
39	}
40
41	pub fn authenticate(&mut self, password: String) -> Result<message::Message, Box<dyn Error>> {
42		self.send_message(message::MessageType::Authenticate as i32, password)
43	}
44
45	pub fn send_command(&mut self, command: String) -> Result<message::Message, Box<dyn Error>> {
46		self.send_message(message::MessageType::Command as i32, command)
47	}
48
49	fn next_id(&self) -> i32 {
50		let prev = self.last_id.load(Ordering::Relaxed);
51		let next = prev + 1;
52		self.last_id.store(next, Ordering::Relaxed);
53		next
54	}
55
56	fn send_message(&mut self, msg_type: i32, msg_body: String) -> Result<message::Message, Box<dyn Error>> {
57		let req_id = self.next_id();
58		let req = message::Message{
59			size: msg_body.len() as i32 + message::HEADER_SIZE,
60			id: req_id.clone(),
61			msg_type: msg_type,
62			body: msg_body,
63		};
64
65		self.conn.write_all(&message::encode_message(req)[..])?;
66		let mut resp_bytes = [0u8; MAX_MESSAGE_SIZE];
67		self.conn.read(&mut resp_bytes)?;
68		let resp = message::decode_message(resp_bytes.to_vec())?;
69
70		if req_id == resp.id {
71			Ok(resp)
72		} else {
73			Err(Box::new(RequestIDMismatchError))
74		}
75	}
76}