commit a91b0cb788c98573b6b03cf7ad3cfeb1eb6f2db3 Author: MedzikUser Date: Sat Jun 4 13:58:21 2022 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..593e131 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,262 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "servers" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "libloading", + "log", + "tokio", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f392c8f16bda3456c0b00c6de39cb100449b98de55ac41c6cdd2bfcf53a1245" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7d3d0c8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "servers" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.57" +async-trait = "0.1.56" +libloading = "0.7.3" +log = "0.4.17" +tokio = { version = "1.19.0", features = ["rt-multi-thread", "macros", "net", "io-std", "io-util"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6ea179 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Medzik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..07bea65 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,34 @@ +#![allow(clippy::unused_io_amount)] + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; + +pub struct Client { + pub stream: TcpStream, +} + +impl Client { + /// Create new Client + pub fn new(stream: TcpStream) -> Self { + Self { stream } + } + + /// Read message/buffer from Client + pub async fn read(&mut self) -> anyhow::Result { + let mut buf = [0; 1024]; + + self.stream.read(&mut buf).await?; + + let encoded = String::from_utf8(buf.to_vec())?.replace('\0', ""); + + Ok(encoded) + } + + /// Send message to Client + pub async fn send(&mut self, content: &str) -> anyhow::Result<()> { + self.stream.write_all(format!("{content}\n\r").as_bytes()).await?; + Ok(()) + } +} diff --git a/src/command_handler.rs b/src/command_handler.rs new file mode 100644 index 0000000..a3c32ad --- /dev/null +++ b/src/command_handler.rs @@ -0,0 +1,57 @@ +use std::{any::Any, sync::Arc}; + +use async_trait::async_trait; + +use crate::{Client, commands}; + +#[async_trait] +pub trait Command: Any + Send + Sync { + /// Command name + fn name(&self) -> &'static str; + /// Command help message + fn help(&self) -> &'static str; + /// Command function + async fn execute( + &self, + client: &mut Client, + args: Vec<&str>, + plugins: &CommandManagerType, + ); +} + +pub struct CommandManager { + /// Vector with plugins + pub commands: Vec>, +} + +impl CommandManager { + pub fn new() -> Self { + Self { + commands: Vec::new(), + } + } +} + +impl Default for CommandManager { + fn default() -> Self { + Self::new() + } +} + +pub type CommandManagerType = Vec>>; + +pub fn register_commands() -> CommandManagerType { + let mut command_manager = CommandManager::new(); + + for command in commands::register_commands() { + command_manager.commands.push(command) + } + + // create Arc in Vector + let mut commands: CommandManagerType = Vec::new(); + for command in command_manager.commands { + commands.push(Arc::new(command)) + } + + commands +} diff --git a/src/commands/help.rs b/src/commands/help.rs new file mode 100644 index 0000000..c60c3dc --- /dev/null +++ b/src/commands/help.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; + +use crate::{command_handler::Command, CommandManagerType}; + +pub struct CommandHelp; + +#[async_trait] +impl Command for CommandHelp { + fn name(&self) -> &'static str { + "/help" + } + + fn help(&self) -> &'static str { + "show help" + } + + async fn execute( + &self, + client: &mut crate::Client, + _args: Vec<&str>, + commands: &CommandManagerType, + ) { + for command in commands { + client + .send(&format!("{} - {}", command.name(), command.help())) + .await + .expect("send message"); + } + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..483d1af --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,13 @@ +mod help; + +pub use help::*; + +use crate::command_handler::Command; + +pub fn register_commands() -> Vec> { + let mut commands: Vec> = Vec::new(); + + commands.push(Box::new(CommandHelp)); + + commands +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..711a3bc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +mod client; +mod command_handler; +mod commands; +mod plugin_loader; + +pub use client::*; +pub use command_handler::*; +pub use plugin_loader::*; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..11335b5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,64 @@ +use servers::{loader, Client, PluginManagerType, register_commands, CommandManagerType}; +use tokio::{io::AsyncWriteExt, net::TcpListener}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // listen Tcp server + let listener = TcpListener::bind("0.0.0.0:9999").await?; + + // load plugins + let plugin_manager = loader()?; + + // load command + let commands_manager = register_commands(); + + // Accepts a new incoming connection from this listener. + while let Ok((stream, _address)) = listener.accept().await { + let client = Client::new(stream); + + // handle client connection in new thread + tokio::spawn(handle_connection(client, (*plugin_manager).to_vec(), (*commands_manager).to_vec())); + } + + Ok(()) +} + +async fn handle_connection(mut client: Client, plugins: PluginManagerType, commands: CommandManagerType) -> anyhow::Result<()> { + println!("New Client: {:?}", client.stream.peer_addr()?); + + loop { + // read client message/buffer + let buf = client.read().await?; + + // split message by whitespace + let args: &Vec<&str> = &buf.split_ascii_whitespace().collect(); + + // get command from args + let cmd = args[0]; + + println!("{:?}", &args); + + for command in &commands { + if cmd == command.name() { + println!("s"); + command.execute(&mut client, args[1..args.len()].to_vec(), &commands).await; + break + } + } + + // search command in plugins + for plugin in &plugins { + // if command found execute plugin + if cmd == plugin.command() { + plugin.execute(&mut client, args[1..args.len()].to_vec()) + } + } + + // if an I/O or EOF error, abort the connection + if client.stream.flush().await.is_err() { + break; + } + } + + Ok(()) +} diff --git a/src/plugin_loader.rs b/src/plugin_loader.rs new file mode 100644 index 0000000..104ad80 --- /dev/null +++ b/src/plugin_loader.rs @@ -0,0 +1,105 @@ +use std::{any::Any, fs, sync::Arc}; + +use libloading::{Library, Symbol}; +use log::{debug, trace}; + +use crate::Client; + +/// A plugin which allows you to add extra functionality. +pub trait Plugin: Any + Send + Sync { + /// Get a name describing the `Plugin`. + fn name(&self) -> &'static str; + /// A function that runs immediately after plugin loading. + /// Usually used for initialization. + fn on_plugin_load(&self); + /// A function that runs immediately before the plugin is unloaded. + /// Use this if you want to do any cleanup. + fn on_plugin_unload(&self); + /// Plugin command. + /// For example: `/command` + fn command(&self) -> &'static str; + /// Help message of this command. + fn help(&self) -> &'static str; + /// The function will be executed, when sending plugin command. + fn execute(&self, client: &mut Client, args: Vec<&str>); +} + +pub struct PluginManager { + /// Vector with loaded plugins + pub plugins: Vec>, +} + +impl PluginManager { + /// Create empty `PluginManager` + pub fn new() -> Self { + Self { + plugins: Vec::new(), + } + } + + /// Unload all plugins and loaded plugin libraries, making sure to fire + /// their `on_plugin_unload()` methods so they can do any necessary cleanup. + pub fn unload(&mut self) { + debug!("Unloading plugins"); + + for plugin in self.plugins.drain(..) { + trace!("Firing on_plugin_unload for {:?}", plugin.name()); + plugin.on_plugin_unload(); + } + } +} + +impl Default for PluginManager { + fn default() -> Self { + Self::new() + } +} + +pub trait PluginRegistrar { + fn register_plugin(&mut self, plugin: Box); +} + +impl PluginRegistrar for PluginManager { + fn register_plugin(&mut self, plugin: Box) { + self.plugins.push(plugin) + } +} + +pub type PluginManagerType = Vec>>; + +pub fn loader() -> anyhow::Result { + // get path to .so lib from command argument + let config_dir = "./plugins"; + let paths = fs::read_dir(config_dir)?; + + // create a plugin manager where all loaded plugins will be located + let mut plugin_manager = PluginManager::new(); + + // for all plugin in directory + for path in paths { + // get library file path + let path = path?.path(); + + // loading library with .so is unsafe + unsafe { + // load library + // Box::new and Box::leak must be there because if it isn't there it throws a segmentation fault + let lib = Box::leak(Box::new(Library::new(path)?)); + + // get `plugin_entry` from library + let func: Symbol ()> = + lib.get(b"plugin_entry")?; + + // execute initial function + func(&mut plugin_manager); + } + } + + // create Arc in Vector + let mut plugins: PluginManagerType = Vec::new(); + for plugin in plugin_manager.plugins { + plugins.push(Arc::new(plugin)) + } + + Ok(plugins) +}