From 9dbc613765de8ab7dfa8e1374cf6661dcfd56bc8 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Thu, 20 May 2021 17:05:59 +0000 Subject: refactor(global): move crates around, stricter module isolation --- .github/workflows/rust.yml | 10 +- Cargo.toml | 10 +- crates/whirl/.license_template | 2 + crates/whirl/Cargo.toml | 69 +++++++++ crates/whirl/src/cli.rs | 93 ++++++++++++ crates/whirl/src/lib.rs | 29 ++++ crates/whirl/src/main.rs | 9 ++ crates/whirl/src/subs.rs | 49 ++++++ crates/whirl/src/whirl.rs | 38 +++++ crates/whirl_api/Cargo.toml | 30 ++++ crates/whirl_api/src/lib.rs | 46 ++++++ crates/whirl_api/src/routes/mod.rs | 4 + crates/whirl_api/src/routes/stats/mod.rs | 33 ++++ crates/whirl_api/src/routes/stats/structures.rs | 21 +++ crates/whirl_common/Cargo.toml | 18 +++ crates/whirl_common/src/lib.rs | 16 ++ crates/whirl_common/src/log.rs | 19 +++ crates/whirl_common/src/sort.rs | 6 + crates/whirl_common/src/system.rs | 38 +++++ crates/whirl_config/.license_template | 2 + crates/whirl_config/Cargo.toml | 25 +++ crates/whirl_config/Whirl.default.toml | 24 +++ crates/whirl_config/src/lib.rs | 90 +++++++++++ crates/whirl_config/src/structures.rs | 37 +++++ crates/whirl_db/Cargo.toml | 22 +++ crates/whirl_db/src/lib.rs | 48 ++++++ crates/whirl_db/src/models.rs | 50 ++++++ crates/whirl_db/src/schema.rs | 35 +++++ crates/whirl_prompt/.license_template | 2 + crates/whirl_prompt/Cargo.toml | 25 +++ crates/whirl_prompt/src/builtins/mod.rs | 108 +++++++++++++ crates/whirl_prompt/src/builtins/structures.rs | 34 +++++ crates/whirl_prompt/src/constants.rs | 19 +++ crates/whirl_prompt/src/lib.rs | 150 ++++++++++++++++++ crates/whirl_prompt/src/structure.rs | 10 ++ crates/whirl_server/.license_template | 2 + crates/whirl_server/Cargo.toml | 37 +++++ crates/whirl_server/src/cmd/commands/action.rs | 26 ++++ crates/whirl_server/src/cmd/commands/buddy_list.rs | 48 ++++++ crates/whirl_server/src/cmd/commands/mod.rs | 12 ++ .../src/cmd/commands/property/create.rs | 168 +++++++++++++++++++++ .../whirl_server/src/cmd/commands/property/mod.rs | 5 + .../src/cmd/commands/property/parse.rs | 14 ++ .../whirl_server/src/cmd/commands/redirect_id.rs | 42 ++++++ .../src/cmd/commands/room_id_request.rs | 18 +++ .../src/cmd/commands/subscribe_distance.rs | 24 +++ .../src/cmd/commands/subscribe_room.rs | 30 ++++ crates/whirl_server/src/cmd/commands/teleport.rs | 34 +++++ crates/whirl_server/src/cmd/commands/text.rs | 67 ++++++++ crates/whirl_server/src/cmd/constants.rs | 32 ++++ crates/whirl_server/src/cmd/extendable.rs | 18 +++ crates/whirl_server/src/cmd/mod.rs | 9 ++ crates/whirl_server/src/cmd/set_parser.rs | 39 +++++ crates/whirl_server/src/cmd/structure.rs | 22 +++ crates/whirl_server/src/distributor.rs | 148 ++++++++++++++++++ crates/whirl_server/src/hub.rs | 161 ++++++++++++++++++++ crates/whirl_server/src/interaction/mod.rs | 5 + crates/whirl_server/src/interaction/peer.rs | 49 ++++++ crates/whirl_server/src/interaction/shared.rs | 28 ++++ crates/whirl_server/src/lib.rs | 90 +++++++++++ crates/whirl_server/src/net/constants.rs | 93 ++++++++++++ crates/whirl_server/src/net/converter.rs | 57 +++++++ crates/whirl_server/src/net/mod.rs | 7 + crates/whirl_server/src/net/property_parser.rs | 38 +++++ crates/whirl_server/src/net/structure.rs | 18 +++ crates/whirl_server/src/packet_parser.rs | 38 +++++ crates/whirl_server/src/types.rs | 8 + diesel.toml | 2 +- rustfmt.toml | 2 +- whirl/.license_template | 2 - whirl/Cargo.toml | 72 --------- whirl/src/api/mod.rs | 31 ---- whirl/src/api/routes/mod.rs | 4 - whirl/src/api/routes/stats/mod.rs | 35 ----- whirl/src/api/routes/stats/structures.rs | 21 --- whirl/src/cli.rs | 93 ------------ whirl/src/db/mod.rs | 35 ----- whirl/src/db/models.rs | 50 ------ whirl/src/db/schema.rs | 35 ----- whirl/src/lib.rs | 37 ----- whirl/src/main.rs | 9 -- whirl/src/subs.rs | 50 ------ whirl/src/utils/log.rs | 19 --- whirl/src/utils/mod.rs | 6 - whirl/src/utils/sort.rs | 6 - whirl/src/utils/system.rs | 38 ----- whirl/src/whirl.rs | 36 ----- whirl_config/.license_template | 2 - whirl_config/Cargo.toml | 25 --- whirl_config/Whirl.default.toml | 24 --- whirl_config/src/lib.rs | 90 ----------- whirl_config/src/structures.rs | 37 ----- whirl_prompt/.license_template | 2 - whirl_prompt/Cargo.toml | 25 --- whirl_prompt/src/builtins/mod.rs | 108 ------------- whirl_prompt/src/builtins/structures.rs | 34 ----- whirl_prompt/src/constants.rs | 19 --- whirl_prompt/src/lib.rs | 150 ------------------ whirl_prompt/src/structure.rs | 10 -- whirl_server/.license_template | 2 - whirl_server/Cargo.toml | 37 ----- whirl_server/src/cmd/commands/action.rs | 26 ---- whirl_server/src/cmd/commands/buddy_list.rs | 48 ------ whirl_server/src/cmd/commands/mod.rs | 12 -- whirl_server/src/cmd/commands/property/create.rs | 168 --------------------- whirl_server/src/cmd/commands/property/mod.rs | 5 - whirl_server/src/cmd/commands/property/parse.rs | 14 -- whirl_server/src/cmd/commands/redirect_id.rs | 42 ------ whirl_server/src/cmd/commands/room_id_request.rs | 18 --- .../src/cmd/commands/subscribe_distance.rs | 24 --- whirl_server/src/cmd/commands/subscribe_room.rs | 30 ---- whirl_server/src/cmd/commands/teleport.rs | 34 ----- whirl_server/src/cmd/commands/text.rs | 67 -------- whirl_server/src/cmd/constants.rs | 32 ---- whirl_server/src/cmd/extendable.rs | 18 --- whirl_server/src/cmd/mod.rs | 9 -- whirl_server/src/cmd/set_parser.rs | 39 ----- whirl_server/src/cmd/structure.rs | 22 --- whirl_server/src/distributor.rs | 148 ------------------ whirl_server/src/hub.rs | 161 -------------------- whirl_server/src/interaction/mod.rs | 5 - whirl_server/src/interaction/peer.rs | 49 ------ whirl_server/src/interaction/shared.rs | 28 ---- whirl_server/src/lib.rs | 90 ----------- whirl_server/src/net/constants.rs | 93 ------------ whirl_server/src/net/converter.rs | 57 ------- whirl_server/src/net/mod.rs | 7 - whirl_server/src/net/property_parser.rs | 38 ----- whirl_server/src/net/structure.rs | 18 --- whirl_server/src/packet_parser.rs | 38 ----- whirl_server/src/types.rs | 8 - 131 files changed, 2601 insertions(+), 2503 deletions(-) create mode 100644 crates/whirl/.license_template create mode 100644 crates/whirl/Cargo.toml create mode 100644 crates/whirl/src/cli.rs create mode 100644 crates/whirl/src/lib.rs create mode 100644 crates/whirl/src/main.rs create mode 100644 crates/whirl/src/subs.rs create mode 100644 crates/whirl/src/whirl.rs create mode 100644 crates/whirl_api/Cargo.toml create mode 100644 crates/whirl_api/src/lib.rs create mode 100644 crates/whirl_api/src/routes/mod.rs create mode 100644 crates/whirl_api/src/routes/stats/mod.rs create mode 100644 crates/whirl_api/src/routes/stats/structures.rs create mode 100644 crates/whirl_common/Cargo.toml create mode 100644 crates/whirl_common/src/lib.rs create mode 100644 crates/whirl_common/src/log.rs create mode 100644 crates/whirl_common/src/sort.rs create mode 100644 crates/whirl_common/src/system.rs create mode 100644 crates/whirl_config/.license_template create mode 100644 crates/whirl_config/Cargo.toml create mode 100644 crates/whirl_config/Whirl.default.toml create mode 100644 crates/whirl_config/src/lib.rs create mode 100644 crates/whirl_config/src/structures.rs create mode 100644 crates/whirl_db/Cargo.toml create mode 100644 crates/whirl_db/src/lib.rs create mode 100644 crates/whirl_db/src/models.rs create mode 100644 crates/whirl_db/src/schema.rs create mode 100644 crates/whirl_prompt/.license_template create mode 100644 crates/whirl_prompt/Cargo.toml create mode 100644 crates/whirl_prompt/src/builtins/mod.rs create mode 100644 crates/whirl_prompt/src/builtins/structures.rs create mode 100644 crates/whirl_prompt/src/constants.rs create mode 100644 crates/whirl_prompt/src/lib.rs create mode 100644 crates/whirl_prompt/src/structure.rs create mode 100644 crates/whirl_server/.license_template create mode 100644 crates/whirl_server/Cargo.toml create mode 100644 crates/whirl_server/src/cmd/commands/action.rs create mode 100644 crates/whirl_server/src/cmd/commands/buddy_list.rs create mode 100644 crates/whirl_server/src/cmd/commands/mod.rs create mode 100644 crates/whirl_server/src/cmd/commands/property/create.rs create mode 100644 crates/whirl_server/src/cmd/commands/property/mod.rs create mode 100644 crates/whirl_server/src/cmd/commands/property/parse.rs create mode 100644 crates/whirl_server/src/cmd/commands/redirect_id.rs create mode 100644 crates/whirl_server/src/cmd/commands/room_id_request.rs create mode 100644 crates/whirl_server/src/cmd/commands/subscribe_distance.rs create mode 100644 crates/whirl_server/src/cmd/commands/subscribe_room.rs create mode 100644 crates/whirl_server/src/cmd/commands/teleport.rs create mode 100644 crates/whirl_server/src/cmd/commands/text.rs create mode 100644 crates/whirl_server/src/cmd/constants.rs create mode 100644 crates/whirl_server/src/cmd/extendable.rs create mode 100644 crates/whirl_server/src/cmd/mod.rs create mode 100644 crates/whirl_server/src/cmd/set_parser.rs create mode 100644 crates/whirl_server/src/cmd/structure.rs create mode 100644 crates/whirl_server/src/distributor.rs create mode 100644 crates/whirl_server/src/hub.rs create mode 100644 crates/whirl_server/src/interaction/mod.rs create mode 100644 crates/whirl_server/src/interaction/peer.rs create mode 100644 crates/whirl_server/src/interaction/shared.rs create mode 100644 crates/whirl_server/src/lib.rs create mode 100644 crates/whirl_server/src/net/constants.rs create mode 100644 crates/whirl_server/src/net/converter.rs create mode 100644 crates/whirl_server/src/net/mod.rs create mode 100644 crates/whirl_server/src/net/property_parser.rs create mode 100644 crates/whirl_server/src/net/structure.rs create mode 100644 crates/whirl_server/src/packet_parser.rs create mode 100644 crates/whirl_server/src/types.rs delete mode 100644 whirl/.license_template delete mode 100644 whirl/Cargo.toml delete mode 100644 whirl/src/api/mod.rs delete mode 100644 whirl/src/api/routes/mod.rs delete mode 100644 whirl/src/api/routes/stats/mod.rs delete mode 100644 whirl/src/api/routes/stats/structures.rs delete mode 100644 whirl/src/cli.rs delete mode 100644 whirl/src/db/mod.rs delete mode 100644 whirl/src/db/models.rs delete mode 100644 whirl/src/db/schema.rs delete mode 100644 whirl/src/lib.rs delete mode 100644 whirl/src/main.rs delete mode 100644 whirl/src/subs.rs delete mode 100644 whirl/src/utils/log.rs delete mode 100644 whirl/src/utils/mod.rs delete mode 100644 whirl/src/utils/sort.rs delete mode 100644 whirl/src/utils/system.rs delete mode 100644 whirl/src/whirl.rs delete mode 100644 whirl_config/.license_template delete mode 100644 whirl_config/Cargo.toml delete mode 100644 whirl_config/Whirl.default.toml delete mode 100644 whirl_config/src/lib.rs delete mode 100644 whirl_config/src/structures.rs delete mode 100644 whirl_prompt/.license_template delete mode 100644 whirl_prompt/Cargo.toml delete mode 100644 whirl_prompt/src/builtins/mod.rs delete mode 100644 whirl_prompt/src/builtins/structures.rs delete mode 100644 whirl_prompt/src/constants.rs delete mode 100644 whirl_prompt/src/lib.rs delete mode 100644 whirl_prompt/src/structure.rs delete mode 100644 whirl_server/.license_template delete mode 100644 whirl_server/Cargo.toml delete mode 100644 whirl_server/src/cmd/commands/action.rs delete mode 100644 whirl_server/src/cmd/commands/buddy_list.rs delete mode 100644 whirl_server/src/cmd/commands/mod.rs delete mode 100644 whirl_server/src/cmd/commands/property/create.rs delete mode 100644 whirl_server/src/cmd/commands/property/mod.rs delete mode 100644 whirl_server/src/cmd/commands/property/parse.rs delete mode 100644 whirl_server/src/cmd/commands/redirect_id.rs delete mode 100644 whirl_server/src/cmd/commands/room_id_request.rs delete mode 100644 whirl_server/src/cmd/commands/subscribe_distance.rs delete mode 100644 whirl_server/src/cmd/commands/subscribe_room.rs delete mode 100644 whirl_server/src/cmd/commands/teleport.rs delete mode 100644 whirl_server/src/cmd/commands/text.rs delete mode 100644 whirl_server/src/cmd/constants.rs delete mode 100644 whirl_server/src/cmd/extendable.rs delete mode 100644 whirl_server/src/cmd/mod.rs delete mode 100644 whirl_server/src/cmd/set_parser.rs delete mode 100644 whirl_server/src/cmd/structure.rs delete mode 100644 whirl_server/src/distributor.rs delete mode 100644 whirl_server/src/hub.rs delete mode 100644 whirl_server/src/interaction/mod.rs delete mode 100644 whirl_server/src/interaction/peer.rs delete mode 100644 whirl_server/src/interaction/shared.rs delete mode 100644 whirl_server/src/lib.rs delete mode 100644 whirl_server/src/net/constants.rs delete mode 100644 whirl_server/src/net/converter.rs delete mode 100644 whirl_server/src/net/mod.rs delete mode 100644 whirl_server/src/net/property_parser.rs delete mode 100644 whirl_server/src/net/structure.rs delete mode 100644 whirl_server/src/packet_parser.rs delete mode 100644 whirl_server/src/types.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dcfba87..5ada3b2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,16 +3,10 @@ name: Rust ✅ on: push: paths: - - "whirl/**" - - "whirl_config/**" - - "whirl_prompt/**" - - "whirl_server/**" + - "crates/**" pull_request: paths: - - "whirl/**" - - "whirl_config/**" - - "whirl_prompt/**" - - "whirl_server/**" + - "crates/**" env: CARGO_TERM_COLOR: always diff --git a/Cargo.toml b/Cargo.toml index b77f983..744e523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["whirl_config", "whirl", "whirl_prompt", "whirl_server"] +members = [ + "crates/whirl", + "crates/whirl_api", + "crates/whirl_common", + "crates/whirl_config", + "crates/whirl_db", + "crates/whirl_prompt", + "crates/whirl_server" +] [profile.release] lto = "fat" diff --git a/crates/whirl/.license_template b/crates/whirl/.license_template new file mode 100644 index 0000000..1fda769 --- /dev/null +++ b/crates/whirl/.license_template @@ -0,0 +1,2 @@ +// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only diff --git a/crates/whirl/Cargo.toml b/crates/whirl/Cargo.toml new file mode 100644 index 0000000..730309b --- /dev/null +++ b/crates/whirl/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "whirl" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Environment +dotenv = "0.15.0" + +# Logging +log = "0.4.14" +flexi_logger = "0.17.1" +human-panic = "1.0.3" +# simple-error = "0.2.3" + +# Utility +rand = "0.8.3" +async-trait = "0.1.50" +sysinfo = "0.17.5" +whirl_common = { path = "../whirl_common" } + +# Byte Manipulation +bytes = "1.0.1" +byteorder = "1.4.3" + +# Serialization +serde = "1.0.126" +serde_derive = "1.0.126" + +# CLI +structopt = "0.3.21" + +# Config +whirl_config = { path = "../whirl_config" } + +# TCP +tokio = { version = "1.6.0", features = ["full"] } +tokio-util = { version = "0.6.7", features = ["codec"] } +tokio-stream = "0.1.6" + +# Web-server +actix-web = { version = "3.3.2", features = ["rustls"] } +actix-cors = "0.5.4" +whirl_api = { path = "../whirl_api" } + +# Prompt +whirl_prompt = { path = "../whirl_prompt" } + +# Server +whirl_server = { path = "../whirl_server" } + +# Allocator +[target.'cfg(windows)'.dependencies] +mimalloc = { version = "0.1.25", default-features = false } + +[target.'cfg(unix)'.dependencies] +jemallocator = "0.3.2" diff --git a/crates/whirl/src/cli.rs b/crates/whirl/src/cli.rs new file mode 100644 index 0000000..82d18c1 --- /dev/null +++ b/crates/whirl/src/cli.rs @@ -0,0 +1,93 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use structopt::clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; +use whirl_config::Config; + +use crate::subs::run; + +pub struct Cli; +impl Cli { + pub fn setup() -> ArgMatches<'static> { + let matches = Self::cli().get_matches(); + + std::env::set_var("DATABASE_URL", "whirl.sqlite3"); + + matches + } + + pub async fn execute(matches: ArgMatches<'_>) -> Result<(), Box> { + if Config::get().whirlsplash.log.test { + error!("error"); + warn!("warn"); + info!("info"); + debug!("debug"); + trace!("trace"); + } + + if matches.is_present("run") { + run().await; + } else if let Some(cmd) = matches.subcommand_matches("config") { + if cmd.is_present("show") { + println!("{:#?}", Config::get()); + } + } else if let Some(shell) = matches.subcommand_matches("completions") { + if shell.is_present("powershell") { + Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::PowerShell, "."); + } else if shell.is_present("bash") { + Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Bash, "."); + } else if shell.is_present("elvish") { + Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Elvish, "."); + } else if shell.is_present("zsh") { + Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Zsh, "."); + } else if shell.is_present("fish") { + Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Fish, "."); + } + debug!("generated shell completions"); + } else if matches.is_present("clean") { + let cleanable_directories = vec!["./log/"]; + for dir in cleanable_directories { + let mut file_type = "directory"; + if !dir.ends_with('/') { + file_type = "file"; + } + println!("cleaning {}: {}", file_type, dir); + if let Err(e) = std::fs::remove_dir_all(dir) { + warn!("cannot delete {}: {}: {}", file_type, dir, e); + } + } + } + + Ok(()) + } + + fn cli() -> App<'static, 'static> { + App::new(env!("CARGO_PKG_NAME")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .settings(&[AppSettings::SubcommandRequiredElseHelp]) + .subcommands(vec![ + SubCommand::with_name("run").about("Start the WorldServer"), + SubCommand::with_name("config") + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommands(vec![SubCommand::with_name("show")]), + SubCommand::with_name("completions") + .setting(AppSettings::SubcommandRequiredElseHelp) + .about("Generate shell completions") + .subcommands(vec![ + SubCommand::with_name("powershell"), + SubCommand::with_name("bash"), + SubCommand::with_name("elvish"), + SubCommand::with_name("zsh"), + SubCommand::with_name("fish"), + ]), + SubCommand::with_name("clean") + .about("Delete Whirl generated files/ directories which are NOT critical. E.g., logs/"), + ]) + .args(&[ + Arg::with_name("debug").short("d").long("debug"), + Arg::with_name("trace").short("t").long("trace"), + ]) + } +} diff --git a/crates/whirl/src/lib.rs b/crates/whirl/src/lib.rs new file mode 100644 index 0000000..9ef177c --- /dev/null +++ b/crates/whirl/src/lib.rs @@ -0,0 +1,29 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +#[macro_use] +extern crate log; +// #[macro_use] +// extern crate simple_error; + +#[cfg(windows)] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +#[cfg(unix)] +#[global_allocator] +static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; + +pub mod cli; +pub mod subs; +pub mod whirl; diff --git a/crates/whirl/src/main.rs b/crates/whirl/src/main.rs new file mode 100644 index 0000000..074ca69 --- /dev/null +++ b/crates/whirl/src/main.rs @@ -0,0 +1,9 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::error::Error; + +use whirl::whirl::Whirl; + +#[tokio::main] +async fn main() -> Result<(), Box> { Whirl::splash().await } diff --git a/crates/whirl/src/subs.rs b/crates/whirl/src/subs.rs new file mode 100644 index 0000000..08da4d6 --- /dev/null +++ b/crates/whirl/src/subs.rs @@ -0,0 +1,49 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use whirl_api::Api; +use whirl_config::Config; +use whirl_prompt::Prompt; +use whirl_server::{ + distributor::Distributor, + hub::Hub, + Server, + ServerType::{AutoServer, RoomServer}, +}; + +pub async fn run() { + let (tx, _rx) = std::sync::mpsc::channel(); + + let _threads = vec![ + tokio::spawn(async move { + let _ = Distributor::listen( + &*format!("0.0.0.0:{}", Config::get().distributor.port), + AutoServer, + ) + .await; + }), + tokio::spawn(async move { + let _ = Hub::listen(&*format!("0.0.0.0:{}", Config::get().hub.port), RoomServer).await; + }), + tokio::spawn(async move { + let _ = Api::listen( + tx, + &*format!("0.0.0.0:{}", Config::get().whirlsplash.api.port), + ) + .await; + }), + ]; + + if std::env::var("DISABLE_PROMPT").unwrap_or_else(|_| "false".to_string()) == "true" + || !Config::get().whirlsplash.prompt.enable + { + info!("starting with prompt disabled"); + loop { + std::thread::sleep(std::time::Duration::default()); + } + } else { + Prompt::handle().await; + } + + // actix_web::rt::System::new("").block_on(rx.recv().unwrap().stop(true)); +} diff --git a/crates/whirl/src/whirl.rs b/crates/whirl/src/whirl.rs new file mode 100644 index 0000000..451d79f --- /dev/null +++ b/crates/whirl/src/whirl.rs @@ -0,0 +1,38 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::error::Error; + +use whirl_common::log::calculate_log_level; + +use crate::cli::Cli; + +pub struct Whirl; +impl Whirl { + pub async fn splash() -> Result<(), Box> { + // Environment and CLI + let matches = Cli::setup(); + + // Logging + dotenv::dotenv().ok(); + human_panic::setup_panic!(); + let logger = flexi_logger::Logger::with_str(calculate_log_level()); + if std::env::var("LOG_FILE").unwrap_or_else(|_| "true".to_string()) == "false" + || !whirl_config::Config::get().whirlsplash.log.file + || std::env::args().collect::>()[1] == "clean" + // Cheeky as all hell. + { + logger.start()?; + } else { + logger + .print_message() + .log_to_file() + .directory("log") + .start()?; + } + + Cli::execute(matches).await.unwrap(); + + Ok(()) + } +} diff --git a/crates/whirl_api/Cargo.toml b/crates/whirl_api/Cargo.toml new file mode 100644 index 0000000..7687b42 --- /dev/null +++ b/crates/whirl_api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "whirl_api" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Web-server +actix-web = { version = "3.3.2", features = ["rustls"] } +actix-cors = "0.5.4" + +# Utility +sysinfo = "0.17.5" +whirl_common = { path = "../whirl_common" } + +# Serialization +serde = "1.0.126" +serde_derive = "1.0.126" + +# Logging +log = "0.4.14" diff --git a/crates/whirl_api/src/lib.rs b/crates/whirl_api/src/lib.rs new file mode 100644 index 0000000..e77e7d3 --- /dev/null +++ b/crates/whirl_api/src/lib.rs @@ -0,0 +1,46 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; + +use actix_web::web::resource; + +mod routes; + +pub struct Api; +impl Api { + pub async fn listen( + tx: std::sync::mpsc::Sender, + address: &str, + ) -> std::io::Result<()> { + let mut sys = actix_web::rt::System::new("api"); + + let server = actix_web::HttpServer::new(|| { + actix_web::App::new() + .wrap(actix_cors::Cors::default().allow_any_origin()) + .service(resource("/").to(|| async { "Whirlsplash" })) + .service(resource("/api/v1/statistics").to(routes::stats::statistics)) + }) + .bind(address)? + .run(); + + info!("http api now listening at {}", address); + + let _ = tx.send(server.clone()); + + sys.block_on(server) + } +} diff --git a/crates/whirl_api/src/routes/mod.rs b/crates/whirl_api/src/routes/mod.rs new file mode 100644 index 0000000..f5a2ff4 --- /dev/null +++ b/crates/whirl_api/src/routes/mod.rs @@ -0,0 +1,4 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod stats; diff --git a/crates/whirl_api/src/routes/stats/mod.rs b/crates/whirl_api/src/routes/stats/mod.rs new file mode 100644 index 0000000..d5d0937 --- /dev/null +++ b/crates/whirl_api/src/routes/stats/mod.rs @@ -0,0 +1,33 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod structures; + +use actix_web::HttpResponse; +use sysinfo::{get_current_pid, ProcessExt, System, SystemExt}; +use whirl_common::system::seconds_to_hrtime; + +use crate::routes::stats::structures::{Statistics, StatisticsProcess, StatisticsSystem}; + +// This is mostly for developmental testing, it consumes more CPU than it's +// worth. +pub fn statistics() -> HttpResponse { + let mut sys = System::new_all(); + sys.refresh_all(); + + let process = sys.get_process(get_current_pid().unwrap()).unwrap(); + + HttpResponse::Ok().json(Statistics { + system: StatisticsSystem { + os_type: sys.get_name().unwrap(), + release: sys.get_kernel_version().unwrap(), + uptime: seconds_to_hrtime(sysinfo::System::new().get_uptime() as usize), + }, + process: StatisticsProcess { + // (process.cpu_usage() * 100.0).round() / 100.0 + memory_usage: (process.memory() / 1000).to_string(), + cpu_usage: (process.cpu_usage() / sys.get_processors().len() as f32).to_string(), + // uptime: seconds_to_hrtime((sys.get_uptime() - process.start_time()) as usize), + }, + }) +} diff --git a/crates/whirl_api/src/routes/stats/structures.rs b/crates/whirl_api/src/routes/stats/structures.rs new file mode 100644 index 0000000..88fc852 --- /dev/null +++ b/crates/whirl_api/src/routes/stats/structures.rs @@ -0,0 +1,21 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#[derive(Serialize)] +pub struct Statistics { + pub system: StatisticsSystem, + pub process: StatisticsProcess, +} +#[derive(Serialize)] +pub struct StatisticsSystem { + #[serde(rename = "type")] + pub os_type: String, + pub release: String, + pub uptime: String, +} +#[derive(Serialize)] +pub struct StatisticsProcess { + pub memory_usage: String, + pub cpu_usage: String, + // pub uptime: String, +} diff --git a/crates/whirl_common/Cargo.toml b/crates/whirl_common/Cargo.toml new file mode 100644 index 0000000..aea1c8f --- /dev/null +++ b/crates/whirl_common/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "whirl_common" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Config +whirl_config = { path = "../whirl_config" } diff --git a/crates/whirl_common/src/lib.rs b/crates/whirl_common/src/lib.rs new file mode 100644 index 0000000..05a0e0c --- /dev/null +++ b/crates/whirl_common/src/lib.rs @@ -0,0 +1,16 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +pub mod log; +pub mod sort; +pub mod system; diff --git a/crates/whirl_common/src/log.rs b/crates/whirl_common/src/log.rs new file mode 100644 index 0000000..39ceca0 --- /dev/null +++ b/crates/whirl_common/src/log.rs @@ -0,0 +1,19 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use whirl_config::Config; + +pub fn calculate_log_level() -> String { + let mut level; + + level = match Config::get().whirlsplash.log.level { + 2 => "debug".to_string(), + 3 => "trace".to_string(), + _ => "info".to_string(), + }; + if !Config::get().whirlsplash.log.everything { + level = format!("whirl={}", level); + } + + level +} diff --git a/crates/whirl_common/src/sort.rs b/crates/whirl_common/src/sort.rs new file mode 100644 index 0000000..131fa55 --- /dev/null +++ b/crates/whirl_common/src/sort.rs @@ -0,0 +1,6 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub fn sort_vec_alphabetically(vec: &mut Vec<&str>) { + vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); +} diff --git a/crates/whirl_common/src/system.rs b/crates/whirl_common/src/system.rs new file mode 100644 index 0000000..7a823a0 --- /dev/null +++ b/crates/whirl_common/src/system.rs @@ -0,0 +1,38 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +const WEEK: usize = 60 * 60 * 60 * 60; +const DAY: usize = 60 * 60 * 60; +const HOUR: usize = 60 * 60; +const MIN: usize = 60; + +fn make_parts(t: usize, steps: &[usize], mut accum: Vec) -> Vec { + match steps.split_first() { + None => accum, + Some((s, steps)) => { + accum.push(t / *s); + make_parts(t % *s, steps, accum) + } + } +} + +pub fn seconds_to_hrtime(seconds: usize) -> String { + let word = ["week", "day", "hour", "min", "sec"]; + + make_parts(seconds, &[WEEK, DAY, HOUR, MIN, 1], Vec::new()) + .iter() + .enumerate() + .filter_map(|(i, s)| { + if s > &0 { + if s > &1 { + Some(format!("{} {}s", s, word[i])) + } else { + Some(format!("{} {}", s, word[i])) + } + } else { + None + } + }) + .collect::>() + .join(", ") +} diff --git a/crates/whirl_config/.license_template b/crates/whirl_config/.license_template new file mode 100644 index 0000000..1fda769 --- /dev/null +++ b/crates/whirl_config/.license_template @@ -0,0 +1,2 @@ +// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only diff --git a/crates/whirl_config/Cargo.toml b/crates/whirl_config/Cargo.toml new file mode 100644 index 0000000..64100d1 --- /dev/null +++ b/crates/whirl_config/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "whirl_config" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Config +config = "0.11.0" + +# Serialization +serde = "1.0.126" +serde_derive = "1.0.126" + +# Logging +log = "0.4.14" diff --git a/crates/whirl_config/Whirl.default.toml b/crates/whirl_config/Whirl.default.toml new file mode 100644 index 0000000..aa2d684 --- /dev/null +++ b/crates/whirl_config/Whirl.default.toml @@ -0,0 +1,24 @@ +# See more keys and their definitions at https://whirlsplash.org/docs/whirl/configuration + +[whirlsplash] +worldsmaster_username = "WORLDSMASTER" +ip = "0.0.0.0" +api.port = 8080 + +[whirlsplash.prompt] +enable = false +ps1 = "[WORLDSMASTER@Whirlsplash ~]$" + +[whirlsplash.log] +enable = true +level = 1 +everything = false +test = false +file = true + +[distributor] +worldsmaster_greeting = "Welcome to Whirlsplash!" +port = 6650 + +[hub] +port = 5673 diff --git a/crates/whirl_config/src/lib.rs b/crates/whirl_config/src/lib.rs new file mode 100644 index 0000000..8f312cd --- /dev/null +++ b/crates/whirl_config/src/lib.rs @@ -0,0 +1,90 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate log; + +mod structures; + +use config::{ConfigError, File}; + +use crate::structures::{ + DistributorConfig, + HubConfig, + WhirlsplashApiConfig, + WhirlsplashConfig, + WhirlsplashLogConfig, + WhirlsplashPromptConfig, +}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Config { + pub whirlsplash: WhirlsplashConfig, + pub distributor: DistributorConfig, + pub hub: HubConfig, +} +impl Config { + pub fn refresh() { let _ = config::Config::new().refresh(); } + + fn load() -> Result { + let mut s = config::Config::new(); + + s.merge(File::with_name("./Whirl.toml").required(false))?; + s.try_into() + } + + pub fn get() -> Config { + return if let Err(why) = Self::load() { + error!( + "unable to load configuration file, reverting to default value: {}", + why + ); + Self::default() + } else { + Self::load().unwrap() + }; + } +} +impl Default for Config { + fn default() -> Self { + Config { + whirlsplash: WhirlsplashConfig { + worldsmaster_username: "WORLDSMASTER".to_string(), + ip: "0.0.0.0".to_string(), + api: WhirlsplashApiConfig { + port: 80 + }, + prompt: WhirlsplashPromptConfig { + enable: false, + ps1: "[WORLDSMASTER@Whirlsplash ~]$".to_string(), + }, + log: WhirlsplashLogConfig { + enable: true, + level: 1, + everything: false, + test: false, + file: true, + }, + }, + distributor: DistributorConfig { + worldsmaster_greeting: "Welcome to Whirlsplash!".to_string(), + port: 6650, + }, + hub: HubConfig { + port: 5673 + }, + } + } +} diff --git a/crates/whirl_config/src/structures.rs b/crates/whirl_config/src/structures.rs new file mode 100644 index 0000000..4f1d62e --- /dev/null +++ b/crates/whirl_config/src/structures.rs @@ -0,0 +1,37 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#[derive(Serialize, Deserialize, Debug)] +pub struct WhirlsplashConfig { + pub worldsmaster_username: String, + pub ip: String, + pub api: WhirlsplashApiConfig, + pub prompt: WhirlsplashPromptConfig, + pub log: WhirlsplashLogConfig, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct WhirlsplashApiConfig { + pub port: i64, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct WhirlsplashPromptConfig { + pub enable: bool, + pub ps1: String, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct WhirlsplashLogConfig { + pub enable: bool, + pub level: i64, + pub everything: bool, + pub test: bool, + pub file: bool, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct DistributorConfig { + pub worldsmaster_greeting: String, + pub port: i64, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct HubConfig { + pub port: i64, +} diff --git a/crates/whirl_db/Cargo.toml b/crates/whirl_db/Cargo.toml new file mode 100644 index 0000000..0e9bdf0 --- /dev/null +++ b/crates/whirl_db/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "whirl_db" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Database +libsqlite3-sys = { version = "0.9.1", features = ["bundled"] } +diesel = { version = "1.4.6", features = ["sqlite"] } + +# Environment +dotenv = "0.15.0" diff --git a/crates/whirl_db/src/lib.rs b/crates/whirl_db/src/lib.rs new file mode 100644 index 0000000..35f05ae --- /dev/null +++ b/crates/whirl_db/src/lib.rs @@ -0,0 +1,48 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +#[macro_use] +extern crate diesel; + +pub mod models; +mod schema; + +use diesel::prelude::*; + +// use crate::db::models::*; + +pub fn establish_connection() -> SqliteConnection { + let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "whirl.sqlite3".to_string()); + SqliteConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("error connecting to {}", database_url)) +} + +/// Only works if you have a valid database already setup! +#[cfg(test)] +#[test] +#[ignore] +pub fn show_serials() { + use crate::{models::SerialNumber, schema::serial_numbers::dsl::*}; + + dotenv::dotenv().ok(); + + let results = serial_numbers + .limit(5) + .load::(&establish_connection()) + .expect("error loading serial numbers table"); + + println!("found {} results", results.len()); + for result in results { + println!("{}", result.user_name); + } +} diff --git a/crates/whirl_db/src/models.rs b/crates/whirl_db/src/models.rs new file mode 100644 index 0000000..52304b6 --- /dev/null +++ b/crates/whirl_db/src/models.rs @@ -0,0 +1,50 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +//! Much of the documentation that you will see within this module is quoted +//! from http://dev.worlds.net/private/GammaDocs/WorldServer.html#RoomServer. + +// use crate::db::schema::*; + +// -------------- +// | Queryables | +// -------------- + +#[derive(Queryable, Debug)] +pub struct SerialNumber { + pub serial_number: String, + pub user_name: String, + pub serial_status: i32, +} + +#[derive(Queryable, Debug)] +pub struct UserRegistration { + pub user_name_lower: String, + pub user_name: String, + pub serial_number: String, + pub password: String, + pub client_version: String, + pub account_status: i32, + pub registration_date: String, + pub times_on: i32, + pub total_minutes: i32, + pub user_privileges: i32, +} + +#[derive(Queryable, Debug)] +pub struct UserProperty { + pub user_name: String, + pub property_id: i32, + pub property_flags: i32, + pub property_access: i32, + pub property_string_value: String, + pub property_binary_value: String, +} + +// --------------- +// | Insertables | +// --------------- + +// -------------- +// | Updatables | +// -------------- diff --git a/crates/whirl_db/src/schema.rs b/crates/whirl_db/src/schema.rs new file mode 100644 index 0000000..6d58598 --- /dev/null +++ b/crates/whirl_db/src/schema.rs @@ -0,0 +1,35 @@ +table! { + serial_numbers (user_name) { + serial_number -> Text, + user_name -> Text, + serial_status -> Integer, + } +} + +table! { + user_properties (user_name) { + user_name -> Text, + property_id -> Integer, + property_flags -> Integer, + property_access -> Integer, + property_string_value -> Integer, + property_binary_value -> Nullable, + } +} + +table! { + user_registration (user_name) { + user_name_lower -> Text, + user_name -> Text, + serial_number -> Text, + password -> Text, + client_version -> Text, + account_status -> Integer, + registration_date -> Text, + times_on -> Integer, + total_minutes -> Integer, + user_privileges -> Integer, + } +} + +allow_tables_to_appear_in_same_query!(serial_numbers, user_properties, user_registration,); diff --git a/crates/whirl_prompt/.license_template b/crates/whirl_prompt/.license_template new file mode 100644 index 0000000..1fda769 --- /dev/null +++ b/crates/whirl_prompt/.license_template @@ -0,0 +1,2 @@ +// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only diff --git a/crates/whirl_prompt/Cargo.toml b/crates/whirl_prompt/Cargo.toml new file mode 100644 index 0000000..00abbcf --- /dev/null +++ b/crates/whirl_prompt/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "whirl_prompt" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Utility +sysinfo = "0.17.5" +colour = "0.6.0" + +# Config +whirl_config = { path = "../whirl_config" } + +# Web +curl = "0.4.37" diff --git a/crates/whirl_prompt/src/builtins/mod.rs b/crates/whirl_prompt/src/builtins/mod.rs new file mode 100644 index 0000000..0359443 --- /dev/null +++ b/crates/whirl_prompt/src/builtins/mod.rs @@ -0,0 +1,108 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod structures; + +use std::io::Write; + +use sysinfo::SystemExt; +use whirl_config::Config; + +use crate::constants::{FILES, HELPABLES_BUILTINS, HELPABLES_BUILTIN_CONFIG}; + +pub fn builtin_echo(args: &[String]) -> i32 { + println!("{}", args.join(" ")); + 0 +} + +pub fn builtin_history(history: &[String]) -> i32 { + for (index, cmd) in history.iter().enumerate() { + println!("{} {}", index, cmd.trim()); + } + 0 +} + +pub fn builtin_help() -> i32 { + for help in HELPABLES_BUILTINS.iter() { + println!("{}", help); + } + + 0 +} + +pub fn builtin_ls() -> i32 { + for file in &FILES { + print!("{} ", file); + } + println!(); + + 0 +} + +pub async fn builtin_cat(args: &[String]) -> i32 { + let file; + if let Some(file_name) = args.get(0) { + file = file_name.to_string(); + } else { + return 0; + }; + + match file.as_str() { + "README.rst" => { + let mut easy = curl::easy::Easy::new(); + + easy + .url("https://raw.githubusercontent.com/Whirlsplash/whirl/develop/README.rst") + .unwrap(); + + let mut transfer = easy.transfer(); + transfer + .write_function(|data| { + std::io::stdout().write_all(data).unwrap(); + Ok(data.len()) + }) + .unwrap(); + transfer.perform().unwrap(); + } + "Whirl.toml" => { + colour::red_ln!("NOTE: This is just a wrapper for `config show`."); + println!("{:#?}", Config::get()); + } + _ => println!("/cat: {}: no such file or directory", file), + } + + 0 +} + +pub fn builtin_config(args: &[String]) -> i32 { + match args.get(0) { + Some(sub) => + match sub.as_str() { + "show" => println!("{:#?}", Config::get()), + "help" | "--help" | "-h" => + for help in HELPABLES_BUILTIN_CONFIG.iter() { + println!("{}", help); + }, + "refresh" => Config::refresh(), + _ => println!("invalid arguments provided"), + }, + None => println!("invalid amount arguments provided"), + } + 0 +} + +pub fn builtin_fetch() -> i32 { + // rfetch: https://github.com/Mangeshrex/rfetch + + let mut sys = sysinfo::System::new(); + sys.refresh_processes(); + + println!(" "); + println!(" .-. os {}", env!("CARGO_PKG_NAME")); + println!(" oo| ker {}", env!("CARGO_PKG_VERSION")); + println!(" / '\\ sh /wsh"); + println!(" (\\_;/) up null"); + println!(" "); + + 0 +} diff --git a/crates/whirl_prompt/src/builtins/structures.rs b/crates/whirl_prompt/src/builtins/structures.rs new file mode 100644 index 0000000..4217a38 --- /dev/null +++ b/crates/whirl_prompt/src/builtins/structures.rs @@ -0,0 +1,34 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::FromStr; + +pub enum BuiltIn { + Echo, + History, + Exit, + Null, + Help, + Ls, + Cat, + Config, + Fetch, +} +impl FromStr for BuiltIn { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "echo" => Ok(BuiltIn::Echo), + "history" => Ok(BuiltIn::History), + "exit" => Ok(BuiltIn::Exit), + "null" => Ok(BuiltIn::Null), + "help" => Ok(BuiltIn::Help), + "ls" => Ok(BuiltIn::Ls), + "cat" => Ok(BuiltIn::Cat), + "config" => Ok(BuiltIn::Config), + "fetch" => Ok(BuiltIn::Fetch), + _ => Err(()), + } + } +} diff --git a/crates/whirl_prompt/src/constants.rs b/crates/whirl_prompt/src/constants.rs new file mode 100644 index 0000000..c173a57 --- /dev/null +++ b/crates/whirl_prompt/src/constants.rs @@ -0,0 +1,19 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub const FILES: [&str; 2] = ["README.rst", "Whirl.toml"]; +pub const HELPABLES_BUILTINS: [&str; 8] = [ + "cat - display the contents of a present file", + "config - manipulate the configuration", + "echo - display a line of predefined text", + "exit - end the process", + "fetch - a neofetch like utility loosely based on rfetch", + "help - you are here", + "history - display the command history", + "ls - display the present files", +]; +pub const HELPABLES_BUILTIN_CONFIG: [&str; 3] = [ + "help - you are here", + "refresh - reload the configuration file", + "show - display the current configuration", +]; diff --git a/crates/whirl_prompt/src/lib.rs b/crates/whirl_prompt/src/lib.rs new file mode 100644 index 0000000..32b247f --- /dev/null +++ b/crates/whirl_prompt/src/lib.rs @@ -0,0 +1,150 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +mod builtins; +mod constants; +mod structure; + +use std::{io, io::Write, str::FromStr}; + +use whirl_config::Config; + +use crate::{ + builtins::{ + builtin_cat, + builtin_config, + builtin_echo, + builtin_fetch, + builtin_help, + builtin_history, + builtin_ls, + structures::BuiltIn, + }, + structure::Command, +}; + +pub struct Prompt { + history: Vec, +} +impl Prompt { + pub async fn handle() -> ! { + let mut prompt = Prompt { + history: vec![] + }; + + loop { + Prompt::write_prompt(); + let command = prompt.read_command(); + prompt + .process_command(Prompt::tokenize_command(command)) + .await; + } + } + + fn write_prompt() { + print!("{} ", Config::get().whirlsplash.prompt.ps1); + io::stdout().flush().unwrap(); + } + + fn read_command(&mut self) -> String { + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("failed to read command from stdin"); + + if input.len() <= 2 { + input = "null".to_string(); + } + + input + } + + fn tokenize_command(c: String) -> Command { + let mut command_split: Vec = c.split_whitespace().map(|s| s.to_string()).collect(); + + Command { + keyword: command_split.remove(0), + args: command_split, + } + } + + // TODO: Find a way to make this access itself `history` doesn't have to be + // passed everytime. + async fn process_command(&mut self, c: Command) -> i32 { + let exit_code = match BuiltIn::from_str(&c.keyword) { + Ok(BuiltIn::Echo) => builtin_echo(&c.args), + Ok(BuiltIn::Exit) => std::process::exit(0), + Ok(BuiltIn::History) => builtin_history(&self.history), + Ok(BuiltIn::Null) => 0, + Ok(BuiltIn::Help) => builtin_help(), + Ok(BuiltIn::Ls) => builtin_ls(), + Ok(BuiltIn::Cat) => builtin_cat(&c.args).await, + Ok(BuiltIn::Config) => builtin_config(&c.args), + Ok(BuiltIn::Fetch) => builtin_fetch(), + _ => { + println!("wsh: command not found: {}", &c.keyword); + 1 + } + }; + + if c.keyword != "null" { + self.history.push(c.to_line()); + } + + exit_code + } +} + +#[cfg(test)] +mod tokenize_command { + use crate::Prompt; + + #[test] + #[ignore] + fn empty_command() { assert_eq!("", Prompt::tokenize_command("".to_string()).keyword) } + + #[test] + fn test_keyword() { assert_eq!("test", Prompt::tokenize_command("test".to_string()).keyword) } + + #[test] + fn no_arg() { assert_eq!(0, Prompt::tokenize_command("test".to_string()).args.len()) } + + #[test] + fn one_arg() { + assert_eq!( + 1, + Prompt::tokenize_command("test one".to_string()).args.len() + ) + } + + #[test] + fn multi_arg() { + assert_eq!( + 3, + Prompt::tokenize_command("test one two three".to_string()) + .args + .len() + ) + } + + #[test] + #[ignore] + fn quotes() { + assert_eq!( + 2, + Prompt::tokenize_command("test \"one two\" three".to_string()) + .args + .len() + ) + } +} diff --git a/crates/whirl_prompt/src/structure.rs b/crates/whirl_prompt/src/structure.rs new file mode 100644 index 0000000..4603d37 --- /dev/null +++ b/crates/whirl_prompt/src/structure.rs @@ -0,0 +1,10 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub struct Command { + pub keyword: String, + pub args: Vec, +} +impl Command { + pub fn to_line(&self) -> String { format!("{} {}", self.keyword, self.args.join(" ")) } +} diff --git a/crates/whirl_server/.license_template b/crates/whirl_server/.license_template new file mode 100644 index 0000000..1fda769 --- /dev/null +++ b/crates/whirl_server/.license_template @@ -0,0 +1,2 @@ +// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only diff --git a/crates/whirl_server/Cargo.toml b/crates/whirl_server/Cargo.toml new file mode 100644 index 0000000..be88f2f --- /dev/null +++ b/crates/whirl_server/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "whirl_server" +version = "0.1.0" +authors = ["Fuwn "] +edition = "2018" +description = "Whirl, an open-source WorldServer implementation in Rust." +documentation = "https://whirlsplash.org/docs/" +readme = "../../README.rst" +homepage = "https://whirlsplash.org" +repository = "https://github.com/Whirlsplash/whirl" +license = "GPL-3.0-only" +# license-file = "LICENSE" +keywords = ["rust", "worldserver", "whirl", "whirlsplash"] +publish = false + +[dependencies] +# Logging +log = "0.4.14" + +# Utility +async-trait = "0.1.50" + +# Byte Manipulation +bytes = "1.0.1" +byteorder = "1.4.3" + +# Serialization +serde = "1.0.126" +serde_derive = "1.0.126" + +# TCP +tokio = { version = "1.6.0", features = ["full"] } +tokio-util = { version = "0.6.7", features = ["codec"] } +tokio-stream = "0.1.6" + +# Config +whirl_config = { path = "../whirl_config" } diff --git a/crates/whirl_server/src/cmd/commands/action.rs b/crates/whirl_server/src/cmd/commands/action.rs new file mode 100644 index 0000000..8d1fb0b --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/action.rs @@ -0,0 +1,26 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +// TODO: of2m-ify +// +// Of2m-ifying isn't much of a priority right now as the whole action ordeal +// hasn't been fully dissected yet. Once more is known about the inner working +// of actions, it will be of2m-ified. + +use bytes::{BufMut, BytesMut}; + +pub fn create_action() -> Vec { + let mut command = BytesMut::new(); + + command.put_slice(&[ + 0x01, 0x11, 0x00, 0x05, 0x54, 0x52, 0x41, 0x44, 0x45, 0x07, 0x26, 0x7c, 0x2b, 0x69, 0x6e, 0x76, + 0x3e, + ]); + + // Convert to vector and insert the length + let mut command_as_vec = command.to_vec(); + command_as_vec.insert(0, command.len() as u8 + 1); + + // Return bytes + command_as_vec +} diff --git a/crates/whirl_server/src/cmd/commands/buddy_list.rs b/crates/whirl_server/src/cmd/commands/buddy_list.rs new file mode 100644 index 0000000..931db52 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/buddy_list.rs @@ -0,0 +1,48 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::from_utf8; + +use bytes::{BufMut, BytesMut}; + +use crate::cmd::{ + constants::BUDDYLISTNOTIFY, + extendable::{Creatable, Parsable}, +}; + +#[derive(Clone)] +pub struct BuddyList { + pub buddy: String, + pub add: i8, +} +impl Parsable for BuddyList { + fn parse(data: Vec) -> Self { + Self { + buddy: from_utf8(&data[4..data[0] as usize - 1]) + .unwrap() + .to_string(), + + // Get the last byte + add: data[data[0] as usize - 1] as i8, + } + } +} +impl Creatable for BuddyList { + fn create(self) -> Vec { + let mut command = BytesMut::new(); + + // Header + command.put_u8(0x01); // ObjId + command.put_i8(BUDDYLISTNOTIFY as i8); // Type + + // Content + command.put_u8(self.buddy.len() as u8); // Buddy (name) length + command.put_slice(self.buddy.as_bytes()); // Buddy (name) + command.put_u8(self.add as u8); // "Is buddy logged on?" (?) + + let mut command_as_vec = command.to_vec(); + command_as_vec.insert(0, command.len() as u8 + 1); + + command_as_vec + } +} diff --git a/crates/whirl_server/src/cmd/commands/mod.rs b/crates/whirl_server/src/cmd/commands/mod.rs new file mode 100644 index 0000000..49758c2 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/mod.rs @@ -0,0 +1,12 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod action; +pub mod buddy_list; +pub mod property; +pub mod redirect_id; +pub mod room_id_request; +pub mod subscribe_distance; +pub mod subscribe_room; +pub mod teleport; +pub mod text; diff --git a/crates/whirl_server/src/cmd/commands/property/create.rs b/crates/whirl_server/src/cmd/commands/property/create.rs new file mode 100644 index 0000000..40ec2be --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/property/create.rs @@ -0,0 +1,168 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +// TODO: of2m-ify? + +use whirl_config::Config; + +use crate::{ + cmd::constants::{PROPUPD, SESSINIT}, + net::{ + constants::{ + VAR_APPNAME, + VAR_CHANNEL, + VAR_ERROR, + VAR_EXTERNAL_HTTP_SERVER, + VAR_MAIL_DOMAIN, + VAR_PRIV, + VAR_PROTOCOL, + VAR_SCRIPT_SERVER, + VAR_SERIAL, + VAR_SERVERTYPE, + VAR_SMTP_SERVER, + VAR_UPDATETIME, + }, + converter::property_list_to_bytes, + structure::NetworkProperty, + }, +}; + +pub fn create_property_update_as_distributor() -> Vec { + property_list_to_bytes( + PROPUPD, + 0xFF, + vec![ + NetworkProperty { + prop_id: VAR_MAIL_DOMAIN, + value: "worlds3d.com".to_string(), + }, + NetworkProperty { + prop_id: VAR_SMTP_SERVER, + value: "mail.worlds.net:25".to_string(), + }, + NetworkProperty { + prop_id: VAR_SCRIPT_SERVER, + value: "http://www-dynamic.us.worlds.net/cgi-bin".to_string(), + }, + NetworkProperty { + prop_id: VAR_EXTERNAL_HTTP_SERVER, + value: "http://www-static.us.worlds.net".to_string(), + }, + NetworkProperty { + prop_id: VAR_SERVERTYPE, + value: "1".to_string(), + }, + NetworkProperty { + prop_id: VAR_PROTOCOL, + value: "24".to_string(), + }, + NetworkProperty { + prop_id: VAR_APPNAME, + value: Config::get().whirlsplash.worldsmaster_username, + }, + ], + ) +} + +pub fn create_property_update_as_hub() -> Vec { + property_list_to_bytes( + PROPUPD, + 0xFF, + vec![ + NetworkProperty { + prop_id: VAR_UPDATETIME, + value: "1000000".to_string(), + }, + NetworkProperty { + prop_id: VAR_MAIL_DOMAIN, + value: "worlds3d.com".to_string(), + }, + NetworkProperty { + prop_id: VAR_SMTP_SERVER, + value: "mail.worlds.net:25".to_string(), + }, + NetworkProperty { + prop_id: VAR_SCRIPT_SERVER, + value: "http://www-dynamic.us.worlds.net/cgi-bin".to_string(), + }, + NetworkProperty { + prop_id: VAR_EXTERNAL_HTTP_SERVER, + value: "http://www-static.us.worlds.net".to_string(), + }, + NetworkProperty { + prop_id: VAR_SERVERTYPE, + value: "3".to_string(), + }, + NetworkProperty { + prop_id: VAR_PROTOCOL, + value: "24".to_string(), + }, + NetworkProperty { + prop_id: VAR_APPNAME, + value: Config::get().whirlsplash.worldsmaster_username, + }, + ], + ) +} + +pub fn create_property_request_as_distributor() -> Vec { + property_list_to_bytes( + SESSINIT as i32, + 0x01, + vec![ + NetworkProperty { + prop_id: VAR_ERROR, + value: "0".to_string(), + }, + NetworkProperty { + prop_id: VAR_APPNAME, + value: Config::get().whirlsplash.worldsmaster_username, + }, + NetworkProperty { + prop_id: VAR_PROTOCOL, + value: "24".to_string(), + }, + NetworkProperty { + prop_id: VAR_SERVERTYPE, + value: "1".to_string(), + }, + NetworkProperty { + prop_id: VAR_SERIAL, + value: "DWLV000000000000".to_string(), + }, + NetworkProperty { + prop_id: VAR_PRIV, + value: "0".to_string(), + }, + NetworkProperty { + prop_id: VAR_CHANNEL, + value: "dimension-1".to_string(), + }, + ], + ) +} + +pub fn create_property_request_as_hub() -> Vec { + property_list_to_bytes( + SESSINIT as i32, + 0x01, + vec![ + NetworkProperty { + prop_id: VAR_ERROR, + value: "0".to_string(), + }, + NetworkProperty { + prop_id: VAR_SERVERTYPE, + value: "3".to_string(), + }, + NetworkProperty { + prop_id: VAR_UPDATETIME, + value: "1000000".to_string(), + }, + NetworkProperty { + prop_id: VAR_PROTOCOL, + value: "24".to_string(), + }, + ], + ) +} diff --git a/crates/whirl_server/src/cmd/commands/property/mod.rs b/crates/whirl_server/src/cmd/commands/property/mod.rs new file mode 100644 index 0000000..83b015b --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/property/mod.rs @@ -0,0 +1,5 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod create; +pub mod parse; diff --git a/crates/whirl_server/src/cmd/commands/property/parse.rs b/crates/whirl_server/src/cmd/commands/property/parse.rs new file mode 100644 index 0000000..415d19f --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/property/parse.rs @@ -0,0 +1,14 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use crate::net::structure::NetworkProperty; + +pub fn find_property_in_property_list( + property_list: &[NetworkProperty], + property: i32, +) -> &NetworkProperty { + property_list + .iter() + .find(|i| i.prop_id == property) + .unwrap() +} diff --git a/crates/whirl_server/src/cmd/commands/redirect_id.rs b/crates/whirl_server/src/cmd/commands/redirect_id.rs new file mode 100644 index 0000000..8f56c86 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/redirect_id.rs @@ -0,0 +1,42 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use bytes::{BufMut, BytesMut}; +use whirl_config::Config; + +use crate::cmd::{constants::REDIRID, extendable::Creatable}; + +#[derive(Debug)] +pub struct RedirectId { + pub room_name: String, + pub room_number: i8, +} +impl Creatable for RedirectId { + fn create(self) -> Vec { + let mut command = BytesMut::new(); + + // Header + command.put_u8(0x01); // ObjId + command.put_u8(REDIRID as u8); // Type + + // Content + command.put_u8(self.room_name.len() as u8); // Room name length + command.put_slice(self.room_name.as_bytes()); // Room name + // command.put_u8(0x00); // Unimplemented byte (?) + // command.put_u8(room_id); // Room ID + command.put_u16(self.room_number as u16); // Room ID + + // IP + for byte in Config::get().whirlsplash.ip.split('.') { + command.put_u8(byte.parse::().unwrap()); + } + command.put_u16(Config::get().hub.port as u16); // Port + + // Length + let mut command_as_vec = command.to_vec(); + command_as_vec.insert(0, command.len() as u8 + 1); + + // Return + command_as_vec + } +} diff --git a/crates/whirl_server/src/cmd/commands/room_id_request.rs b/crates/whirl_server/src/cmd/commands/room_id_request.rs new file mode 100644 index 0000000..cf507fa --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/room_id_request.rs @@ -0,0 +1,18 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::from_utf8; + +use crate::cmd::extendable::Parsable; + +#[derive(Debug)] +pub struct RoomIdRequest { + pub room_name: String, +} +impl Parsable for RoomIdRequest { + fn parse(data: Vec) -> Self { + Self { + room_name: from_utf8(&data[4..data[0] as usize]).unwrap().to_string(), + } + } +} diff --git a/crates/whirl_server/src/cmd/commands/subscribe_distance.rs b/crates/whirl_server/src/cmd/commands/subscribe_distance.rs new file mode 100644 index 0000000..d5cbcf6 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/subscribe_distance.rs @@ -0,0 +1,24 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use byteorder::{BigEndian, ReadBytesExt}; +use bytes::{Buf, BytesMut}; + +use crate::cmd::extendable::Parsable; + +#[derive(Debug)] +pub struct SubscribeDistance { + pub distance: i16, + pub room_number: i16, +} +impl Parsable for SubscribeDistance { + fn parse(data: Vec) -> Self { + // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 + let mut data = BytesMut::from(data.as_slice()).reader(); + + Self { + distance: data.read_i16::().unwrap(), + room_number: data.read_i16::().unwrap(), + } + } +} diff --git a/crates/whirl_server/src/cmd/commands/subscribe_room.rs b/crates/whirl_server/src/cmd/commands/subscribe_room.rs new file mode 100644 index 0000000..9e7d732 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/subscribe_room.rs @@ -0,0 +1,30 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use byteorder::{BigEndian, ReadBytesExt}; +use bytes::{Buf, BytesMut}; + +use crate::cmd::extendable::Parsable; + +#[derive(Debug)] +pub struct SubscribeRoom { + pub room_number: i8, + pub x: f32, + pub y: f32, + pub z: f32, + pub distance: f32, +} +impl Parsable for SubscribeRoom { + fn parse(data: Vec) -> Self { + // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 + let mut data = BytesMut::from(data.as_slice()).reader(); + + Self { + room_number: data.read_i16::().unwrap() as i8, + x: data.read_i16::().unwrap() as i8 as f32, + y: data.read_i16::().unwrap() as i8 as f32, + z: data.read_i16::().unwrap() as i8 as f32, + distance: data.read_i16::().unwrap() as i8 as f32, // + 100 + } + } +} diff --git a/crates/whirl_server/src/cmd/commands/teleport.rs b/crates/whirl_server/src/cmd/commands/teleport.rs new file mode 100644 index 0000000..ef8f6b2 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/teleport.rs @@ -0,0 +1,34 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use byteorder::{BigEndian, ReadBytesExt}; +use bytes::{Buf, BytesMut}; + +use crate::cmd::extendable::Parsable; + +#[derive(Debug)] +pub struct Teleport { + pub room_id: i8, + pub exit_type: u8, + pub entry_type: u8, + pub x: f32, // i16 + pub y: f32, + pub z: f32, + pub direction: f32, +} +impl Parsable for Teleport { + fn parse(data: Vec) -> Self { + // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 + let mut data = BytesMut::from(data.as_slice()).reader(); + + Self { + room_id: data.read_u16::().unwrap() as i8, + exit_type: data.read_u8().unwrap(), + entry_type: data.read_u8().unwrap(), + x: data.read_i16::().unwrap() as f32, + y: data.read_i16::().unwrap() as f32, + z: data.read_i16::().unwrap() as f32, + direction: data.read_i16::().unwrap() as f32, + } + } +} diff --git a/crates/whirl_server/src/cmd/commands/text.rs b/crates/whirl_server/src/cmd/commands/text.rs new file mode 100644 index 0000000..2bf7e17 --- /dev/null +++ b/crates/whirl_server/src/cmd/commands/text.rs @@ -0,0 +1,67 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::from_utf8; + +use bytes::{BufMut, BytesMut}; + +use crate::cmd::{ + constants::TEXT, + extendable::{Creatable, ParsableWithArguments}, +}; + +pub struct Text { + pub sender: String, + pub content: String, +} +impl Creatable for Text { + fn create(self) -> Vec { + let mut command = BytesMut::new(); + + // Header + command.put_u8(0x01); + command.put_i8(TEXT as i8); + + // Content + // TODO: Find a way to parse ObjIds. + // + // The below byte is suspected to be the sender's short ObjId. + command.put_i8(0x00); + + command.put_u8(self.sender.len() as u8); + command.put_slice(self.sender.as_bytes()); + command.put_u8(self.content.len() as u8); + command.put_slice(self.content.as_bytes()); + + // Convert to vector and insert the length + let mut command_as_vec = command.to_vec(); + command_as_vec.insert(0, command.len() as u8 + 1); + + // Return bytes + command_as_vec + } +} +impl ParsableWithArguments for Text { + /// The first and only element of `args` *should* be the username of the + /// sender. + /// + /// There isn't anything currently stopping someone from passing some other + /// value so that might be annoying at times. + /// + /// Realistically, this method is mostly static so the username will *always* + /// be passed properly unless someone intentionally commits breaking changes + /// on purpose regarding what is passed to to this method where called. + /// + /// It would be neat to have some sort of ability to statically check if the + /// `args` argument contains x number of elements at compile time or + /// something of the sort but the Rust RFC is probably not focused on that. + /// + /// So, right now, trust is in the developers' hands to make sure to pass the + /// right -- number -- of elements to `args`. + fn parse(data: Vec, args: &[&str]) -> Self { + Self { + sender: args[0].to_string(), + content: from_utf8(&data[6..]).unwrap().to_string(), + } + } +} diff --git a/crates/whirl_server/src/cmd/constants.rs b/crates/whirl_server/src/cmd/constants.rs new file mode 100644 index 0000000..22d29c1 --- /dev/null +++ b/crates/whirl_server/src/cmd/constants.rs @@ -0,0 +1,32 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub const LONGLOC: i32 = 1; +pub const STATE: i32 = 2; +pub const PROP: i32 = 3; +pub const SHORTLOC: i32 = 4; +pub const ROOMCHNG: i32 = 5; +pub const SESSINIT: i32 = 6; +pub const SESSEXIT: i32 = 7; +pub const APPINIT: i32 = 8; +pub const PROPREQ: i32 = 10; +pub const DISAPPR: i32 = 11; +pub const APPRACTR: i32 = 12; +pub const REGOBJID: i32 = 13; +pub const TEXT: i32 = 14; +pub const PROPSET: i32 = 15; +pub const PROPUPD: i32 = 16; +pub const WHISPER: i32 = 17; +pub const TELEPORT: i32 = 18; +pub const ROOMIDRQ: i32 = 20; +pub const ROOMID: i32 = 21; +pub const SUBSCRIB: i32 = 22; +pub const UNSUBSCR: i32 = 23; +pub const SUB_DIST: i32 = 24; // SUB-DIST +pub const REDIRECT: i32 = 25; +pub const REDIRID: i32 = 26; +pub const FINGREQ: i32 = 27; +pub const FINGREP: i32 = 28; +pub const BUDDYLISTUPDATE: i32 = 29; +pub const BUDDYLISTNOTIFY: i32 = 30; +pub const CHANNEL: i32 = 31; diff --git a/crates/whirl_server/src/cmd/extendable.rs b/crates/whirl_server/src/cmd/extendable.rs new file mode 100644 index 0000000..e6f3c2b --- /dev/null +++ b/crates/whirl_server/src/cmd/extendable.rs @@ -0,0 +1,18 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub trait Parsable { + fn parse(data: Vec) -> Self; +} + +pub trait Creatable { + fn create(self) -> Vec; +} + +/// Having to do this makes me with there was operator overloading in Rust. +/// +/// I *could* do this with a macro but since Text is the only struct that +/// implements this trait, it shouldn't be that big of a deal. +pub trait ParsableWithArguments { + fn parse(data: Vec, args: &[&str]) -> Self; +} diff --git a/crates/whirl_server/src/cmd/mod.rs b/crates/whirl_server/src/cmd/mod.rs new file mode 100644 index 0000000..ef91de7 --- /dev/null +++ b/crates/whirl_server/src/cmd/mod.rs @@ -0,0 +1,9 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod commands; + +pub mod constants; +pub mod extendable; +mod set_parser; +mod structure; diff --git a/crates/whirl_server/src/cmd/set_parser.rs b/crates/whirl_server/src/cmd/set_parser.rs new file mode 100644 index 0000000..9d6a4b3 --- /dev/null +++ b/crates/whirl_server/src/cmd/set_parser.rs @@ -0,0 +1,39 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cmd::structure::Command; + +/// Iterate over a command set in the from of bytes and return a list of +/// human-readable commands. +fn _parse_command_set(mut data: Vec) -> Vec { + let mut command_set = vec![]; + + // Iterate over all commands + loop { + // Check if any commands are present + if data.len() <= 2 { + break; + } + if data[0] == 0 { + break; + } + + let command_length = data[0]; + let mut command = Command { + length: command_length as i32, + obj_id: data[1] as i32, + id: data[2] as i32, + body: vec![], + }; + if command.length > 3 { + command.body = data[3..].to_owned(); + } + command_set.push(command); + + // Remove current command from the command set + data = data[command_length as usize..].to_vec(); + } + + // Return the human-readable command set + command_set +} diff --git a/crates/whirl_server/src/cmd/structure.rs b/crates/whirl_server/src/cmd/structure.rs new file mode 100644 index 0000000..23e91ca --- /dev/null +++ b/crates/whirl_server/src/cmd/structure.rs @@ -0,0 +1,22 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub struct Command { + pub length: i32, + pub obj_id: i32, + pub id: i32, + pub body: Vec, +} +impl Command { + pub fn _new() -> Self { Command::default() } +} +impl Default for Command { + fn default() -> Self { + Command { + length: 0, + obj_id: 0, + id: 0, + body: vec![], + } + } +} diff --git a/crates/whirl_server/src/distributor.rs b/crates/whirl_server/src/distributor.rs new file mode 100644 index 0000000..22b698b --- /dev/null +++ b/crates/whirl_server/src/distributor.rs @@ -0,0 +1,148 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +//! The distributor functions as bare-minimal +//! [AutoServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#AutoServer). +//! +//! It intercepts a client and distributes it to a +//! [RoomServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#RoomServer). +//! +//! This is not meant to be a high performant section of code as the distributor +//! is only meant to handle the initial and brief session initialization of the +//! client. + +use std::{error::Error, net::SocketAddr, sync::Arc}; + +use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex}; +use tokio_stream::StreamExt; +use tokio_util::codec::{BytesCodec, Decoder}; +use whirl_config::Config; + +use crate::{ + cmd::{ + commands::{ + action::create_action, + buddy_list::BuddyList, + property::{ + create::{create_property_request_as_distributor, create_property_update_as_distributor}, + parse::find_property_in_property_list, + }, + redirect_id::RedirectId, + room_id_request::RoomIdRequest, + text::Text, + }, + constants::*, + extendable::{Creatable, Parsable}, + }, + interaction::{peer::Peer, shared::Shared}, + net::{constants::VAR_USERNAME, property_parser::parse_network_property}, + packet_parser::parse_commands_from_packet, + Server, +}; + +pub struct Distributor; +#[async_trait] +impl Server for Distributor { + async fn handle( + state: Arc>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box> { + let bytes = BytesCodec::new().framed(stream); + let mut peer = Peer::new(state.clone(), bytes, count.to_string()).await?; + let mut room_ids = vec![]; + let mut username = String::from("unknown"); + + loop { + tokio::select! { + Some(msg) = peer.rx.recv() => { + peer.bytes.get_mut().write_all(&msg).await?; + } + result = peer.bytes.next() => match result { + Some(Ok(msg)) => { + for msg in parse_commands_from_packet(msg) { + match msg.get(2).unwrap().to_owned() as i32 { + PROPREQ => { + debug!("received property request from client"); + + peer.bytes.get_mut() + .write_all(&create_property_update_as_distributor()).await?; + trace!("sent property update to client"); + } + SESSINIT => { + username = (&*find_property_in_property_list( + &parse_network_property(msg[3..].to_vec()), + VAR_USERNAME, + ).value).to_string(); + + debug!("received session initialization from {}", username); + + peer.bytes.get_mut() + .write_all(&create_property_request_as_distributor()).await?; + trace!("sent property request to {}", username); + } + PROPSET => { + debug!("received property set from {}", username); + + peer.bytes.get_mut() + .write_all(&Text { + sender: Config::get().whirlsplash.worldsmaster_username, + content: Config::get().distributor.worldsmaster_greeting, + }.create()).await?; + peer.bytes.get_mut() + .write_all(&create_action()).await?; + trace!("sent text to {}", username); + } + BUDDYLISTUPDATE => { + let buddy = BuddyList::parse(msg.to_vec()); + debug!("received buddy list update from {}: {}", username, buddy.buddy); + peer.bytes.get_mut().write_all(&buddy.clone().create()).await?; + trace!("sent buddy list notify to {}: {}", username, buddy.buddy); + } + ROOMIDRQ => { + let room = RoomIdRequest::parse(msg.to_vec()); + debug!("received room id request from {}: {}", username, &room.room_name); + + let room_id; + if !room_ids.contains(&room.room_name) { + room_ids.push((&*room.room_name).to_string()); + room_id = room_ids.iter().position(|r| r == &room.room_name).unwrap(); + trace!("inserted room: {}", room.room_name); + } else { + let position = room_ids.iter().position(|r| r == &room.room_name).unwrap(); + trace!("found room: {}", room.room_name); + room_id = position; + } + + peer.bytes.get_mut().write_all(&RedirectId { + room_name: (&*room.room_name).to_string(), + room_number: room_id as i8, + }.create()).await?; + trace!("sent redirect id to {}: {}", username, room.room_name); + } + SESSEXIT => { + debug!("received session exit from {}", username); break; + } + _ => (), + } + } + } + Some(Err(e)) => { + error!("error while processing message (s): {}", e); break; + } + None => break, + } + } + } + + // Deregister client + debug!("de-registering client"); + { + state.lock().await.peers.remove(&count.to_string()); + } + debug!("de-registered client"); + + Ok(()) + } +} diff --git a/crates/whirl_server/src/hub.rs b/crates/whirl_server/src/hub.rs new file mode 100644 index 0000000..6de4bca --- /dev/null +++ b/crates/whirl_server/src/hub.rs @@ -0,0 +1,161 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +//! The hub functions as a +//! [RoomServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#AutoServer). +//! +//! The RoomServer is responsible for handling just about every request from the +//! client after they have been redirected to a room (hub). + +use std::{error::Error, net::SocketAddr, sync::Arc}; + +use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex}; +use tokio_stream::StreamExt; +use tokio_util::codec::{BytesCodec, Decoder}; +use whirl_config::Config; + +use crate::{ + cmd::{ + commands::{ + action::create_action, + buddy_list::BuddyList, + property::{ + create::{create_property_request_as_hub, create_property_update_as_hub}, + parse::find_property_in_property_list, + }, + subscribe_distance::SubscribeDistance, + subscribe_room::SubscribeRoom, + teleport::Teleport, + text::Text, + }, + constants::*, + extendable::{Creatable, Parsable, ParsableWithArguments}, + }, + interaction::{peer::Peer, shared::Shared}, + net::{constants::VAR_USERNAME, property_parser::parse_network_property}, + packet_parser::parse_commands_from_packet, + Server, +}; + +pub struct Hub; +#[async_trait] +impl Server for Hub { + async fn handle( + state: Arc>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box> { + let bytes = BytesCodec::new().framed(stream); + let mut peer = Peer::new(state.clone(), bytes, count.to_string()).await?; + // let mut room_ids = vec![]; + let mut username = String::from("unknown"); + + loop { + tokio::select! { + Some(msg) = peer.rx.recv() => { + // trace!("got peer activity: {:?}", &msg); + peer.bytes.get_mut().write_all(&msg).await?; + } + result = peer.bytes.next() => match result { + Some(Ok(msg)) => { + // trace!("got some bytes: {:?}", &msg); + for msg in parse_commands_from_packet(msg) { + match msg.get(2).unwrap().to_owned() as i32 { + PROPREQ => { + debug!("received property request from client"); + + peer.bytes.get_mut() + .write_all(&create_property_update_as_hub()).await?; + trace!("sent property update to client"); + } + SESSINIT => { + username = (&*find_property_in_property_list( + &parse_network_property(msg[3..].to_vec()), + VAR_USERNAME, + ).value).to_string(); + + debug!("received session initialization from {}", username); + + peer.bytes.get_mut() + .write_all(&create_property_request_as_hub()).await?; + trace!("sent property request to {}", username); + } + PROPSET => { + debug!("received property set from {}", username); + + peer.bytes.get_mut() + .write_all(&Text { + sender: Config::get().whirlsplash.worldsmaster_username, + content: Config::get().distributor.worldsmaster_greeting, + }.create()).await?; + peer.bytes.get_mut() + .write_all(&create_action()).await?; + trace!("sent text to {}", username); + } + BUDDYLISTUPDATE => { + let buddy = BuddyList::parse(msg.to_vec()); + debug!("received buddy list update from {}: {}", username, buddy.buddy); + peer.bytes.get_mut().write_all(&buddy.clone().create()).await?; + trace!("sent buddy list notify to {}: {}", username, buddy.buddy); + } + // TODO: Figure out if this is actually even needed. + // ROOMIDRQ => { + // let room = RoomIdRequest::parse(msg.to_vec()); + // debug!("received room id request from {}: {}", username, room.room_name); + // trace!("{:?}", create_room_id_request(&room.room_name, 0x00)); + // } + SESSEXIT => { + debug!("received session exit from {}", username); break; + } + TEXT => { + let text = Text::parse(msg.to_vec(), &[&username]); + debug!("received text from {}:{}", username, text.content); + + { + state.lock().await.broadcast(&Text { + sender: (&*username).to_string(), + content: text.content, + }.create()).await; + } + debug!("broadcasted text to hub"); + } + SUBSCRIB => { + let subscribe_room = SubscribeRoom::parse(msg[3..].to_vec()); + debug!("received subscribe room from {}: {:?}", + username, subscribe_room); + } + SUB_DIST => { + let subscribe_distance = SubscribeDistance::parse(msg[3..].to_vec()); + debug!("received subscribe distance from {}: {:?}", + username, subscribe_distance); + } + TELEPORT => { + let teleport = Teleport::parse(msg[3..].to_vec()); + debug!("received teleport from {}: {:?}", + username, teleport); + } + _ => (), + } + } + } + Some(Err(e)) => { + error!("error while processing message (s): {}", e); break; + } + None => { + trace!("nothing"); break; + }, + } + } + } + + // Deregister client + debug!("de-registering client"); + { + state.lock().await.peers.remove(&count.to_string()); + } + debug!("de-registered client"); + + Ok(()) + } +} diff --git a/crates/whirl_server/src/interaction/mod.rs b/crates/whirl_server/src/interaction/mod.rs new file mode 100644 index 0000000..c85e09d --- /dev/null +++ b/crates/whirl_server/src/interaction/mod.rs @@ -0,0 +1,5 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod peer; +pub mod shared; diff --git a/crates/whirl_server/src/interaction/peer.rs b/crates/whirl_server/src/interaction/peer.rs new file mode 100644 index 0000000..38c02c5 --- /dev/null +++ b/crates/whirl_server/src/interaction/peer.rs @@ -0,0 +1,49 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::sync::Arc; + +use tokio::{ + net::TcpStream, + sync::{mpsc, Mutex}, +}; +use tokio_util::codec::{BytesCodec, Framed}; + +use crate::{interaction::shared::Shared, types::Rx}; + +pub struct Peer { + pub bytes: Framed, + pub rx: Rx, +} +impl Peer { + pub async fn new( + state: Arc>, + bytes: Framed, + username: String, + ) -> std::io::Result { + let (tx, rx) = mpsc::unbounded_channel(); + state.lock().await.peers.insert(username, tx); + + Ok(Peer { + bytes, + rx, + }) + } + + pub async fn _change_username( + self, + state: Arc>, + username: &str, + new_username: &str, + ) { + // Remove peer from peers + { + state.lock().await.peers.remove(username); + } + + // Add the peer back with the new username + Self::new(state, self.bytes, new_username.to_string()) + .await + .unwrap(); + } +} diff --git a/crates/whirl_server/src/interaction/shared.rs b/crates/whirl_server/src/interaction/shared.rs new file mode 100644 index 0000000..c2ee671 --- /dev/null +++ b/crates/whirl_server/src/interaction/shared.rs @@ -0,0 +1,28 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::collections::HashMap; + +use bytes::BytesMut; + +use crate::types::Tx; + +pub struct Shared { + pub peers: HashMap, +} +impl Shared { + pub fn new() -> Self { + Shared { + peers: HashMap::new(), + } + } + + pub async fn broadcast(&mut self, message: &[u8]) { + for peer in self.peers.iter_mut() { + peer.1.send(BytesMut::from(message)).unwrap(); + } + } +} +impl Default for Shared { + fn default() -> Self { Self::new() } +} diff --git a/crates/whirl_server/src/lib.rs b/crates/whirl_server/src/lib.rs new file mode 100644 index 0000000..9c3e9d0 --- /dev/null +++ b/crates/whirl_server/src/lib.rs @@ -0,0 +1,90 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +#![feature( + type_ascription, + hash_set_entry, + type_name_of_val, + decl_macro, + proc_macro_hygiene +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "128"] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate async_trait; + +pub mod cmd; +mod interaction; +pub mod net; + +pub mod distributor; +pub mod hub; +mod packet_parser; +mod types; + +use std::{error::Error, fmt, net::SocketAddr, sync::Arc}; + +use tokio::{ + net::{TcpListener, TcpStream}, + sync::Mutex, +}; + +use crate::interaction::shared::Shared; + +#[derive(Debug)] +pub enum ServerType { + AnonRoomServer, + AnonUserServer, + AutoServer, + RoomServer, + UserServer, +} +// https://stackoverflow.com/a/32712140/14452787 +impl fmt::Display for ServerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } +} + +#[async_trait] +pub trait Server { + async fn listen(address: &str, server_type: ServerType) -> Result<(), Box> { + let listener = TcpListener::bind(address).await?; + let state = Arc::new(Mutex::new(Shared::new())); + let mut counter = 0; + + info!( + "server of type {} now listening at {}", + server_type.to_string(), + address + ); + + loop { + let (stream, address) = listener.accept().await?; + counter += 1; + let state = Arc::clone(&state); + + debug!("accepted client at {}", address); + + tokio::spawn(async move { + if let Err(e) = Self::handle(state, stream, address, counter).await { + error!("an error occurred: {}", e); + } + + if std::env::var("EXIT_ON_CLIENT_DISCONNECT").unwrap_or_else(|_| "false".to_string()) + == "true" + { + std::process::exit(0); + } + }); + } + } + + async fn handle( + state: Arc>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box>; +} diff --git a/crates/whirl_server/src/net/constants.rs b/crates/whirl_server/src/net/constants.rs new file mode 100644 index 0000000..169b461 --- /dev/null +++ b/crates/whirl_server/src/net/constants.rs @@ -0,0 +1,93 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub const VAR_PROTOCOL_VERSION: i32 = 24; +pub const STATECMD: i32 = 2; +pub const MAXCMD: i32 = 255; +pub const CURRENT_ROOM: i32 = 253; +pub const CLIENT: i32 = 1; +pub const CO: i32 = 254; +pub const PO: i32 = 255; +pub const VAR_APPNAME: i32 = 1; +pub const VAR_USERNAME: i32 = 2; +pub const VAR_PROTOCOL: i32 = 3; +pub const VAR_ERROR: i32 = 4; +pub const VAR_CHANNEL: i32 = 5; +pub const VAR_BITMAP: i32 = 5; +pub const VAR_PASSWORD: i32 = 6; +pub const VAR_AVATARS: i32 = 7; +pub const VAR_UPDATETIME: i32 = 8; +pub const VAR_CLIENT: i32 = 9; +pub const VAR_SERIAL: i32 = 10; +pub const VAR_EMAIL: i32 = 11; +pub const VAR_LOGONOFF: i32 = 12; +pub const VAR_DURATION: i32 = 13; +pub const VAR_GUEST: i32 = 14; +pub const VAR_SERVERTYPE: i32 = 15; +pub const VAR_VIZCARD: i32 = 16; +pub const VAR_NEW_PASSWD: i32 = 20; +pub const VAR_PRIV: i32 = 22; +pub const VAR_ASLEEP: i32 = 23; +pub const VAR_EXTERNAL_HTTP_SERVER: i32 = 24; +pub const VAR_SCRIPT_SERVER: i32 = 25; +pub const VAR_SMTP_SERVER: i32 = 26; +pub const VAR_MAIL_DOMAIN: i32 = 27; +pub const VAR_NEW_USERNAME: i32 = 28; +pub const VAR_INTERNAL_HTTP_SERVER: i32 = 29; +pub const VAR_INVENTORY: i32 = 32; +pub const ACK: i32 = 0; +pub const NAK_BAD_USER: i32 = 1; +pub const NAK_MAX_ORDINARY: i32 = 2; +pub const NAK_MAX_PRIORITY: i32 = 3; +pub const NAL_BAD_WORLD: i32 = 4; +pub const NAK_FATAIL: i32 = 5; +pub const NAK_BAD_PROTOCOL: i32 = 6; +pub const NAK_BAD_CLIENTSW: i32 = 7; +pub const NAK_BAD_ROOM: i32 = 8; +pub const NAK_BAD_SERIAL: i32 = 9; +pub const NAK_TAKEN_SERIAL: i32 = 10; +pub const NAK_TAKEN_USER: i32 = 11; +pub const NAK_NO_SUCH_USER: i32 = 12; +pub const NAK_BAD_PASSWORD: i32 = 13; +pub const NAK_BAD_ACCOUNT: i32 = 14; +pub const NAK_NOT_LOGGEDON: i32 = 15; +pub const NAK_BAD_IPADDRESS: i32 = 16; +pub const NAK_LOGGEDON: i32 = 17; +pub const NAK_CRYPT_METHOD: i32 = 18; +pub const NAK_CRYPT_ERROR: i32 = 19; +pub const NAK_SESSIONINIT: i32 = 20; +pub const NAK_ROOM_FULL: i32 = 21; +pub const NAK_SHUTDOWN: i32 = 100; +pub const NAK_WRITE_ERROR: i32 = 101; +pub const NAK_READ_ERROR: i32 = 102; +pub const NAK_UNEXPECTED: i32 = 103; +pub const NAK_CONNECTION: i32 = 104; +pub const NAK_IOSTREAMS: i32 = 105; +pub const NAK_TIMEOUT: i32 = 106; +pub const NAK_UNREACHABLE: i32 = 107; +pub const STATUS_CONNECTED: i32 = 200; +pub const STATUS_DETACHING: i32 = 201; +pub const STATUS_WILLRETRY: i32 = 202; +pub const STATUS_DISCONNECTED: i32 = 203; +pub const STATUS_DEAD: i32 = 204; +pub const STATUS_OFFLINE: i32 = 205; +pub const STATUS_GALAXY_ONLINE: i32 = 206; +pub const STATUS_GALAXY_OFFLINE: i32 = 206; +pub const PROPFLAG_BINARY: i32 = 16; +pub const PROPFLAG_FINGER: i32 = 32; +pub const PROPFLAG_AUTOUPDATE: i32 = 64; +pub const PROPFLAG_DBSTORE: i32 = 128; +pub const PROPACCESS_POSSESS: i32 = 1; +pub const PROPACCESS_PRIVATE: i32 = 2; +pub const SERVER_UNKNOWN: i32 = 0; +pub const USER_SERVER_DB: i32 = 1; +pub const USER_SERVER_ANON: i32 = 2; +pub const ROOM_SERVER_US: i32 = 3; +pub const ROOM_SERVER_ANON: i32 = 4; +pub const PRIV_NONE: i32 = 0; +pub const PRIV_BUILD: i32 = 1; +pub const PRIV_BROADCAST: i32 = 2; +pub const PRIV_PROPERTY: i32 = 4; +pub const PRIV_VIP: i32 = 8; +pub const PRIV_VIP2: i32 = 16; +pub const PRIV_SPECIALGUEST: i32 = 64; diff --git a/crates/whirl_server/src/net/converter.rs b/crates/whirl_server/src/net/converter.rs new file mode 100644 index 0000000..c976dff --- /dev/null +++ b/crates/whirl_server/src/net/converter.rs @@ -0,0 +1,57 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use bytes::{BufMut, BytesMut}; + +use crate::{ + cmd::constants::PROPUPD, + net::{ + constants::{PROPACCESS_POSSESS, PROPFLAG_DBSTORE}, + structure::NetworkProperty, + }, +}; + +pub fn property_list_to_bytes( + command_id: i32, + obj_id: i32, + mut property_list: Vec, +) -> Vec { + let mut command = BytesMut::new(); + + // Iterate over all network properties + loop { + // Check if there are any properties left + trace!("props left: {}", property_list.len()); + if property_list.is_empty() { + break; + } + + let property = &property_list[0]; // Property we are currently iterating over + trace!("current prop: {}:{}", property.prop_id, property.value); + + command.put_u8(property.prop_id as u8); // Property ID + + // NOTE: THIS IS SUPER BAD DO NOT DO THIS! But it works! + if command_id == PROPUPD { + command.put_u8(PROPFLAG_DBSTORE as u8); // Flag (s) + command.put_u8(PROPACCESS_POSSESS as u8); // Access + } + + command.put_u8(property.value.len() as u8); // Property UTF-8 Length + command.put_slice(property.value.as_bytes()); // Property UTF-8 + + property_list.reverse(); + property_list.pop(); + property_list.reverse(); + } + + // Convert to vector and insert the header + let mut command_as_vec = command.to_vec(); + + command_as_vec.insert(0, command_id as u8); // Command ID + command_as_vec.insert(0, obj_id as u8); // ObjId + command_as_vec.insert(0, command.len() as u8 + 3); // Data length + + // Return bytes + command_as_vec +} diff --git a/crates/whirl_server/src/net/mod.rs b/crates/whirl_server/src/net/mod.rs new file mode 100644 index 0000000..afa45c8 --- /dev/null +++ b/crates/whirl_server/src/net/mod.rs @@ -0,0 +1,7 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub mod constants; +pub mod converter; +pub mod property_parser; +pub mod structure; diff --git a/crates/whirl_server/src/net/property_parser.rs b/crates/whirl_server/src/net/property_parser.rs new file mode 100644 index 0000000..f86b767 --- /dev/null +++ b/crates/whirl_server/src/net/property_parser.rs @@ -0,0 +1,38 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::from_utf8; + +use crate::net::structure::NetworkProperty; + +/// Iterate over a network property in the form of bytes and return a list of +/// human-readable properties. +pub fn parse_network_property(mut data: Vec) -> Vec { + let mut property_list = vec![]; + + // Iterate over all network properties + loop { + // Check if any commands are present + if data.len() <= 2 { + break; + } + trace!("iteration: {:?}", data); + // if data[0] == 0 { + // break; + // } + + let property_length = data[1] + 2; + property_list.push(NetworkProperty { + prop_id: data[0] as i32, + value: from_utf8(&data[2..data[1] as usize + 2]) + .unwrap() + .to_string(), + }); + + // Remove current property from the network property + data = data[property_length as usize..].to_vec(); + } + + // Return the human-readable network property + property_list +} diff --git a/crates/whirl_server/src/net/structure.rs b/crates/whirl_server/src/net/structure.rs new file mode 100644 index 0000000..1fb1051 --- /dev/null +++ b/crates/whirl_server/src/net/structure.rs @@ -0,0 +1,18 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +pub struct NetworkProperty { + pub prop_id: i32, + pub value: String, +} +impl NetworkProperty { + pub fn new() -> Self { NetworkProperty::default() } +} +impl Default for NetworkProperty { + fn default() -> Self { + NetworkProperty { + prop_id: 0, + value: "".to_string(), + } + } +} diff --git a/crates/whirl_server/src/packet_parser.rs b/crates/whirl_server/src/packet_parser.rs new file mode 100644 index 0000000..bfeba9e --- /dev/null +++ b/crates/whirl_server/src/packet_parser.rs @@ -0,0 +1,38 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use bytes::BytesMut; + +/// Read all commands from the given buffer. +/// +/// # Process +/// 1. Get a command from `buffer` based on first byte. +/// 2. Push command to `commands`. +/// 3. Remove command from `buffer`. +/// 4. Iterate and do this for all commands within `buffer`. +pub fn parse_commands_from_packet(mut buffer: BytesMut) -> Vec { + let mut commands: Vec = Vec::new(); + trace!("initial buffer: {:?}, length: {}", buffer, buffer.len()); + + let data_length = buffer.get(0).unwrap().to_owned() as usize; + if buffer.len() > data_length { + loop { + trace!("loop: {:?}, length: {}", buffer, buffer.len()); + let command_length = buffer.get(0).unwrap().to_owned() as usize; + commands.push(BytesMut::from(buffer.get(0..command_length).unwrap())); + + // Remove command from buffer + buffer = buffer.split_off(command_length); + + // Check if any more commands are present + if buffer.is_empty() { + break; + } + } + } else { + // There will always be at least one command, push it. + commands.push(BytesMut::from(buffer.get(0..data_length).unwrap())); + } + + commands // Return command (s) +} diff --git a/crates/whirl_server/src/types.rs b/crates/whirl_server/src/types.rs new file mode 100644 index 0000000..3d49752 --- /dev/null +++ b/crates/whirl_server/src/types.rs @@ -0,0 +1,8 @@ +// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective +// SPDX-License-Identifier: GPL-3.0-only + +use bytes::BytesMut; +use tokio::sync::mpsc; + +pub type Tx = mpsc::UnboundedSender; +pub type Rx = mpsc::UnboundedReceiver; diff --git a/diesel.toml b/diesel.toml index 6db2d37..489209e 100644 --- a/diesel.toml +++ b/diesel.toml @@ -2,4 +2,4 @@ # see diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "whirl/src/db/schema.rs" +file = "crates/whirl_db/src/schema.rs" diff --git a/rustfmt.toml b/rustfmt.toml index d2d6a2d..0e9396b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -8,7 +8,7 @@ force_multiline_blocks = true format_code_in_doc_comments = true format_macro_matchers = true format_strings = true -ignore = ["whirl/src/db/schema.rs"] +ignore = ["crates/whirl_db/src/schema.rs"] imports_layout = "HorizontalVertical" license_template_path = ".license_template" match_arm_blocks = false diff --git a/whirl/.license_template b/whirl/.license_template deleted file mode 100644 index 1fda769..0000000 --- a/whirl/.license_template +++ /dev/null @@ -1,2 +0,0 @@ -// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only diff --git a/whirl/Cargo.toml b/whirl/Cargo.toml deleted file mode 100644 index 2d13bfb..0000000 --- a/whirl/Cargo.toml +++ /dev/null @@ -1,72 +0,0 @@ -[package] -name = "whirl" -version = "0.1.0" -authors = ["Fuwn "] -edition = "2018" -description = "Whirl, an open-source WorldServer implementation in Rust." -documentation = "https://whirlsplash.org/docs/" -readme = "../README.rst" -homepage = "https://whirlsplash.org" -repository = "https://github.com/Whirlsplash/whirl" -license = "GPL-3.0-only" -# license-file = "LICENSE" -keywords = ["rust", "worldserver", "whirl", "whirlsplash"] -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Environment -dotenv = "0.15.0" - -# Logging -log = "0.4.14" -flexi_logger = "0.17.1" -human-panic = "1.0.3" -# simple-error = "0.2.3" - -# Utility -rand = "0.8.3" -async-trait = "0.1.50" -sysinfo = "0.17.5" - -# Byte Manipulation -bytes = "1.0.1" -byteorder = "1.4.3" - -# Serialization -serde = "1.0.126" -serde_derive = "1.0.126" - -# CLI -structopt = "0.3.21" - -# Config -whirl_config = { path = "../whirl_config" } - -# TCP -tokio = { version = "1.6.0", features = ["full"] } -tokio-util = { version = "0.6.7", features = ["codec"] } -tokio-stream = "0.1.6" - -# Database -libsqlite3-sys = { version = "0.9.1", features = ["bundled"] } -diesel = { version = "1.4.6", features = ["sqlite"] } - -# Web-server -actix-web = { version = "3.3.2", features = ["rustls"] } -actix-cors = "0.5.4" - -# Prompt -whirl_prompt = { path = "../whirl_prompt" } - -# Server -whirl_server = { path = "../whirl_server" } - -# Allocator -[target.'cfg(windows)'.dependencies] -mimalloc = { version = "0.1.25", default-features = false } - -[target.'cfg(unix)'.dependencies] -jemallocator = "0.3.2" - diff --git a/whirl/src/api/mod.rs b/whirl/src/api/mod.rs deleted file mode 100644 index c744f3d..0000000 --- a/whirl/src/api/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use actix_web::web::resource; - -mod routes; - -pub struct Api; -impl Api { - pub async fn listen( - tx: std::sync::mpsc::Sender, - address: &str, - ) -> std::io::Result<()> { - let mut sys = actix_web::rt::System::new("api"); - - let server = actix_web::HttpServer::new(|| { - actix_web::App::new() - .wrap(actix_cors::Cors::default().allow_any_origin()) - .service(resource("/").to(|| async { "Whirlsplash" })) - .service(resource("/api/v1/statistics").to(routes::stats::statistics)) - }) - .bind(address)? - .run(); - - info!("http api now listening at {}", address); - - let _ = tx.send(server.clone()); - - sys.block_on(server) - } -} diff --git a/whirl/src/api/routes/mod.rs b/whirl/src/api/routes/mod.rs deleted file mode 100644 index f5a2ff4..0000000 --- a/whirl/src/api/routes/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod stats; diff --git a/whirl/src/api/routes/stats/mod.rs b/whirl/src/api/routes/stats/mod.rs deleted file mode 100644 index 04ce5e6..0000000 --- a/whirl/src/api/routes/stats/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod structures; - -use actix_web::HttpResponse; -use sysinfo::{get_current_pid, ProcessExt, System, SystemExt}; - -use crate::{ - api::routes::stats::structures::{Statistics, StatisticsProcess, StatisticsSystem}, - utils::system::seconds_to_hrtime, -}; - -// This is mostly for developmental testing, it consumes more CPU than it's -// worth. -pub fn statistics() -> HttpResponse { - let mut sys = System::new_all(); - sys.refresh_all(); - - let process = sys.get_process(get_current_pid().unwrap()).unwrap(); - - HttpResponse::Ok().json(Statistics { - system: StatisticsSystem { - os_type: sys.get_name().unwrap(), - release: sys.get_kernel_version().unwrap(), - uptime: seconds_to_hrtime(sysinfo::System::new().get_uptime() as usize), - }, - process: StatisticsProcess { - // (process.cpu_usage() * 100.0).round() / 100.0 - memory_usage: (process.memory() / 1000).to_string(), - cpu_usage: (process.cpu_usage() / sys.get_processors().len() as f32).to_string(), - // uptime: seconds_to_hrtime((sys.get_uptime() - process.start_time()) as usize), - }, - }) -} diff --git a/whirl/src/api/routes/stats/structures.rs b/whirl/src/api/routes/stats/structures.rs deleted file mode 100644 index 88fc852..0000000 --- a/whirl/src/api/routes/stats/structures.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#[derive(Serialize)] -pub struct Statistics { - pub system: StatisticsSystem, - pub process: StatisticsProcess, -} -#[derive(Serialize)] -pub struct StatisticsSystem { - #[serde(rename = "type")] - pub os_type: String, - pub release: String, - pub uptime: String, -} -#[derive(Serialize)] -pub struct StatisticsProcess { - pub memory_usage: String, - pub cpu_usage: String, - // pub uptime: String, -} diff --git a/whirl/src/cli.rs b/whirl/src/cli.rs deleted file mode 100644 index 82d18c1..0000000 --- a/whirl/src/cli.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use structopt::clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; -use whirl_config::Config; - -use crate::subs::run; - -pub struct Cli; -impl Cli { - pub fn setup() -> ArgMatches<'static> { - let matches = Self::cli().get_matches(); - - std::env::set_var("DATABASE_URL", "whirl.sqlite3"); - - matches - } - - pub async fn execute(matches: ArgMatches<'_>) -> Result<(), Box> { - if Config::get().whirlsplash.log.test { - error!("error"); - warn!("warn"); - info!("info"); - debug!("debug"); - trace!("trace"); - } - - if matches.is_present("run") { - run().await; - } else if let Some(cmd) = matches.subcommand_matches("config") { - if cmd.is_present("show") { - println!("{:#?}", Config::get()); - } - } else if let Some(shell) = matches.subcommand_matches("completions") { - if shell.is_present("powershell") { - Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::PowerShell, "."); - } else if shell.is_present("bash") { - Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Bash, "."); - } else if shell.is_present("elvish") { - Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Elvish, "."); - } else if shell.is_present("zsh") { - Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Zsh, "."); - } else if shell.is_present("fish") { - Self::cli().gen_completions(env!("CARGO_PKG_NAME"), Shell::Fish, "."); - } - debug!("generated shell completions"); - } else if matches.is_present("clean") { - let cleanable_directories = vec!["./log/"]; - for dir in cleanable_directories { - let mut file_type = "directory"; - if !dir.ends_with('/') { - file_type = "file"; - } - println!("cleaning {}: {}", file_type, dir); - if let Err(e) = std::fs::remove_dir_all(dir) { - warn!("cannot delete {}: {}: {}", file_type, dir, e); - } - } - } - - Ok(()) - } - - fn cli() -> App<'static, 'static> { - App::new(env!("CARGO_PKG_NAME")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) - .settings(&[AppSettings::SubcommandRequiredElseHelp]) - .subcommands(vec![ - SubCommand::with_name("run").about("Start the WorldServer"), - SubCommand::with_name("config") - .setting(AppSettings::SubcommandRequiredElseHelp) - .subcommands(vec![SubCommand::with_name("show")]), - SubCommand::with_name("completions") - .setting(AppSettings::SubcommandRequiredElseHelp) - .about("Generate shell completions") - .subcommands(vec![ - SubCommand::with_name("powershell"), - SubCommand::with_name("bash"), - SubCommand::with_name("elvish"), - SubCommand::with_name("zsh"), - SubCommand::with_name("fish"), - ]), - SubCommand::with_name("clean") - .about("Delete Whirl generated files/ directories which are NOT critical. E.g., logs/"), - ]) - .args(&[ - Arg::with_name("debug").short("d").long("debug"), - Arg::with_name("trace").short("t").long("trace"), - ]) - } -} diff --git a/whirl/src/db/mod.rs b/whirl/src/db/mod.rs deleted file mode 100644 index b93390d..0000000 --- a/whirl/src/db/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod models; -mod schema; - -use diesel::prelude::*; - -// use crate::db::models::*; - -pub fn establish_connection() -> SqliteConnection { - let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "whirl.sqlite3".to_string()); - SqliteConnection::establish(&database_url) - .unwrap_or_else(|_| panic!("error connecting to {}", database_url)) -} - -/// Only works if you have a valid database already setup! -#[cfg(test)] -#[test] -#[ignore] -pub fn show_serials() { - use crate::db::{models::SerialNumber, schema::serial_numbers::dsl::*}; - - dotenv::dotenv().ok(); - - let results = serial_numbers - .limit(5) - .load::(&establish_connection()) - .expect("error loading serial numbers table"); - - println!("found {} results", results.len()); - for result in results { - println!("{}", result.user_name); - } -} diff --git a/whirl/src/db/models.rs b/whirl/src/db/models.rs deleted file mode 100644 index 52304b6..0000000 --- a/whirl/src/db/models.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -//! Much of the documentation that you will see within this module is quoted -//! from http://dev.worlds.net/private/GammaDocs/WorldServer.html#RoomServer. - -// use crate::db::schema::*; - -// -------------- -// | Queryables | -// -------------- - -#[derive(Queryable, Debug)] -pub struct SerialNumber { - pub serial_number: String, - pub user_name: String, - pub serial_status: i32, -} - -#[derive(Queryable, Debug)] -pub struct UserRegistration { - pub user_name_lower: String, - pub user_name: String, - pub serial_number: String, - pub password: String, - pub client_version: String, - pub account_status: i32, - pub registration_date: String, - pub times_on: i32, - pub total_minutes: i32, - pub user_privileges: i32, -} - -#[derive(Queryable, Debug)] -pub struct UserProperty { - pub user_name: String, - pub property_id: i32, - pub property_flags: i32, - pub property_access: i32, - pub property_string_value: String, - pub property_binary_value: String, -} - -// --------------- -// | Insertables | -// --------------- - -// -------------- -// | Updatables | -// -------------- diff --git a/whirl/src/db/schema.rs b/whirl/src/db/schema.rs deleted file mode 100644 index 6d58598..0000000 --- a/whirl/src/db/schema.rs +++ /dev/null @@ -1,35 +0,0 @@ -table! { - serial_numbers (user_name) { - serial_number -> Text, - user_name -> Text, - serial_status -> Integer, - } -} - -table! { - user_properties (user_name) { - user_name -> Text, - property_id -> Integer, - property_flags -> Integer, - property_access -> Integer, - property_string_value -> Integer, - property_binary_value -> Nullable, - } -} - -table! { - user_registration (user_name) { - user_name_lower -> Text, - user_name -> Text, - serial_number -> Text, - password -> Text, - client_version -> Text, - account_status -> Integer, - registration_date -> Text, - times_on -> Integer, - total_minutes -> Integer, - user_privileges -> Integer, - } -} - -allow_tables_to_appear_in_same_query!(serial_numbers, user_properties, user_registration,); diff --git a/whirl/src/lib.rs b/whirl/src/lib.rs deleted file mode 100644 index 5c7da0b..0000000 --- a/whirl/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#![feature( - type_ascription, - hash_set_entry, - type_name_of_val, - decl_macro, - proc_macro_hygiene -)] -#![warn(rust_2018_idioms)] -#![recursion_limit = "128"] - -#[macro_use] -extern crate log; -#[macro_use] -extern crate diesel; -#[macro_use] -extern crate serde_derive; -// #[macro_use] -// extern crate simple_error; - -#[cfg(windows)] -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -#[cfg(unix)] -#[global_allocator] -static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; - -pub mod cli; -pub mod subs; -pub mod whirl; - -pub mod api; -pub mod db; -pub mod utils; diff --git a/whirl/src/main.rs b/whirl/src/main.rs deleted file mode 100644 index 074ca69..0000000 --- a/whirl/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::error::Error; - -use whirl::whirl::Whirl; - -#[tokio::main] -async fn main() -> Result<(), Box> { Whirl::splash().await } diff --git a/whirl/src/subs.rs b/whirl/src/subs.rs deleted file mode 100644 index 36b00a5..0000000 --- a/whirl/src/subs.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use whirl_config::Config; -use whirl_prompt::Prompt; -use whirl_server::{ - distributor::Distributor, - hub::Hub, - Server, - ServerType::{AutoServer, RoomServer}, -}; - -use crate::api::Api; - -pub async fn run() { - let (tx, _rx) = std::sync::mpsc::channel(); - - let _threads = vec![ - tokio::spawn(async move { - let _ = Distributor::listen( - &*format!("0.0.0.0:{}", Config::get().distributor.port), - AutoServer, - ) - .await; - }), - tokio::spawn(async move { - let _ = Hub::listen(&*format!("0.0.0.0:{}", Config::get().hub.port), RoomServer).await; - }), - tokio::spawn(async move { - let _ = Api::listen( - tx, - &*format!("0.0.0.0:{}", Config::get().whirlsplash.api.port), - ) - .await; - }), - ]; - - if std::env::var("DISABLE_PROMPT").unwrap_or_else(|_| "false".to_string()) == "true" - || !Config::get().whirlsplash.prompt.enable - { - info!("starting with prompt disabled"); - loop { - std::thread::sleep(std::time::Duration::default()); - } - } else { - Prompt::handle().await; - } - - // actix_web::rt::System::new("").block_on(rx.recv().unwrap().stop(true)); -} diff --git a/whirl/src/utils/log.rs b/whirl/src/utils/log.rs deleted file mode 100644 index 39ceca0..0000000 --- a/whirl/src/utils/log.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use whirl_config::Config; - -pub fn calculate_log_level() -> String { - let mut level; - - level = match Config::get().whirlsplash.log.level { - 2 => "debug".to_string(), - 3 => "trace".to_string(), - _ => "info".to_string(), - }; - if !Config::get().whirlsplash.log.everything { - level = format!("whirl={}", level); - } - - level -} diff --git a/whirl/src/utils/mod.rs b/whirl/src/utils/mod.rs deleted file mode 100644 index dc9a008..0000000 --- a/whirl/src/utils/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod log; -pub mod sort; -pub mod system; diff --git a/whirl/src/utils/sort.rs b/whirl/src/utils/sort.rs deleted file mode 100644 index 131fa55..0000000 --- a/whirl/src/utils/sort.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub fn sort_vec_alphabetically(vec: &mut Vec<&str>) { - vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); -} diff --git a/whirl/src/utils/system.rs b/whirl/src/utils/system.rs deleted file mode 100644 index 7a823a0..0000000 --- a/whirl/src/utils/system.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -const WEEK: usize = 60 * 60 * 60 * 60; -const DAY: usize = 60 * 60 * 60; -const HOUR: usize = 60 * 60; -const MIN: usize = 60; - -fn make_parts(t: usize, steps: &[usize], mut accum: Vec) -> Vec { - match steps.split_first() { - None => accum, - Some((s, steps)) => { - accum.push(t / *s); - make_parts(t % *s, steps, accum) - } - } -} - -pub fn seconds_to_hrtime(seconds: usize) -> String { - let word = ["week", "day", "hour", "min", "sec"]; - - make_parts(seconds, &[WEEK, DAY, HOUR, MIN, 1], Vec::new()) - .iter() - .enumerate() - .filter_map(|(i, s)| { - if s > &0 { - if s > &1 { - Some(format!("{} {}s", s, word[i])) - } else { - Some(format!("{} {}", s, word[i])) - } - } else { - None - } - }) - .collect::>() - .join(", ") -} diff --git a/whirl/src/whirl.rs b/whirl/src/whirl.rs deleted file mode 100644 index 88772f6..0000000 --- a/whirl/src/whirl.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::error::Error; - -use crate::{cli::Cli, utils::log::calculate_log_level}; - -pub struct Whirl; -impl Whirl { - pub async fn splash() -> Result<(), Box> { - // Environment and CLI - let matches = Cli::setup(); - - // Logging - dotenv::dotenv().ok(); - human_panic::setup_panic!(); - let logger = flexi_logger::Logger::with_str(calculate_log_level()); - if std::env::var("LOG_FILE").unwrap_or_else(|_| "true".to_string()) == "false" - || !whirl_config::Config::get().whirlsplash.log.file - || std::env::args().collect::>()[1] == "clean" - // Cheeky as all hell. - { - logger.start()?; - } else { - logger - .print_message() - .log_to_file() - .directory("log") - .start()?; - } - - Cli::execute(matches).await.unwrap(); - - Ok(()) - } -} diff --git a/whirl_config/.license_template b/whirl_config/.license_template deleted file mode 100644 index 1fda769..0000000 --- a/whirl_config/.license_template +++ /dev/null @@ -1,2 +0,0 @@ -// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only diff --git a/whirl_config/Cargo.toml b/whirl_config/Cargo.toml deleted file mode 100644 index e66c7ce..0000000 --- a/whirl_config/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "whirl_config" -version = "0.1.0" -authors = ["Fuwn "] -edition = "2018" -description = "Whirl, an open-source WorldServer implementation in Rust." -documentation = "https://whirlsplash.org/docs/" -readme = "../README.rst" -homepage = "https://whirlsplash.org" -repository = "https://github.com/Whirlsplash/whirl" -license = "GPL-3.0-only" -# license-file = "LICENSE" -keywords = ["rust", "worldserver", "whirl", "whirlsplash"] -publish = false - -[dependencies] -# Config -config = "0.11.0" - -# Serialization -serde = "1.0.126" -serde_derive = "1.0.126" - -# Logging -log = "0.4.14" diff --git a/whirl_config/Whirl.default.toml b/whirl_config/Whirl.default.toml deleted file mode 100644 index aa2d684..0000000 --- a/whirl_config/Whirl.default.toml +++ /dev/null @@ -1,24 +0,0 @@ -# See more keys and their definitions at https://whirlsplash.org/docs/whirl/configuration - -[whirlsplash] -worldsmaster_username = "WORLDSMASTER" -ip = "0.0.0.0" -api.port = 8080 - -[whirlsplash.prompt] -enable = false -ps1 = "[WORLDSMASTER@Whirlsplash ~]$" - -[whirlsplash.log] -enable = true -level = 1 -everything = false -test = false -file = true - -[distributor] -worldsmaster_greeting = "Welcome to Whirlsplash!" -port = 6650 - -[hub] -port = 5673 diff --git a/whirl_config/src/lib.rs b/whirl_config/src/lib.rs deleted file mode 100644 index 8f312cd..0000000 --- a/whirl_config/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#![feature( - type_ascription, - hash_set_entry, - type_name_of_val, - decl_macro, - proc_macro_hygiene -)] -#![warn(rust_2018_idioms)] -#![recursion_limit = "128"] - -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate log; - -mod structures; - -use config::{ConfigError, File}; - -use crate::structures::{ - DistributorConfig, - HubConfig, - WhirlsplashApiConfig, - WhirlsplashConfig, - WhirlsplashLogConfig, - WhirlsplashPromptConfig, -}; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Config { - pub whirlsplash: WhirlsplashConfig, - pub distributor: DistributorConfig, - pub hub: HubConfig, -} -impl Config { - pub fn refresh() { let _ = config::Config::new().refresh(); } - - fn load() -> Result { - let mut s = config::Config::new(); - - s.merge(File::with_name("./Whirl.toml").required(false))?; - s.try_into() - } - - pub fn get() -> Config { - return if let Err(why) = Self::load() { - error!( - "unable to load configuration file, reverting to default value: {}", - why - ); - Self::default() - } else { - Self::load().unwrap() - }; - } -} -impl Default for Config { - fn default() -> Self { - Config { - whirlsplash: WhirlsplashConfig { - worldsmaster_username: "WORLDSMASTER".to_string(), - ip: "0.0.0.0".to_string(), - api: WhirlsplashApiConfig { - port: 80 - }, - prompt: WhirlsplashPromptConfig { - enable: false, - ps1: "[WORLDSMASTER@Whirlsplash ~]$".to_string(), - }, - log: WhirlsplashLogConfig { - enable: true, - level: 1, - everything: false, - test: false, - file: true, - }, - }, - distributor: DistributorConfig { - worldsmaster_greeting: "Welcome to Whirlsplash!".to_string(), - port: 6650, - }, - hub: HubConfig { - port: 5673 - }, - } - } -} diff --git a/whirl_config/src/structures.rs b/whirl_config/src/structures.rs deleted file mode 100644 index 4f1d62e..0000000 --- a/whirl_config/src/structures.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#[derive(Serialize, Deserialize, Debug)] -pub struct WhirlsplashConfig { - pub worldsmaster_username: String, - pub ip: String, - pub api: WhirlsplashApiConfig, - pub prompt: WhirlsplashPromptConfig, - pub log: WhirlsplashLogConfig, -} -#[derive(Serialize, Deserialize, Debug)] -pub struct WhirlsplashApiConfig { - pub port: i64, -} -#[derive(Serialize, Deserialize, Debug)] -pub struct WhirlsplashPromptConfig { - pub enable: bool, - pub ps1: String, -} -#[derive(Serialize, Deserialize, Debug)] -pub struct WhirlsplashLogConfig { - pub enable: bool, - pub level: i64, - pub everything: bool, - pub test: bool, - pub file: bool, -} -#[derive(Serialize, Deserialize, Debug)] -pub struct DistributorConfig { - pub worldsmaster_greeting: String, - pub port: i64, -} -#[derive(Serialize, Deserialize, Debug)] -pub struct HubConfig { - pub port: i64, -} diff --git a/whirl_prompt/.license_template b/whirl_prompt/.license_template deleted file mode 100644 index 1fda769..0000000 --- a/whirl_prompt/.license_template +++ /dev/null @@ -1,2 +0,0 @@ -// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only diff --git a/whirl_prompt/Cargo.toml b/whirl_prompt/Cargo.toml deleted file mode 100644 index d778c7e..0000000 --- a/whirl_prompt/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "whirl_prompt" -version = "0.1.0" -authors = ["Fuwn "] -edition = "2018" -description = "Whirl, an open-source WorldServer implementation in Rust." -documentation = "https://whirlsplash.org/docs/" -readme = "../README.rst" -homepage = "https://whirlsplash.org" -repository = "https://github.com/Whirlsplash/whirl" -license = "GPL-3.0-only" -# license-file = "LICENSE" -keywords = ["rust", "worldserver", "whirl", "whirlsplash"] -publish = false - -[dependencies] -# Utility -sysinfo = "0.17.5" -colour = "0.6.0" - -# Config -whirl_config = { path = "../whirl_config" } - -# Web -curl = "0.4.37" diff --git a/whirl_prompt/src/builtins/mod.rs b/whirl_prompt/src/builtins/mod.rs deleted file mode 100644 index 0359443..0000000 --- a/whirl_prompt/src/builtins/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod structures; - -use std::io::Write; - -use sysinfo::SystemExt; -use whirl_config::Config; - -use crate::constants::{FILES, HELPABLES_BUILTINS, HELPABLES_BUILTIN_CONFIG}; - -pub fn builtin_echo(args: &[String]) -> i32 { - println!("{}", args.join(" ")); - 0 -} - -pub fn builtin_history(history: &[String]) -> i32 { - for (index, cmd) in history.iter().enumerate() { - println!("{} {}", index, cmd.trim()); - } - 0 -} - -pub fn builtin_help() -> i32 { - for help in HELPABLES_BUILTINS.iter() { - println!("{}", help); - } - - 0 -} - -pub fn builtin_ls() -> i32 { - for file in &FILES { - print!("{} ", file); - } - println!(); - - 0 -} - -pub async fn builtin_cat(args: &[String]) -> i32 { - let file; - if let Some(file_name) = args.get(0) { - file = file_name.to_string(); - } else { - return 0; - }; - - match file.as_str() { - "README.rst" => { - let mut easy = curl::easy::Easy::new(); - - easy - .url("https://raw.githubusercontent.com/Whirlsplash/whirl/develop/README.rst") - .unwrap(); - - let mut transfer = easy.transfer(); - transfer - .write_function(|data| { - std::io::stdout().write_all(data).unwrap(); - Ok(data.len()) - }) - .unwrap(); - transfer.perform().unwrap(); - } - "Whirl.toml" => { - colour::red_ln!("NOTE: This is just a wrapper for `config show`."); - println!("{:#?}", Config::get()); - } - _ => println!("/cat: {}: no such file or directory", file), - } - - 0 -} - -pub fn builtin_config(args: &[String]) -> i32 { - match args.get(0) { - Some(sub) => - match sub.as_str() { - "show" => println!("{:#?}", Config::get()), - "help" | "--help" | "-h" => - for help in HELPABLES_BUILTIN_CONFIG.iter() { - println!("{}", help); - }, - "refresh" => Config::refresh(), - _ => println!("invalid arguments provided"), - }, - None => println!("invalid amount arguments provided"), - } - 0 -} - -pub fn builtin_fetch() -> i32 { - // rfetch: https://github.com/Mangeshrex/rfetch - - let mut sys = sysinfo::System::new(); - sys.refresh_processes(); - - println!(" "); - println!(" .-. os {}", env!("CARGO_PKG_NAME")); - println!(" oo| ker {}", env!("CARGO_PKG_VERSION")); - println!(" / '\\ sh /wsh"); - println!(" (\\_;/) up null"); - println!(" "); - - 0 -} diff --git a/whirl_prompt/src/builtins/structures.rs b/whirl_prompt/src/builtins/structures.rs deleted file mode 100644 index 4217a38..0000000 --- a/whirl_prompt/src/builtins/structures.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::str::FromStr; - -pub enum BuiltIn { - Echo, - History, - Exit, - Null, - Help, - Ls, - Cat, - Config, - Fetch, -} -impl FromStr for BuiltIn { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "echo" => Ok(BuiltIn::Echo), - "history" => Ok(BuiltIn::History), - "exit" => Ok(BuiltIn::Exit), - "null" => Ok(BuiltIn::Null), - "help" => Ok(BuiltIn::Help), - "ls" => Ok(BuiltIn::Ls), - "cat" => Ok(BuiltIn::Cat), - "config" => Ok(BuiltIn::Config), - "fetch" => Ok(BuiltIn::Fetch), - _ => Err(()), - } - } -} diff --git a/whirl_prompt/src/constants.rs b/whirl_prompt/src/constants.rs deleted file mode 100644 index c173a57..0000000 --- a/whirl_prompt/src/constants.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub const FILES: [&str; 2] = ["README.rst", "Whirl.toml"]; -pub const HELPABLES_BUILTINS: [&str; 8] = [ - "cat - display the contents of a present file", - "config - manipulate the configuration", - "echo - display a line of predefined text", - "exit - end the process", - "fetch - a neofetch like utility loosely based on rfetch", - "help - you are here", - "history - display the command history", - "ls - display the present files", -]; -pub const HELPABLES_BUILTIN_CONFIG: [&str; 3] = [ - "help - you are here", - "refresh - reload the configuration file", - "show - display the current configuration", -]; diff --git a/whirl_prompt/src/lib.rs b/whirl_prompt/src/lib.rs deleted file mode 100644 index 32b247f..0000000 --- a/whirl_prompt/src/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#![feature( - type_ascription, - hash_set_entry, - type_name_of_val, - decl_macro, - proc_macro_hygiene -)] -#![warn(rust_2018_idioms)] -#![recursion_limit = "128"] - -mod builtins; -mod constants; -mod structure; - -use std::{io, io::Write, str::FromStr}; - -use whirl_config::Config; - -use crate::{ - builtins::{ - builtin_cat, - builtin_config, - builtin_echo, - builtin_fetch, - builtin_help, - builtin_history, - builtin_ls, - structures::BuiltIn, - }, - structure::Command, -}; - -pub struct Prompt { - history: Vec, -} -impl Prompt { - pub async fn handle() -> ! { - let mut prompt = Prompt { - history: vec![] - }; - - loop { - Prompt::write_prompt(); - let command = prompt.read_command(); - prompt - .process_command(Prompt::tokenize_command(command)) - .await; - } - } - - fn write_prompt() { - print!("{} ", Config::get().whirlsplash.prompt.ps1); - io::stdout().flush().unwrap(); - } - - fn read_command(&mut self) -> String { - let mut input = String::new(); - io::stdin() - .read_line(&mut input) - .expect("failed to read command from stdin"); - - if input.len() <= 2 { - input = "null".to_string(); - } - - input - } - - fn tokenize_command(c: String) -> Command { - let mut command_split: Vec = c.split_whitespace().map(|s| s.to_string()).collect(); - - Command { - keyword: command_split.remove(0), - args: command_split, - } - } - - // TODO: Find a way to make this access itself `history` doesn't have to be - // passed everytime. - async fn process_command(&mut self, c: Command) -> i32 { - let exit_code = match BuiltIn::from_str(&c.keyword) { - Ok(BuiltIn::Echo) => builtin_echo(&c.args), - Ok(BuiltIn::Exit) => std::process::exit(0), - Ok(BuiltIn::History) => builtin_history(&self.history), - Ok(BuiltIn::Null) => 0, - Ok(BuiltIn::Help) => builtin_help(), - Ok(BuiltIn::Ls) => builtin_ls(), - Ok(BuiltIn::Cat) => builtin_cat(&c.args).await, - Ok(BuiltIn::Config) => builtin_config(&c.args), - Ok(BuiltIn::Fetch) => builtin_fetch(), - _ => { - println!("wsh: command not found: {}", &c.keyword); - 1 - } - }; - - if c.keyword != "null" { - self.history.push(c.to_line()); - } - - exit_code - } -} - -#[cfg(test)] -mod tokenize_command { - use crate::Prompt; - - #[test] - #[ignore] - fn empty_command() { assert_eq!("", Prompt::tokenize_command("".to_string()).keyword) } - - #[test] - fn test_keyword() { assert_eq!("test", Prompt::tokenize_command("test".to_string()).keyword) } - - #[test] - fn no_arg() { assert_eq!(0, Prompt::tokenize_command("test".to_string()).args.len()) } - - #[test] - fn one_arg() { - assert_eq!( - 1, - Prompt::tokenize_command("test one".to_string()).args.len() - ) - } - - #[test] - fn multi_arg() { - assert_eq!( - 3, - Prompt::tokenize_command("test one two three".to_string()) - .args - .len() - ) - } - - #[test] - #[ignore] - fn quotes() { - assert_eq!( - 2, - Prompt::tokenize_command("test \"one two\" three".to_string()) - .args - .len() - ) - } -} diff --git a/whirl_prompt/src/structure.rs b/whirl_prompt/src/structure.rs deleted file mode 100644 index 4603d37..0000000 --- a/whirl_prompt/src/structure.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub struct Command { - pub keyword: String, - pub args: Vec, -} -impl Command { - pub fn to_line(&self) -> String { format!("{} {}", self.keyword, self.args.join(" ")) } -} diff --git a/whirl_server/.license_template b/whirl_server/.license_template deleted file mode 100644 index 1fda769..0000000 --- a/whirl_server/.license_template +++ /dev/null @@ -1,2 +0,0 @@ -// Copyleft (ɔ) {20\d{2}(-20\d{2})?} The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only diff --git a/whirl_server/Cargo.toml b/whirl_server/Cargo.toml deleted file mode 100644 index da9214d..0000000 --- a/whirl_server/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "whirl_server" -version = "0.1.0" -authors = ["Fuwn "] -edition = "2018" -description = "Whirl, an open-source WorldServer implementation in Rust." -documentation = "https://whirlsplash.org/docs/" -readme = "../README.rst" -homepage = "https://whirlsplash.org" -repository = "https://github.com/Whirlsplash/whirl" -license = "GPL-3.0-only" -# license-file = "LICENSE" -keywords = ["rust", "worldserver", "whirl", "whirlsplash"] -publish = false - -[dependencies] -# Logging -log = "0.4.14" - -# Utility -async-trait = "0.1.50" - -# Byte Manipulation -bytes = "1.0.1" -byteorder = "1.4.3" - -# Serialization -serde = "1.0.126" -serde_derive = "1.0.126" - -# TCP -tokio = { version = "1.6.0", features = ["full"] } -tokio-util = { version = "0.6.7", features = ["codec"] } -tokio-stream = "0.1.6" - -# Config -whirl_config = { path = "../whirl_config" } diff --git a/whirl_server/src/cmd/commands/action.rs b/whirl_server/src/cmd/commands/action.rs deleted file mode 100644 index 8d1fb0b..0000000 --- a/whirl_server/src/cmd/commands/action.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -// TODO: of2m-ify -// -// Of2m-ifying isn't much of a priority right now as the whole action ordeal -// hasn't been fully dissected yet. Once more is known about the inner working -// of actions, it will be of2m-ified. - -use bytes::{BufMut, BytesMut}; - -pub fn create_action() -> Vec { - let mut command = BytesMut::new(); - - command.put_slice(&[ - 0x01, 0x11, 0x00, 0x05, 0x54, 0x52, 0x41, 0x44, 0x45, 0x07, 0x26, 0x7c, 0x2b, 0x69, 0x6e, 0x76, - 0x3e, - ]); - - // Convert to vector and insert the length - let mut command_as_vec = command.to_vec(); - command_as_vec.insert(0, command.len() as u8 + 1); - - // Return bytes - command_as_vec -} diff --git a/whirl_server/src/cmd/commands/buddy_list.rs b/whirl_server/src/cmd/commands/buddy_list.rs deleted file mode 100644 index 931db52..0000000 --- a/whirl_server/src/cmd/commands/buddy_list.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::str::from_utf8; - -use bytes::{BufMut, BytesMut}; - -use crate::cmd::{ - constants::BUDDYLISTNOTIFY, - extendable::{Creatable, Parsable}, -}; - -#[derive(Clone)] -pub struct BuddyList { - pub buddy: String, - pub add: i8, -} -impl Parsable for BuddyList { - fn parse(data: Vec) -> Self { - Self { - buddy: from_utf8(&data[4..data[0] as usize - 1]) - .unwrap() - .to_string(), - - // Get the last byte - add: data[data[0] as usize - 1] as i8, - } - } -} -impl Creatable for BuddyList { - fn create(self) -> Vec { - let mut command = BytesMut::new(); - - // Header - command.put_u8(0x01); // ObjId - command.put_i8(BUDDYLISTNOTIFY as i8); // Type - - // Content - command.put_u8(self.buddy.len() as u8); // Buddy (name) length - command.put_slice(self.buddy.as_bytes()); // Buddy (name) - command.put_u8(self.add as u8); // "Is buddy logged on?" (?) - - let mut command_as_vec = command.to_vec(); - command_as_vec.insert(0, command.len() as u8 + 1); - - command_as_vec - } -} diff --git a/whirl_server/src/cmd/commands/mod.rs b/whirl_server/src/cmd/commands/mod.rs deleted file mode 100644 index 49758c2..0000000 --- a/whirl_server/src/cmd/commands/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod action; -pub mod buddy_list; -pub mod property; -pub mod redirect_id; -pub mod room_id_request; -pub mod subscribe_distance; -pub mod subscribe_room; -pub mod teleport; -pub mod text; diff --git a/whirl_server/src/cmd/commands/property/create.rs b/whirl_server/src/cmd/commands/property/create.rs deleted file mode 100644 index 40ec2be..0000000 --- a/whirl_server/src/cmd/commands/property/create.rs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -// TODO: of2m-ify? - -use whirl_config::Config; - -use crate::{ - cmd::constants::{PROPUPD, SESSINIT}, - net::{ - constants::{ - VAR_APPNAME, - VAR_CHANNEL, - VAR_ERROR, - VAR_EXTERNAL_HTTP_SERVER, - VAR_MAIL_DOMAIN, - VAR_PRIV, - VAR_PROTOCOL, - VAR_SCRIPT_SERVER, - VAR_SERIAL, - VAR_SERVERTYPE, - VAR_SMTP_SERVER, - VAR_UPDATETIME, - }, - converter::property_list_to_bytes, - structure::NetworkProperty, - }, -}; - -pub fn create_property_update_as_distributor() -> Vec { - property_list_to_bytes( - PROPUPD, - 0xFF, - vec![ - NetworkProperty { - prop_id: VAR_MAIL_DOMAIN, - value: "worlds3d.com".to_string(), - }, - NetworkProperty { - prop_id: VAR_SMTP_SERVER, - value: "mail.worlds.net:25".to_string(), - }, - NetworkProperty { - prop_id: VAR_SCRIPT_SERVER, - value: "http://www-dynamic.us.worlds.net/cgi-bin".to_string(), - }, - NetworkProperty { - prop_id: VAR_EXTERNAL_HTTP_SERVER, - value: "http://www-static.us.worlds.net".to_string(), - }, - NetworkProperty { - prop_id: VAR_SERVERTYPE, - value: "1".to_string(), - }, - NetworkProperty { - prop_id: VAR_PROTOCOL, - value: "24".to_string(), - }, - NetworkProperty { - prop_id: VAR_APPNAME, - value: Config::get().whirlsplash.worldsmaster_username, - }, - ], - ) -} - -pub fn create_property_update_as_hub() -> Vec { - property_list_to_bytes( - PROPUPD, - 0xFF, - vec![ - NetworkProperty { - prop_id: VAR_UPDATETIME, - value: "1000000".to_string(), - }, - NetworkProperty { - prop_id: VAR_MAIL_DOMAIN, - value: "worlds3d.com".to_string(), - }, - NetworkProperty { - prop_id: VAR_SMTP_SERVER, - value: "mail.worlds.net:25".to_string(), - }, - NetworkProperty { - prop_id: VAR_SCRIPT_SERVER, - value: "http://www-dynamic.us.worlds.net/cgi-bin".to_string(), - }, - NetworkProperty { - prop_id: VAR_EXTERNAL_HTTP_SERVER, - value: "http://www-static.us.worlds.net".to_string(), - }, - NetworkProperty { - prop_id: VAR_SERVERTYPE, - value: "3".to_string(), - }, - NetworkProperty { - prop_id: VAR_PROTOCOL, - value: "24".to_string(), - }, - NetworkProperty { - prop_id: VAR_APPNAME, - value: Config::get().whirlsplash.worldsmaster_username, - }, - ], - ) -} - -pub fn create_property_request_as_distributor() -> Vec { - property_list_to_bytes( - SESSINIT as i32, - 0x01, - vec![ - NetworkProperty { - prop_id: VAR_ERROR, - value: "0".to_string(), - }, - NetworkProperty { - prop_id: VAR_APPNAME, - value: Config::get().whirlsplash.worldsmaster_username, - }, - NetworkProperty { - prop_id: VAR_PROTOCOL, - value: "24".to_string(), - }, - NetworkProperty { - prop_id: VAR_SERVERTYPE, - value: "1".to_string(), - }, - NetworkProperty { - prop_id: VAR_SERIAL, - value: "DWLV000000000000".to_string(), - }, - NetworkProperty { - prop_id: VAR_PRIV, - value: "0".to_string(), - }, - NetworkProperty { - prop_id: VAR_CHANNEL, - value: "dimension-1".to_string(), - }, - ], - ) -} - -pub fn create_property_request_as_hub() -> Vec { - property_list_to_bytes( - SESSINIT as i32, - 0x01, - vec![ - NetworkProperty { - prop_id: VAR_ERROR, - value: "0".to_string(), - }, - NetworkProperty { - prop_id: VAR_SERVERTYPE, - value: "3".to_string(), - }, - NetworkProperty { - prop_id: VAR_UPDATETIME, - value: "1000000".to_string(), - }, - NetworkProperty { - prop_id: VAR_PROTOCOL, - value: "24".to_string(), - }, - ], - ) -} diff --git a/whirl_server/src/cmd/commands/property/mod.rs b/whirl_server/src/cmd/commands/property/mod.rs deleted file mode 100644 index 83b015b..0000000 --- a/whirl_server/src/cmd/commands/property/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod create; -pub mod parse; diff --git a/whirl_server/src/cmd/commands/property/parse.rs b/whirl_server/src/cmd/commands/property/parse.rs deleted file mode 100644 index 415d19f..0000000 --- a/whirl_server/src/cmd/commands/property/parse.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use crate::net::structure::NetworkProperty; - -pub fn find_property_in_property_list( - property_list: &[NetworkProperty], - property: i32, -) -> &NetworkProperty { - property_list - .iter() - .find(|i| i.prop_id == property) - .unwrap() -} diff --git a/whirl_server/src/cmd/commands/redirect_id.rs b/whirl_server/src/cmd/commands/redirect_id.rs deleted file mode 100644 index 8f56c86..0000000 --- a/whirl_server/src/cmd/commands/redirect_id.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use bytes::{BufMut, BytesMut}; -use whirl_config::Config; - -use crate::cmd::{constants::REDIRID, extendable::Creatable}; - -#[derive(Debug)] -pub struct RedirectId { - pub room_name: String, - pub room_number: i8, -} -impl Creatable for RedirectId { - fn create(self) -> Vec { - let mut command = BytesMut::new(); - - // Header - command.put_u8(0x01); // ObjId - command.put_u8(REDIRID as u8); // Type - - // Content - command.put_u8(self.room_name.len() as u8); // Room name length - command.put_slice(self.room_name.as_bytes()); // Room name - // command.put_u8(0x00); // Unimplemented byte (?) - // command.put_u8(room_id); // Room ID - command.put_u16(self.room_number as u16); // Room ID - - // IP - for byte in Config::get().whirlsplash.ip.split('.') { - command.put_u8(byte.parse::().unwrap()); - } - command.put_u16(Config::get().hub.port as u16); // Port - - // Length - let mut command_as_vec = command.to_vec(); - command_as_vec.insert(0, command.len() as u8 + 1); - - // Return - command_as_vec - } -} diff --git a/whirl_server/src/cmd/commands/room_id_request.rs b/whirl_server/src/cmd/commands/room_id_request.rs deleted file mode 100644 index cf507fa..0000000 --- a/whirl_server/src/cmd/commands/room_id_request.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::str::from_utf8; - -use crate::cmd::extendable::Parsable; - -#[derive(Debug)] -pub struct RoomIdRequest { - pub room_name: String, -} -impl Parsable for RoomIdRequest { - fn parse(data: Vec) -> Self { - Self { - room_name: from_utf8(&data[4..data[0] as usize]).unwrap().to_string(), - } - } -} diff --git a/whirl_server/src/cmd/commands/subscribe_distance.rs b/whirl_server/src/cmd/commands/subscribe_distance.rs deleted file mode 100644 index d5cbcf6..0000000 --- a/whirl_server/src/cmd/commands/subscribe_distance.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use byteorder::{BigEndian, ReadBytesExt}; -use bytes::{Buf, BytesMut}; - -use crate::cmd::extendable::Parsable; - -#[derive(Debug)] -pub struct SubscribeDistance { - pub distance: i16, - pub room_number: i16, -} -impl Parsable for SubscribeDistance { - fn parse(data: Vec) -> Self { - // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 - let mut data = BytesMut::from(data.as_slice()).reader(); - - Self { - distance: data.read_i16::().unwrap(), - room_number: data.read_i16::().unwrap(), - } - } -} diff --git a/whirl_server/src/cmd/commands/subscribe_room.rs b/whirl_server/src/cmd/commands/subscribe_room.rs deleted file mode 100644 index 9e7d732..0000000 --- a/whirl_server/src/cmd/commands/subscribe_room.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use byteorder::{BigEndian, ReadBytesExt}; -use bytes::{Buf, BytesMut}; - -use crate::cmd::extendable::Parsable; - -#[derive(Debug)] -pub struct SubscribeRoom { - pub room_number: i8, - pub x: f32, - pub y: f32, - pub z: f32, - pub distance: f32, -} -impl Parsable for SubscribeRoom { - fn parse(data: Vec) -> Self { - // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 - let mut data = BytesMut::from(data.as_slice()).reader(); - - Self { - room_number: data.read_i16::().unwrap() as i8, - x: data.read_i16::().unwrap() as i8 as f32, - y: data.read_i16::().unwrap() as i8 as f32, - z: data.read_i16::().unwrap() as i8 as f32, - distance: data.read_i16::().unwrap() as i8 as f32, // + 100 - } - } -} diff --git a/whirl_server/src/cmd/commands/teleport.rs b/whirl_server/src/cmd/commands/teleport.rs deleted file mode 100644 index ef8f6b2..0000000 --- a/whirl_server/src/cmd/commands/teleport.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use byteorder::{BigEndian, ReadBytesExt}; -use bytes::{Buf, BytesMut}; - -use crate::cmd::extendable::Parsable; - -#[derive(Debug)] -pub struct Teleport { - pub room_id: i8, - pub exit_type: u8, - pub entry_type: u8, - pub x: f32, // i16 - pub y: f32, - pub z: f32, - pub direction: f32, -} -impl Parsable for Teleport { - fn parse(data: Vec) -> Self { - // https://stackoverflow.com/questions/41034635/how-do-i-convert-between-string-str-vecu8-and-u8 - let mut data = BytesMut::from(data.as_slice()).reader(); - - Self { - room_id: data.read_u16::().unwrap() as i8, - exit_type: data.read_u8().unwrap(), - entry_type: data.read_u8().unwrap(), - x: data.read_i16::().unwrap() as f32, - y: data.read_i16::().unwrap() as f32, - z: data.read_i16::().unwrap() as f32, - direction: data.read_i16::().unwrap() as f32, - } - } -} diff --git a/whirl_server/src/cmd/commands/text.rs b/whirl_server/src/cmd/commands/text.rs deleted file mode 100644 index 2bf7e17..0000000 --- a/whirl_server/src/cmd/commands/text.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::str::from_utf8; - -use bytes::{BufMut, BytesMut}; - -use crate::cmd::{ - constants::TEXT, - extendable::{Creatable, ParsableWithArguments}, -}; - -pub struct Text { - pub sender: String, - pub content: String, -} -impl Creatable for Text { - fn create(self) -> Vec { - let mut command = BytesMut::new(); - - // Header - command.put_u8(0x01); - command.put_i8(TEXT as i8); - - // Content - // TODO: Find a way to parse ObjIds. - // - // The below byte is suspected to be the sender's short ObjId. - command.put_i8(0x00); - - command.put_u8(self.sender.len() as u8); - command.put_slice(self.sender.as_bytes()); - command.put_u8(self.content.len() as u8); - command.put_slice(self.content.as_bytes()); - - // Convert to vector and insert the length - let mut command_as_vec = command.to_vec(); - command_as_vec.insert(0, command.len() as u8 + 1); - - // Return bytes - command_as_vec - } -} -impl ParsableWithArguments for Text { - /// The first and only element of `args` *should* be the username of the - /// sender. - /// - /// There isn't anything currently stopping someone from passing some other - /// value so that might be annoying at times. - /// - /// Realistically, this method is mostly static so the username will *always* - /// be passed properly unless someone intentionally commits breaking changes - /// on purpose regarding what is passed to to this method where called. - /// - /// It would be neat to have some sort of ability to statically check if the - /// `args` argument contains x number of elements at compile time or - /// something of the sort but the Rust RFC is probably not focused on that. - /// - /// So, right now, trust is in the developers' hands to make sure to pass the - /// right -- number -- of elements to `args`. - fn parse(data: Vec, args: &[&str]) -> Self { - Self { - sender: args[0].to_string(), - content: from_utf8(&data[6..]).unwrap().to_string(), - } - } -} diff --git a/whirl_server/src/cmd/constants.rs b/whirl_server/src/cmd/constants.rs deleted file mode 100644 index 22d29c1..0000000 --- a/whirl_server/src/cmd/constants.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub const LONGLOC: i32 = 1; -pub const STATE: i32 = 2; -pub const PROP: i32 = 3; -pub const SHORTLOC: i32 = 4; -pub const ROOMCHNG: i32 = 5; -pub const SESSINIT: i32 = 6; -pub const SESSEXIT: i32 = 7; -pub const APPINIT: i32 = 8; -pub const PROPREQ: i32 = 10; -pub const DISAPPR: i32 = 11; -pub const APPRACTR: i32 = 12; -pub const REGOBJID: i32 = 13; -pub const TEXT: i32 = 14; -pub const PROPSET: i32 = 15; -pub const PROPUPD: i32 = 16; -pub const WHISPER: i32 = 17; -pub const TELEPORT: i32 = 18; -pub const ROOMIDRQ: i32 = 20; -pub const ROOMID: i32 = 21; -pub const SUBSCRIB: i32 = 22; -pub const UNSUBSCR: i32 = 23; -pub const SUB_DIST: i32 = 24; // SUB-DIST -pub const REDIRECT: i32 = 25; -pub const REDIRID: i32 = 26; -pub const FINGREQ: i32 = 27; -pub const FINGREP: i32 = 28; -pub const BUDDYLISTUPDATE: i32 = 29; -pub const BUDDYLISTNOTIFY: i32 = 30; -pub const CHANNEL: i32 = 31; diff --git a/whirl_server/src/cmd/extendable.rs b/whirl_server/src/cmd/extendable.rs deleted file mode 100644 index e6f3c2b..0000000 --- a/whirl_server/src/cmd/extendable.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub trait Parsable { - fn parse(data: Vec) -> Self; -} - -pub trait Creatable { - fn create(self) -> Vec; -} - -/// Having to do this makes me with there was operator overloading in Rust. -/// -/// I *could* do this with a macro but since Text is the only struct that -/// implements this trait, it shouldn't be that big of a deal. -pub trait ParsableWithArguments { - fn parse(data: Vec, args: &[&str]) -> Self; -} diff --git a/whirl_server/src/cmd/mod.rs b/whirl_server/src/cmd/mod.rs deleted file mode 100644 index ef91de7..0000000 --- a/whirl_server/src/cmd/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod commands; - -pub mod constants; -pub mod extendable; -mod set_parser; -mod structure; diff --git a/whirl_server/src/cmd/set_parser.rs b/whirl_server/src/cmd/set_parser.rs deleted file mode 100644 index 9d6a4b3..0000000 --- a/whirl_server/src/cmd/set_parser.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use crate::cmd::structure::Command; - -/// Iterate over a command set in the from of bytes and return a list of -/// human-readable commands. -fn _parse_command_set(mut data: Vec) -> Vec { - let mut command_set = vec![]; - - // Iterate over all commands - loop { - // Check if any commands are present - if data.len() <= 2 { - break; - } - if data[0] == 0 { - break; - } - - let command_length = data[0]; - let mut command = Command { - length: command_length as i32, - obj_id: data[1] as i32, - id: data[2] as i32, - body: vec![], - }; - if command.length > 3 { - command.body = data[3..].to_owned(); - } - command_set.push(command); - - // Remove current command from the command set - data = data[command_length as usize..].to_vec(); - } - - // Return the human-readable command set - command_set -} diff --git a/whirl_server/src/cmd/structure.rs b/whirl_server/src/cmd/structure.rs deleted file mode 100644 index 23e91ca..0000000 --- a/whirl_server/src/cmd/structure.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub struct Command { - pub length: i32, - pub obj_id: i32, - pub id: i32, - pub body: Vec, -} -impl Command { - pub fn _new() -> Self { Command::default() } -} -impl Default for Command { - fn default() -> Self { - Command { - length: 0, - obj_id: 0, - id: 0, - body: vec![], - } - } -} diff --git a/whirl_server/src/distributor.rs b/whirl_server/src/distributor.rs deleted file mode 100644 index 22b698b..0000000 --- a/whirl_server/src/distributor.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -//! The distributor functions as bare-minimal -//! [AutoServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#AutoServer). -//! -//! It intercepts a client and distributes it to a -//! [RoomServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#RoomServer). -//! -//! This is not meant to be a high performant section of code as the distributor -//! is only meant to handle the initial and brief session initialization of the -//! client. - -use std::{error::Error, net::SocketAddr, sync::Arc}; - -use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex}; -use tokio_stream::StreamExt; -use tokio_util::codec::{BytesCodec, Decoder}; -use whirl_config::Config; - -use crate::{ - cmd::{ - commands::{ - action::create_action, - buddy_list::BuddyList, - property::{ - create::{create_property_request_as_distributor, create_property_update_as_distributor}, - parse::find_property_in_property_list, - }, - redirect_id::RedirectId, - room_id_request::RoomIdRequest, - text::Text, - }, - constants::*, - extendable::{Creatable, Parsable}, - }, - interaction::{peer::Peer, shared::Shared}, - net::{constants::VAR_USERNAME, property_parser::parse_network_property}, - packet_parser::parse_commands_from_packet, - Server, -}; - -pub struct Distributor; -#[async_trait] -impl Server for Distributor { - async fn handle( - state: Arc>, - stream: TcpStream, - _address: SocketAddr, - count: usize, - ) -> Result<(), Box> { - let bytes = BytesCodec::new().framed(stream); - let mut peer = Peer::new(state.clone(), bytes, count.to_string()).await?; - let mut room_ids = vec![]; - let mut username = String::from("unknown"); - - loop { - tokio::select! { - Some(msg) = peer.rx.recv() => { - peer.bytes.get_mut().write_all(&msg).await?; - } - result = peer.bytes.next() => match result { - Some(Ok(msg)) => { - for msg in parse_commands_from_packet(msg) { - match msg.get(2).unwrap().to_owned() as i32 { - PROPREQ => { - debug!("received property request from client"); - - peer.bytes.get_mut() - .write_all(&create_property_update_as_distributor()).await?; - trace!("sent property update to client"); - } - SESSINIT => { - username = (&*find_property_in_property_list( - &parse_network_property(msg[3..].to_vec()), - VAR_USERNAME, - ).value).to_string(); - - debug!("received session initialization from {}", username); - - peer.bytes.get_mut() - .write_all(&create_property_request_as_distributor()).await?; - trace!("sent property request to {}", username); - } - PROPSET => { - debug!("received property set from {}", username); - - peer.bytes.get_mut() - .write_all(&Text { - sender: Config::get().whirlsplash.worldsmaster_username, - content: Config::get().distributor.worldsmaster_greeting, - }.create()).await?; - peer.bytes.get_mut() - .write_all(&create_action()).await?; - trace!("sent text to {}", username); - } - BUDDYLISTUPDATE => { - let buddy = BuddyList::parse(msg.to_vec()); - debug!("received buddy list update from {}: {}", username, buddy.buddy); - peer.bytes.get_mut().write_all(&buddy.clone().create()).await?; - trace!("sent buddy list notify to {}: {}", username, buddy.buddy); - } - ROOMIDRQ => { - let room = RoomIdRequest::parse(msg.to_vec()); - debug!("received room id request from {}: {}", username, &room.room_name); - - let room_id; - if !room_ids.contains(&room.room_name) { - room_ids.push((&*room.room_name).to_string()); - room_id = room_ids.iter().position(|r| r == &room.room_name).unwrap(); - trace!("inserted room: {}", room.room_name); - } else { - let position = room_ids.iter().position(|r| r == &room.room_name).unwrap(); - trace!("found room: {}", room.room_name); - room_id = position; - } - - peer.bytes.get_mut().write_all(&RedirectId { - room_name: (&*room.room_name).to_string(), - room_number: room_id as i8, - }.create()).await?; - trace!("sent redirect id to {}: {}", username, room.room_name); - } - SESSEXIT => { - debug!("received session exit from {}", username); break; - } - _ => (), - } - } - } - Some(Err(e)) => { - error!("error while processing message (s): {}", e); break; - } - None => break, - } - } - } - - // Deregister client - debug!("de-registering client"); - { - state.lock().await.peers.remove(&count.to_string()); - } - debug!("de-registered client"); - - Ok(()) - } -} diff --git a/whirl_server/src/hub.rs b/whirl_server/src/hub.rs deleted file mode 100644 index 6de4bca..0000000 --- a/whirl_server/src/hub.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -//! The hub functions as a -//! [RoomServer](http://dev.worlds.net/private/GammaDocs/WorldServer.html#AutoServer). -//! -//! The RoomServer is responsible for handling just about every request from the -//! client after they have been redirected to a room (hub). - -use std::{error::Error, net::SocketAddr, sync::Arc}; - -use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex}; -use tokio_stream::StreamExt; -use tokio_util::codec::{BytesCodec, Decoder}; -use whirl_config::Config; - -use crate::{ - cmd::{ - commands::{ - action::create_action, - buddy_list::BuddyList, - property::{ - create::{create_property_request_as_hub, create_property_update_as_hub}, - parse::find_property_in_property_list, - }, - subscribe_distance::SubscribeDistance, - subscribe_room::SubscribeRoom, - teleport::Teleport, - text::Text, - }, - constants::*, - extendable::{Creatable, Parsable, ParsableWithArguments}, - }, - interaction::{peer::Peer, shared::Shared}, - net::{constants::VAR_USERNAME, property_parser::parse_network_property}, - packet_parser::parse_commands_from_packet, - Server, -}; - -pub struct Hub; -#[async_trait] -impl Server for Hub { - async fn handle( - state: Arc>, - stream: TcpStream, - _address: SocketAddr, - count: usize, - ) -> Result<(), Box> { - let bytes = BytesCodec::new().framed(stream); - let mut peer = Peer::new(state.clone(), bytes, count.to_string()).await?; - // let mut room_ids = vec![]; - let mut username = String::from("unknown"); - - loop { - tokio::select! { - Some(msg) = peer.rx.recv() => { - // trace!("got peer activity: {:?}", &msg); - peer.bytes.get_mut().write_all(&msg).await?; - } - result = peer.bytes.next() => match result { - Some(Ok(msg)) => { - // trace!("got some bytes: {:?}", &msg); - for msg in parse_commands_from_packet(msg) { - match msg.get(2).unwrap().to_owned() as i32 { - PROPREQ => { - debug!("received property request from client"); - - peer.bytes.get_mut() - .write_all(&create_property_update_as_hub()).await?; - trace!("sent property update to client"); - } - SESSINIT => { - username = (&*find_property_in_property_list( - &parse_network_property(msg[3..].to_vec()), - VAR_USERNAME, - ).value).to_string(); - - debug!("received session initialization from {}", username); - - peer.bytes.get_mut() - .write_all(&create_property_request_as_hub()).await?; - trace!("sent property request to {}", username); - } - PROPSET => { - debug!("received property set from {}", username); - - peer.bytes.get_mut() - .write_all(&Text { - sender: Config::get().whirlsplash.worldsmaster_username, - content: Config::get().distributor.worldsmaster_greeting, - }.create()).await?; - peer.bytes.get_mut() - .write_all(&create_action()).await?; - trace!("sent text to {}", username); - } - BUDDYLISTUPDATE => { - let buddy = BuddyList::parse(msg.to_vec()); - debug!("received buddy list update from {}: {}", username, buddy.buddy); - peer.bytes.get_mut().write_all(&buddy.clone().create()).await?; - trace!("sent buddy list notify to {}: {}", username, buddy.buddy); - } - // TODO: Figure out if this is actually even needed. - // ROOMIDRQ => { - // let room = RoomIdRequest::parse(msg.to_vec()); - // debug!("received room id request from {}: {}", username, room.room_name); - // trace!("{:?}", create_room_id_request(&room.room_name, 0x00)); - // } - SESSEXIT => { - debug!("received session exit from {}", username); break; - } - TEXT => { - let text = Text::parse(msg.to_vec(), &[&username]); - debug!("received text from {}:{}", username, text.content); - - { - state.lock().await.broadcast(&Text { - sender: (&*username).to_string(), - content: text.content, - }.create()).await; - } - debug!("broadcasted text to hub"); - } - SUBSCRIB => { - let subscribe_room = SubscribeRoom::parse(msg[3..].to_vec()); - debug!("received subscribe room from {}: {:?}", - username, subscribe_room); - } - SUB_DIST => { - let subscribe_distance = SubscribeDistance::parse(msg[3..].to_vec()); - debug!("received subscribe distance from {}: {:?}", - username, subscribe_distance); - } - TELEPORT => { - let teleport = Teleport::parse(msg[3..].to_vec()); - debug!("received teleport from {}: {:?}", - username, teleport); - } - _ => (), - } - } - } - Some(Err(e)) => { - error!("error while processing message (s): {}", e); break; - } - None => { - trace!("nothing"); break; - }, - } - } - } - - // Deregister client - debug!("de-registering client"); - { - state.lock().await.peers.remove(&count.to_string()); - } - debug!("de-registered client"); - - Ok(()) - } -} diff --git a/whirl_server/src/interaction/mod.rs b/whirl_server/src/interaction/mod.rs deleted file mode 100644 index c85e09d..0000000 --- a/whirl_server/src/interaction/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod peer; -pub mod shared; diff --git a/whirl_server/src/interaction/peer.rs b/whirl_server/src/interaction/peer.rs deleted file mode 100644 index 38c02c5..0000000 --- a/whirl_server/src/interaction/peer.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::sync::Arc; - -use tokio::{ - net::TcpStream, - sync::{mpsc, Mutex}, -}; -use tokio_util::codec::{BytesCodec, Framed}; - -use crate::{interaction::shared::Shared, types::Rx}; - -pub struct Peer { - pub bytes: Framed, - pub rx: Rx, -} -impl Peer { - pub async fn new( - state: Arc>, - bytes: Framed, - username: String, - ) -> std::io::Result { - let (tx, rx) = mpsc::unbounded_channel(); - state.lock().await.peers.insert(username, tx); - - Ok(Peer { - bytes, - rx, - }) - } - - pub async fn _change_username( - self, - state: Arc>, - username: &str, - new_username: &str, - ) { - // Remove peer from peers - { - state.lock().await.peers.remove(username); - } - - // Add the peer back with the new username - Self::new(state, self.bytes, new_username.to_string()) - .await - .unwrap(); - } -} diff --git a/whirl_server/src/interaction/shared.rs b/whirl_server/src/interaction/shared.rs deleted file mode 100644 index c2ee671..0000000 --- a/whirl_server/src/interaction/shared.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::collections::HashMap; - -use bytes::BytesMut; - -use crate::types::Tx; - -pub struct Shared { - pub peers: HashMap, -} -impl Shared { - pub fn new() -> Self { - Shared { - peers: HashMap::new(), - } - } - - pub async fn broadcast(&mut self, message: &[u8]) { - for peer in self.peers.iter_mut() { - peer.1.send(BytesMut::from(message)).unwrap(); - } - } -} -impl Default for Shared { - fn default() -> Self { Self::new() } -} diff --git a/whirl_server/src/lib.rs b/whirl_server/src/lib.rs deleted file mode 100644 index 9c3e9d0..0000000 --- a/whirl_server/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -#![feature( - type_ascription, - hash_set_entry, - type_name_of_val, - decl_macro, - proc_macro_hygiene -)] -#![warn(rust_2018_idioms)] -#![recursion_limit = "128"] - -#[macro_use] -extern crate log; -#[macro_use] -extern crate async_trait; - -pub mod cmd; -mod interaction; -pub mod net; - -pub mod distributor; -pub mod hub; -mod packet_parser; -mod types; - -use std::{error::Error, fmt, net::SocketAddr, sync::Arc}; - -use tokio::{ - net::{TcpListener, TcpStream}, - sync::Mutex, -}; - -use crate::interaction::shared::Shared; - -#[derive(Debug)] -pub enum ServerType { - AnonRoomServer, - AnonUserServer, - AutoServer, - RoomServer, - UserServer, -} -// https://stackoverflow.com/a/32712140/14452787 -impl fmt::Display for ServerType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } -} - -#[async_trait] -pub trait Server { - async fn listen(address: &str, server_type: ServerType) -> Result<(), Box> { - let listener = TcpListener::bind(address).await?; - let state = Arc::new(Mutex::new(Shared::new())); - let mut counter = 0; - - info!( - "server of type {} now listening at {}", - server_type.to_string(), - address - ); - - loop { - let (stream, address) = listener.accept().await?; - counter += 1; - let state = Arc::clone(&state); - - debug!("accepted client at {}", address); - - tokio::spawn(async move { - if let Err(e) = Self::handle(state, stream, address, counter).await { - error!("an error occurred: {}", e); - } - - if std::env::var("EXIT_ON_CLIENT_DISCONNECT").unwrap_or_else(|_| "false".to_string()) - == "true" - { - std::process::exit(0); - } - }); - } - } - - async fn handle( - state: Arc>, - stream: TcpStream, - _address: SocketAddr, - count: usize, - ) -> Result<(), Box>; -} diff --git a/whirl_server/src/net/constants.rs b/whirl_server/src/net/constants.rs deleted file mode 100644 index 169b461..0000000 --- a/whirl_server/src/net/constants.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub const VAR_PROTOCOL_VERSION: i32 = 24; -pub const STATECMD: i32 = 2; -pub const MAXCMD: i32 = 255; -pub const CURRENT_ROOM: i32 = 253; -pub const CLIENT: i32 = 1; -pub const CO: i32 = 254; -pub const PO: i32 = 255; -pub const VAR_APPNAME: i32 = 1; -pub const VAR_USERNAME: i32 = 2; -pub const VAR_PROTOCOL: i32 = 3; -pub const VAR_ERROR: i32 = 4; -pub const VAR_CHANNEL: i32 = 5; -pub const VAR_BITMAP: i32 = 5; -pub const VAR_PASSWORD: i32 = 6; -pub const VAR_AVATARS: i32 = 7; -pub const VAR_UPDATETIME: i32 = 8; -pub const VAR_CLIENT: i32 = 9; -pub const VAR_SERIAL: i32 = 10; -pub const VAR_EMAIL: i32 = 11; -pub const VAR_LOGONOFF: i32 = 12; -pub const VAR_DURATION: i32 = 13; -pub const VAR_GUEST: i32 = 14; -pub const VAR_SERVERTYPE: i32 = 15; -pub const VAR_VIZCARD: i32 = 16; -pub const VAR_NEW_PASSWD: i32 = 20; -pub const VAR_PRIV: i32 = 22; -pub const VAR_ASLEEP: i32 = 23; -pub const VAR_EXTERNAL_HTTP_SERVER: i32 = 24; -pub const VAR_SCRIPT_SERVER: i32 = 25; -pub const VAR_SMTP_SERVER: i32 = 26; -pub const VAR_MAIL_DOMAIN: i32 = 27; -pub const VAR_NEW_USERNAME: i32 = 28; -pub const VAR_INTERNAL_HTTP_SERVER: i32 = 29; -pub const VAR_INVENTORY: i32 = 32; -pub const ACK: i32 = 0; -pub const NAK_BAD_USER: i32 = 1; -pub const NAK_MAX_ORDINARY: i32 = 2; -pub const NAK_MAX_PRIORITY: i32 = 3; -pub const NAL_BAD_WORLD: i32 = 4; -pub const NAK_FATAIL: i32 = 5; -pub const NAK_BAD_PROTOCOL: i32 = 6; -pub const NAK_BAD_CLIENTSW: i32 = 7; -pub const NAK_BAD_ROOM: i32 = 8; -pub const NAK_BAD_SERIAL: i32 = 9; -pub const NAK_TAKEN_SERIAL: i32 = 10; -pub const NAK_TAKEN_USER: i32 = 11; -pub const NAK_NO_SUCH_USER: i32 = 12; -pub const NAK_BAD_PASSWORD: i32 = 13; -pub const NAK_BAD_ACCOUNT: i32 = 14; -pub const NAK_NOT_LOGGEDON: i32 = 15; -pub const NAK_BAD_IPADDRESS: i32 = 16; -pub const NAK_LOGGEDON: i32 = 17; -pub const NAK_CRYPT_METHOD: i32 = 18; -pub const NAK_CRYPT_ERROR: i32 = 19; -pub const NAK_SESSIONINIT: i32 = 20; -pub const NAK_ROOM_FULL: i32 = 21; -pub const NAK_SHUTDOWN: i32 = 100; -pub const NAK_WRITE_ERROR: i32 = 101; -pub const NAK_READ_ERROR: i32 = 102; -pub const NAK_UNEXPECTED: i32 = 103; -pub const NAK_CONNECTION: i32 = 104; -pub const NAK_IOSTREAMS: i32 = 105; -pub const NAK_TIMEOUT: i32 = 106; -pub const NAK_UNREACHABLE: i32 = 107; -pub const STATUS_CONNECTED: i32 = 200; -pub const STATUS_DETACHING: i32 = 201; -pub const STATUS_WILLRETRY: i32 = 202; -pub const STATUS_DISCONNECTED: i32 = 203; -pub const STATUS_DEAD: i32 = 204; -pub const STATUS_OFFLINE: i32 = 205; -pub const STATUS_GALAXY_ONLINE: i32 = 206; -pub const STATUS_GALAXY_OFFLINE: i32 = 206; -pub const PROPFLAG_BINARY: i32 = 16; -pub const PROPFLAG_FINGER: i32 = 32; -pub const PROPFLAG_AUTOUPDATE: i32 = 64; -pub const PROPFLAG_DBSTORE: i32 = 128; -pub const PROPACCESS_POSSESS: i32 = 1; -pub const PROPACCESS_PRIVATE: i32 = 2; -pub const SERVER_UNKNOWN: i32 = 0; -pub const USER_SERVER_DB: i32 = 1; -pub const USER_SERVER_ANON: i32 = 2; -pub const ROOM_SERVER_US: i32 = 3; -pub const ROOM_SERVER_ANON: i32 = 4; -pub const PRIV_NONE: i32 = 0; -pub const PRIV_BUILD: i32 = 1; -pub const PRIV_BROADCAST: i32 = 2; -pub const PRIV_PROPERTY: i32 = 4; -pub const PRIV_VIP: i32 = 8; -pub const PRIV_VIP2: i32 = 16; -pub const PRIV_SPECIALGUEST: i32 = 64; diff --git a/whirl_server/src/net/converter.rs b/whirl_server/src/net/converter.rs deleted file mode 100644 index c976dff..0000000 --- a/whirl_server/src/net/converter.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use bytes::{BufMut, BytesMut}; - -use crate::{ - cmd::constants::PROPUPD, - net::{ - constants::{PROPACCESS_POSSESS, PROPFLAG_DBSTORE}, - structure::NetworkProperty, - }, -}; - -pub fn property_list_to_bytes( - command_id: i32, - obj_id: i32, - mut property_list: Vec, -) -> Vec { - let mut command = BytesMut::new(); - - // Iterate over all network properties - loop { - // Check if there are any properties left - trace!("props left: {}", property_list.len()); - if property_list.is_empty() { - break; - } - - let property = &property_list[0]; // Property we are currently iterating over - trace!("current prop: {}:{}", property.prop_id, property.value); - - command.put_u8(property.prop_id as u8); // Property ID - - // NOTE: THIS IS SUPER BAD DO NOT DO THIS! But it works! - if command_id == PROPUPD { - command.put_u8(PROPFLAG_DBSTORE as u8); // Flag (s) - command.put_u8(PROPACCESS_POSSESS as u8); // Access - } - - command.put_u8(property.value.len() as u8); // Property UTF-8 Length - command.put_slice(property.value.as_bytes()); // Property UTF-8 - - property_list.reverse(); - property_list.pop(); - property_list.reverse(); - } - - // Convert to vector and insert the header - let mut command_as_vec = command.to_vec(); - - command_as_vec.insert(0, command_id as u8); // Command ID - command_as_vec.insert(0, obj_id as u8); // ObjId - command_as_vec.insert(0, command.len() as u8 + 3); // Data length - - // Return bytes - command_as_vec -} diff --git a/whirl_server/src/net/mod.rs b/whirl_server/src/net/mod.rs deleted file mode 100644 index afa45c8..0000000 --- a/whirl_server/src/net/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub mod constants; -pub mod converter; -pub mod property_parser; -pub mod structure; diff --git a/whirl_server/src/net/property_parser.rs b/whirl_server/src/net/property_parser.rs deleted file mode 100644 index f86b767..0000000 --- a/whirl_server/src/net/property_parser.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use std::str::from_utf8; - -use crate::net::structure::NetworkProperty; - -/// Iterate over a network property in the form of bytes and return a list of -/// human-readable properties. -pub fn parse_network_property(mut data: Vec) -> Vec { - let mut property_list = vec![]; - - // Iterate over all network properties - loop { - // Check if any commands are present - if data.len() <= 2 { - break; - } - trace!("iteration: {:?}", data); - // if data[0] == 0 { - // break; - // } - - let property_length = data[1] + 2; - property_list.push(NetworkProperty { - prop_id: data[0] as i32, - value: from_utf8(&data[2..data[1] as usize + 2]) - .unwrap() - .to_string(), - }); - - // Remove current property from the network property - data = data[property_length as usize..].to_vec(); - } - - // Return the human-readable network property - property_list -} diff --git a/whirl_server/src/net/structure.rs b/whirl_server/src/net/structure.rs deleted file mode 100644 index 1fb1051..0000000 --- a/whirl_server/src/net/structure.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -pub struct NetworkProperty { - pub prop_id: i32, - pub value: String, -} -impl NetworkProperty { - pub fn new() -> Self { NetworkProperty::default() } -} -impl Default for NetworkProperty { - fn default() -> Self { - NetworkProperty { - prop_id: 0, - value: "".to_string(), - } - } -} diff --git a/whirl_server/src/packet_parser.rs b/whirl_server/src/packet_parser.rs deleted file mode 100644 index bfeba9e..0000000 --- a/whirl_server/src/packet_parser.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use bytes::BytesMut; - -/// Read all commands from the given buffer. -/// -/// # Process -/// 1. Get a command from `buffer` based on first byte. -/// 2. Push command to `commands`. -/// 3. Remove command from `buffer`. -/// 4. Iterate and do this for all commands within `buffer`. -pub fn parse_commands_from_packet(mut buffer: BytesMut) -> Vec { - let mut commands: Vec = Vec::new(); - trace!("initial buffer: {:?}, length: {}", buffer, buffer.len()); - - let data_length = buffer.get(0).unwrap().to_owned() as usize; - if buffer.len() > data_length { - loop { - trace!("loop: {:?}, length: {}", buffer, buffer.len()); - let command_length = buffer.get(0).unwrap().to_owned() as usize; - commands.push(BytesMut::from(buffer.get(0..command_length).unwrap())); - - // Remove command from buffer - buffer = buffer.split_off(command_length); - - // Check if any more commands are present - if buffer.is_empty() { - break; - } - } - } else { - // There will always be at least one command, push it. - commands.push(BytesMut::from(buffer.get(0..data_length).unwrap())); - } - - commands // Return command (s) -} diff --git a/whirl_server/src/types.rs b/whirl_server/src/types.rs deleted file mode 100644 index 3d49752..0000000 --- a/whirl_server/src/types.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective -// SPDX-License-Identifier: GPL-3.0-only - -use bytes::BytesMut; -use tokio::sync::mpsc; - -pub type Tx = mpsc::UnboundedSender; -pub type Rx = mpsc::UnboundedReceiver; -- cgit v1.2.3