separate client and wrap as lib
This commit is contained in:
parent
d3a7cebc13
commit
b7669181a1
3 changed files with 114 additions and 110 deletions
110
src/client.rs
Normal file
110
src/client.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use std::{io::{Read, Write}, net::{TcpStream, ToSocketAddrs}};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum PacketType {
|
||||
Login = 3,
|
||||
Command = 2,
|
||||
Response = 0
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
fn to_le_bytes(&self) -> [u8; 4] {
|
||||
(*self as i32).to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for PacketType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Self::Login as i32 => Ok(Self::Login),
|
||||
x if x == Self::Command as i32 => Ok(Self::Command),
|
||||
x if x == Self::Response as i32 => Ok(Self::Response),
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RconClient {
|
||||
stream: TcpStream,
|
||||
next_id: i32
|
||||
}
|
||||
|
||||
impl RconClient {
|
||||
fn read_i32_le(&mut self) -> i32 {
|
||||
let mut buf = [0u8; 4];
|
||||
self.stream.read_exact(&mut buf).unwrap();
|
||||
i32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
fn recv_packet(&mut self) -> (i32, PacketType, String) {
|
||||
let length = self.read_i32_le();
|
||||
if length < 10 {
|
||||
panic!("response length < 10");
|
||||
}
|
||||
|
||||
let id = self.read_i32_le();
|
||||
let ptype = PacketType::try_from(self.read_i32_le()).unwrap();
|
||||
|
||||
let payload_len = (length - 10) as usize;
|
||||
let mut payload_bytes = vec![0u8; payload_len];
|
||||
self.stream.read_exact(&mut payload_bytes).unwrap();
|
||||
|
||||
let mut nulls = [0u8; 2];
|
||||
self.stream.read_exact(&mut nulls).unwrap();
|
||||
|
||||
let payload = String::from_utf8_lossy(&payload_bytes).into_owned();
|
||||
(id, ptype, payload)
|
||||
}
|
||||
|
||||
fn send_packet(&mut self, ptype: PacketType, payload: &str) -> i32 {
|
||||
let id = self.next_id;
|
||||
self.next_id = self.next_id.wrapping_add(1);
|
||||
|
||||
let payload_bytes = payload.as_bytes();
|
||||
let length = (payload_bytes.len() + 10) as i32;
|
||||
|
||||
let mut packet = Vec::with_capacity(4 + length as usize);
|
||||
packet.extend_from_slice(&length.to_le_bytes());
|
||||
packet.extend_from_slice(&id.to_le_bytes());
|
||||
packet.extend_from_slice(&ptype.to_le_bytes());
|
||||
packet.extend_from_slice(&payload_bytes);
|
||||
packet.push(0); // terminate payload
|
||||
packet.push(0); // padding
|
||||
|
||||
self.stream.write_all(&packet).unwrap();
|
||||
id
|
||||
}
|
||||
|
||||
fn authenticate(&mut self, password: &str) {
|
||||
self.send_packet(PacketType::Login, password);
|
||||
|
||||
let (auth_id, _, _) = self.recv_packet();
|
||||
|
||||
if auth_id == -1 {
|
||||
panic!("auth failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(addr: impl ToSocketAddrs, password: &str) -> Self {
|
||||
let stream = TcpStream::connect(addr).unwrap();
|
||||
let mut client = RconClient { stream, next_id: 1 };
|
||||
client.authenticate(password);
|
||||
client
|
||||
}
|
||||
|
||||
pub fn command(&mut self, cmd: &str) -> String {
|
||||
let id = self.send_packet(PacketType::Command, cmd);
|
||||
|
||||
let (resp_id, resp_type, payload) = self.recv_packet();
|
||||
if resp_type != PacketType::Response {
|
||||
panic!("invalid response")
|
||||
}
|
||||
if resp_id != id {
|
||||
panic!("invalid response")
|
||||
}
|
||||
|
||||
payload
|
||||
}
|
||||
}
|
||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod client;
|
||||
pub use client::RconClient;
|
||||
112
src/main.rs
112
src/main.rs
|
|
@ -1,6 +1,7 @@
|
|||
use std::{io::{Read, Write}, net::{TcpStream, ToSocketAddrs}};
|
||||
use clap::Parser;
|
||||
|
||||
use mc_rcon::RconClient;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
|
|
@ -13,115 +14,6 @@ struct Args {
|
|||
cmd: String
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum PacketType {
|
||||
Login = 3,
|
||||
Command = 2,
|
||||
Response = 0
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
fn to_le_bytes(&self) -> [u8; 4] {
|
||||
(*self as i32).to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for PacketType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Self::Login as i32 => Ok(Self::Login),
|
||||
x if x == Self::Command as i32 => Ok(Self::Command),
|
||||
x if x == Self::Response as i32 => Ok(Self::Response),
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RconClient {
|
||||
stream: TcpStream,
|
||||
next_id: i32
|
||||
}
|
||||
|
||||
impl RconClient {
|
||||
fn read_i32_le(&mut self) -> i32 {
|
||||
let mut buf = [0u8; 4];
|
||||
self.stream.read_exact(&mut buf).unwrap();
|
||||
i32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
fn recv_packet(&mut self) -> (i32, PacketType, String) {
|
||||
let length = self.read_i32_le();
|
||||
if length < 10 {
|
||||
panic!("response length < 10");
|
||||
}
|
||||
|
||||
let id = self.read_i32_le();
|
||||
let ptype = PacketType::try_from(self.read_i32_le()).unwrap();
|
||||
|
||||
let payload_len = (length - 10) as usize;
|
||||
let mut payload_bytes = vec![0u8; payload_len];
|
||||
self.stream.read_exact(&mut payload_bytes).unwrap();
|
||||
|
||||
let mut nulls = [0u8; 2];
|
||||
self.stream.read_exact(&mut nulls).unwrap();
|
||||
|
||||
let payload = String::from_utf8_lossy(&payload_bytes).into_owned();
|
||||
(id, ptype, payload)
|
||||
}
|
||||
|
||||
fn send_packet(&mut self, ptype: PacketType, payload: &str) -> i32 {
|
||||
let id = self.next_id;
|
||||
self.next_id = self.next_id.wrapping_add(1);
|
||||
|
||||
let payload_bytes = payload.as_bytes();
|
||||
let length = (payload_bytes.len() + 10) as i32;
|
||||
|
||||
let mut packet = Vec::with_capacity(4 + length as usize);
|
||||
packet.extend_from_slice(&length.to_le_bytes());
|
||||
packet.extend_from_slice(&id.to_le_bytes());
|
||||
packet.extend_from_slice(&ptype.to_le_bytes());
|
||||
packet.extend_from_slice(&payload_bytes);
|
||||
packet.push(0); // terminate payload
|
||||
packet.push(0); // padding
|
||||
|
||||
self.stream.write_all(&packet).unwrap();
|
||||
id
|
||||
}
|
||||
|
||||
fn authenticate(&mut self, password: &str) {
|
||||
self.send_packet(PacketType::Login, password);
|
||||
|
||||
let (auth_id, _, _) = self.recv_packet();
|
||||
|
||||
if auth_id == -1 {
|
||||
panic!("auth failed");
|
||||
}
|
||||
}
|
||||
|
||||
fn connect(addr: impl ToSocketAddrs, password: &str) -> Self {
|
||||
let stream = TcpStream::connect(addr).unwrap();
|
||||
let mut client = RconClient { stream, next_id: 1 };
|
||||
client.authenticate(password);
|
||||
client
|
||||
}
|
||||
|
||||
fn command(&mut self, cmd: &str) -> String {
|
||||
let id = self.send_packet(PacketType::Command, cmd);
|
||||
|
||||
let (resp_id, resp_type, payload) = self.recv_packet();
|
||||
if resp_type != PacketType::Response {
|
||||
panic!("invalid response")
|
||||
}
|
||||
if resp_id != id {
|
||||
panic!("invalid response")
|
||||
}
|
||||
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue