rcon_rs/
lib.rs

1use std::convert::TryInto;
2use std::io::Read;
3use std::io::Write;
4use std::net::TcpStream;
5use std::str::from_utf8;
6use std::sync::atomic::{AtomicBool, AtomicI32, Ordering::Relaxed};
7
8const HEADER_LEN: usize = 10;
9const MAX_PACKET: usize = 4110;
10
11#[repr(i32)]
12pub enum PacketType {
13    Response,
14    _None,
15    Cmd,
16    Auth,
17}
18
19/// Packet struct
20///
21/// Used by the client to send and recieve data from the rcon connection
22///
23/// [`encode`]: #method.encode
24/// [`decode`]: #method.decode
25
26#[derive(Debug)]
27pub struct Packet {
28    /// length of packet data
29    pub len: i32,
30    /// packet id, when minecraft recieves a packet it response with a packet using the same id
31    pub id: i32,
32    /// packet type, Response, Cmd, Auth
33    pub packet_type: i32,
34    /// packet contents, can be a command for example
35    pub body: String,
36}
37
38/// Client struct
39///
40/// Created to communicate with the minecraft server
41///
42/// [`new`]: #method.encode
43/// [`auth`]: #method.auth
44/// [`next_id`]: #method.next_id
45/// [`send`]: #method.send 
46
47pub struct Client {
48    /// raw tcp stream to handle communication
49    conn: TcpStream,
50    /// the next corresponding packet id, starting from 0
51    next: AtomicI32,
52    /// store if client is authenticated or not
53    auth: AtomicBool,
54}
55
56impl Client {
57    /// Create a new client, the host and port is taken in then connected to via tcp
58    pub fn new(host: &str, port: &str) -> Client {
59        let conn =
60            TcpStream::connect(format!("{}:{}", host, port)).expect("failed to open tcp stream");
61
62        Client {
63            conn,
64            next: AtomicI32::new(0),
65            auth: AtomicBool::new(false),
66        }
67    }
68
69    /// Authenticate the client by sending a password packet and reading the response
70    pub fn auth(&mut self, password: &str) -> Result<(), ()> {
71        self.send(password, Some(PacketType::Auth))?;
72        self.auth = AtomicBool::new(true);
73        let mut response = [0u8; MAX_PACKET];
74        self.conn.read(&mut response).unwrap();
75        let _ = Packet::decode(response.to_vec());
76        Ok(())
77    }
78
79    /// increment the id stored by the client struct and return it's value
80    fn next_id(&mut self) -> i32 {
81        let new = self.next.load(Relaxed) + 1;
82        self.next.store(new, Relaxed);
83        new
84    }
85
86    /// send a message over the tcp stream
87    pub fn send(&mut self, cmd: &str, msg_type: Option<PacketType>) -> Result<(), ()> {
88        let msg_type = match msg_type {
89            Some(v) => v,
90            None => PacketType::Cmd,
91        };
92        let message = Packet {
93            len: (cmd.len() + HEADER_LEN) as i32,
94            id: self.next_id(),
95            packet_type: msg_type as i32,
96            body: cmd.to_string(),
97        };
98
99        self.conn.write_all(&message.encode()).unwrap();
100        Ok(())
101    }
102}
103
104impl Packet {
105    
106    /// encode packet struct into a byte vector
107    pub fn encode(&self) -> Vec<u8> {
108        let mut data: Vec<u8> = Vec::new();
109
110        let p = self;
111
112        let extends = vec![
113            p.len.to_le_bytes(),
114            p.id.to_le_bytes(),
115            p.packet_type.to_le_bytes(),
116        ];
117
118        for i in extends {
119            data.extend_from_slice(&i);
120        }
121
122        data.extend_from_slice(&p.body.as_bytes());
123        data.extend_from_slice(&[0, 0]);
124
125        data
126    }
127
128    /// decode byte vector into packet struct
129    pub fn decode(data: Vec<u8>) -> Packet {
130        let len = i32::from_le_bytes(data[0..4].try_into().unwrap());
131        let id = i32::from_le_bytes(data[0..4].try_into().unwrap());
132        let packet_type = i32::from_le_bytes(data[8..12].try_into().unwrap());
133
134        let mut body = "".to_string();
135        let body_len: usize = (len - 10).try_into().unwrap();
136
137        if body_len > 0 {
138            body = from_utf8(&data[12..12 + body_len]).unwrap().to_string();
139        }
140
141        Packet {
142            len,
143            id,
144            packet_type,
145            body,
146        }
147    }
148}