diff options
| author | Fuwn <[email protected]> | 2021-05-20 17:05:59 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2021-05-20 17:05:59 -0700 |
| commit | 9e2121baf98b6fdc15cde6c387a7845a0b3f95d6 (patch) | |
| tree | 15460f59799a9f655ac5b213e4b8a8903d1e57e4 /crates/whirl_server/src | |
| parent | feat(readme): add sqlfluff as a dev dep (diff) | |
| download | whirl-9e2121baf98b6fdc15cde6c387a7845a0b3f95d6.tar.xz whirl-9e2121baf98b6fdc15cde6c387a7845a0b3f95d6.zip | |
refactor(global): move crates around, stricter module isolation
Diffstat (limited to 'crates/whirl_server/src')
30 files changed, 1348 insertions, 0 deletions
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<u8> { + 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<u8>) -> 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<u8> { + 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<u8> { + 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<u8> { + 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<u8> { + 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<u8> { + 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<u8> { + 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::<u8>().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<u8>) -> 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<u8>) -> 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::<BigEndian>().unwrap(), + room_number: data.read_i16::<BigEndian>().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<u8>) -> 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::<BigEndian>().unwrap() as i8, + x: data.read_i16::<BigEndian>().unwrap() as i8 as f32, + y: data.read_i16::<BigEndian>().unwrap() as i8 as f32, + z: data.read_i16::<BigEndian>().unwrap() as i8 as f32, + distance: data.read_i16::<BigEndian>().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<u8>) -> 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::<BigEndian>().unwrap() as i8, + exit_type: data.read_u8().unwrap(), + entry_type: data.read_u8().unwrap(), + x: data.read_i16::<BigEndian>().unwrap() as f32, + y: data.read_i16::<BigEndian>().unwrap() as f32, + z: data.read_i16::<BigEndian>().unwrap() as f32, + direction: data.read_i16::<BigEndian>().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<u8> { + 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<u8>, 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<u8>) -> Self; +} + +pub trait Creatable { + fn create(self) -> Vec<u8>; +} + +/// 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<u8>, 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<u8>) -> Vec<Command> { + 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<u8>, +} +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<Mutex<Shared>>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box<dyn Error>> { + 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<Mutex<Shared>>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box<dyn Error>> { + 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<TcpStream, BytesCodec>, + pub rx: Rx, +} +impl Peer { + pub async fn new( + state: Arc<Mutex<Shared>>, + bytes: Framed<TcpStream, BytesCodec>, + username: String, + ) -> std::io::Result<Peer> { + 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<Mutex<Shared>>, + 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<String, Tx>, +} +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<dyn Error>> { + 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<Mutex<Shared>>, + stream: TcpStream, + _address: SocketAddr, + count: usize, + ) -> Result<(), Box<dyn Error>>; +} 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<NetworkProperty>, +) -> Vec<u8> { + 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<u8>) -> Vec<NetworkProperty> { + 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<BytesMut> { + let mut commands: Vec<BytesMut> = 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<BytesMut>; +pub type Rx = mpsc::UnboundedReceiver<BytesMut>; |