diff --git a/Cargo.lock b/Cargo.lock index 209469f..1708c04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,111 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "num-derive" version = "0.4.1" @@ -28,7 +121,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -40,6 +133,42 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -64,11 +193,29 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", + "clap", "num-derive", "num-traits", "thiserror", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.48" @@ -80,6 +227,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.56" @@ -97,7 +259,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -105,3 +267,40 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 5ea0e64..81b7fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ thiserror = "1.0" anyhow = "1.0" bytes = "1" num-traits = "0.2" -num-derive = "0.4" \ No newline at end of file +num-derive = "0.4" + +[dev-dependencies] +clap = { version = "3", features = ["derive"] } \ No newline at end of file diff --git a/examples/palworld.rs b/examples/palworld.rs index 467018c..213d731 100644 --- a/examples/palworld.rs +++ b/examples/palworld.rs @@ -1,11 +1,33 @@ +//! Palworld client example. +//! +//! # Usage Examples +//! +//! ```bash +//! $ cargo run --example palworld -- --help +//! $ cargo run --example palworld -- -a 127.0.0.1:25575 -p passwrd -c "Info" +//! ``` use std::io::{Read, Write}; use std::net::TcpStream; use std::time::Duration; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use clap::Parser; use rcon::client::RconClient; +#[derive(Debug, Parser)] +#[clap(name = "palworld")] +struct Args { + #[clap(long, short = 'a', default_value = "127.0.0.1:25575")] + remote_address: String, + + #[clap(long, short = 'p', default_value = "password here")] + rcon_password: String, + + #[clap(long, short = 'c', default_value = "Info")] + command: String, +} + #[derive(Debug)] pub struct PalworldClient { stream: TcpStream, @@ -30,14 +52,24 @@ impl RconClient for PalworldClient { } } -fn main() { - let mut client = - PalworldClient::new("127.0.0.1:25575".to_string()).expect("failed to initiate client"); +fn main() -> Result<(), Box> { + let args = Args::parse(); - client.auth("").expect("failed to auth"); + let mut client = PalworldClient::new(args.remote_address)?; + client.auth(args.rcon_password.as_ref())?; - let result = client - .execute_command("Info") - .expect("failed to execute command"); - println!("Result:\n{:?}", result); + // NOTE: Teleport commands is not supported. because these commands often used in case of playing. + if let Some(command) = args.command.split_whitespace().next() { + match command { + "Shutdown" | "DoExit" | "Broadcast" | "KickPlayer" | "BanPlayer" | "ShowPlayers" + | "Info" | "Save" => { + let result = client.execute_command(args.command.as_ref())?; + println!("{}", result); + Ok(()) + } + _ => Err(anyhow!("unsupported command has specified: {}", args.command).into()), + } + } else { + Err(anyhow!("invalid command string: {}", args.command).into()) + } } diff --git a/src/client/mod.rs b/src/client/mod.rs index bffdf9c..33399a2 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,3 +1,4 @@ +/// This module provides SRCDS response types. pub mod response; use anyhow::Result; @@ -8,44 +9,50 @@ use crate::error::ClientError; use crate::packet::packet_type::{RequestPacketType, ResponsePacketType}; use crate::packet::Packet; +/// A trait providing methods for authenticate/execute command to SRCDS. pub trait RconClient { + /// Sends bytes fn send(&mut self, buf: &[u8]) -> Result<(), Box>; + /// Receives bytes fn receive(&mut self, buf: &mut [u8]) -> Result<(), Box>; + /// Authenticate to SRCDS fn auth(&mut self, rcon_password: &str) -> Result> { - let packet = Packet::default() - .identifier(0) - .request_packet_type(RequestPacketType::ServerDataAuth) - .payload(rcon_password.as_bytes().to_vec()); - let packet_bytes = Bytes::try_from(packet)?; - self.send(&packet_bytes)?; + let auth_packet = Bytes::try_from( + Packet::default() + .identifier(0) + .request_packet_type(RequestPacketType::ServerDataAuth) + .payload(rcon_password.as_bytes().to_vec()), + )?; + self.send(&auth_packet)?; self.receive(&mut [])?; // empty response let mut auth_bytes = [0u8; 4096]; self.receive(&mut auth_bytes)?; - let mut auth_buf = BytesMut::from(&auth_bytes[..]); - let auth_response_packet = Packet::try_from(&mut auth_buf)?; - + let auth_response_packet = Packet::try_from(&mut BytesMut::from(&auth_bytes[..]))?; if auth_response_packet.identifier == 0 { Ok(AuthResponse::AuthenticationSucceeded) } else { Err(ClientError::AuthenticationError.into()) } } + /// Execute command to SRCDS. fn execute_command(&mut self, command: &str) -> Result> { - let command_packet = Packet::default() - .identifier(0) - .request_packet_type(RequestPacketType::ServerDataExecCommand) - .payload(command.as_bytes().to_vec()); - let command_packet_bytes = Bytes::try_from(command_packet)?; - self.send(&command_packet_bytes)?; + let command_packet = Bytes::try_from( + Packet::default() + .identifier(0) + .request_packet_type(RequestPacketType::ServerDataExecCommand) + .payload(command.as_bytes().to_vec()), + )?; + self.send(&command_packet)?; - let empty_packet = Packet::default() - .identifier(1) - .response_packet_type(ResponsePacketType::ServerDataResponseValue); - let empty_packet_bytes = Bytes::try_from(empty_packet)?; - self.send(&empty_packet_bytes)?; + let empty_packet = Bytes::try_from( + Packet::default() + .identifier(1) + .response_packet_type(ResponsePacketType::ServerDataResponseValue), + )?; + self.send(&empty_packet)?; // receive loop (for >4096 payloads) let mut buf: Vec = vec![]; diff --git a/src/client/response.rs b/src/client/response.rs index 8706608..7deb698 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,7 @@ +/// Enumerates the possible results for srcds authentication. pub enum AuthResponse { + /// This enumerate describes SERVERDATA_AUTH succeeded AuthenticationSucceeded, + /// This enumerate describes SERVERDATA_AUTH failed AuthenticationFailed, } diff --git a/src/error.rs b/src/error.rs index fa94104..d8552e4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,17 @@ use thiserror::Error; +/// Errors for serialize/deserialize packets. #[derive(Error, Debug)] pub enum PacketError { + /// Malformed packet error (wrong identifier, etc...) #[error("the packet is malformed")] MalformedPacketError, } +/// Errors for client operation(authentication/execute_command). #[derive(Error, Debug)] pub enum ClientError { + /// Authentication error (maybe password is wrong?) #[error("authentication failed")] AuthenticationError, } diff --git a/src/lib.rs b/src/lib.rs index 20d13a0..929e661 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,16 @@ +//! Rust implementation of SRCDS RCON Protocol. +//! +//! Reference: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol +//! +//! Example implementations: +//! - Palworld `Info` command execution example: [palworld.rs](https://github.com/tukeJonny/rcon-rs/blob/master/examples/palworld.rs) +#![warn(missing_docs)] extern crate anyhow; extern crate num_derive; +/// This module provides RCON client traits. pub mod client; + +/// This module provides error types caused by RCON client. pub mod error; mod packet;