diff options
| author | Fuwn <[email protected]> | 2021-03-20 15:46:12 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2021-03-20 15:46:12 -0700 |
| commit | 980a1d0bd02a5453e202bf0fb39d28ffe25a21e0 (patch) | |
| tree | 61f8f37f2372db53ab4000356bdac55e95479eb6 /src | |
| parent | fix: formatting (diff) | |
| download | whirl-980a1d0bd02a5453e202bf0fb39d28ffe25a21e0.tar.xz whirl-980a1d0bd02a5453e202bf0fb39d28ffe25a21e0.zip | |
whirl: :star:
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 8 | ||||
| -rw-r--r-- | src/main.rs | 29 | ||||
| -rw-r--r-- | src/server/auto.rs | 8 | ||||
| -rw-r--r-- | src/server/mod.rs | 2 | ||||
| -rw-r--r-- | src/server/world.rs | 177 | ||||
| -rw-r--r-- | src/sub/buddy_list.rs | 15 | ||||
| -rw-r--r-- | src/sub/mod.rs | 3 | ||||
| -rw-r--r-- | src/sub/property.rs | 73 | ||||
| -rw-r--r-- | src/sub/text.rs | 19 | ||||
| -rw-r--r-- | src/utils.rs | 7 |
10 files changed, 341 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..df9deae --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +#![feature(type_ascription)] // src\sub\property.rs:37:2 + +#[macro_use] +extern crate log; + +pub mod sub; +pub mod server; +pub mod utils; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fc0f560 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +#[macro_use] +extern crate log; + +use mio::net::TcpListener; +use whirl::server; + +fn main() { + dotenv::dotenv().ok(); // Adds ability to use environment variables. + pretty_env_logger::init(); // Adds pretty logging. + + std::thread::spawn(|| { + server::world::WorldServer::new( + TcpListener::bind( + &"0.0.0.0:6650".parse().unwrap() + ).unwrap() + ); + }).join().unwrap(); + debug!("spawned WorldServer thread"); + + // POC, unimplemented. + // std::thread::spawn(move || { + // server::auto::AutoServer::new( + // TcpListener::bind( + // &"0.0.0.0:1337".parse().unwrap() + // ).unwrap() + // ); + // }); + // debug!("spawned AutoServer thread"); +} diff --git a/src/server/auto.rs b/src/server/auto.rs new file mode 100644 index 0000000..7a834b0 --- /dev/null +++ b/src/server/auto.rs @@ -0,0 +1,8 @@ +use mio::net::TcpListener; + +pub struct AutoServer; +impl AutoServer { + pub fn new(_listener: TcpListener) { + unimplemented!(); + } +}
\ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..4d08f2b --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,2 @@ +pub mod auto; +pub mod world; diff --git a/src/server/world.rs b/src/server/world.rs new file mode 100644 index 0000000..19e7ce7 --- /dev/null +++ b/src/server/world.rs @@ -0,0 +1,177 @@ +use mio::net::{TcpListener, TcpStream}; +use std::io::{Read, Write}; +use mio::{Poll, Token, Ready, PollOpt, Events}; +use std::collections::HashMap; +use std::str::from_utf8; +use crate::sub::buddy_list::create_buddy_list_notify_command; +use crate::sub::text::create_text_command; +use crate::sub::property::{create_property_update_command, create_property_request_command}; + +pub struct WorldServer; +impl WorldServer { + pub fn new(listener: TcpListener) { + let poll = Poll::new().unwrap(); + poll.register( + &listener, + Token(0), + Ready::readable(), + PollOpt::edge() + ).unwrap(); + + let mut counter: usize = 0; + let mut sockets: HashMap<Token, TcpStream> = HashMap::new(); + let mut requests: HashMap<Token, Vec<u8>> = HashMap::new(); + let mut buffer = [0 as u8; 1024]; + + let mut events = Events::with_capacity(1024); + loop { + poll.poll(&mut events, None).unwrap(); + for event in &events { + match event.token() { + Token(0) => { + loop { + match listener.accept() { + Ok((socket, address)) => { + counter += 1; + let token = Token(counter); + + poll.register( + &socket, + token, + Ready::readable(), + PollOpt::edge() + ).unwrap(); + + info!( + "registered ip '{}' with token '{}'", + address.ip(), token.0 + ); + + sockets.insert(token, socket); + requests.insert(token, Vec::with_capacity(192)); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => + break, + Err(e) => { error!("unexpected error: {}", e); break } + } + } + }, + token if event.readiness().is_readable() => { + // println!("readable"); + loop { + let read = sockets.get_mut(&token).unwrap().read(&mut buffer); + match read { + Ok(0) => { + // println!("read 0 bytes: {:?}", buffer); + sockets.remove(&token); + break + }, + Ok(n) => { + // println!("read {:?} bytes: {:?}", &n, buffer); + let req = requests.get_mut(&token).unwrap(); + for b in &buffer[0..n] { + req.push(*b); + } + + // First byte means how long data section of the packet is. + // Second byte is to be determined. + // Third byte is the request type. + + // Match packet type by descriptor; **third** byte. + match &buffer.get(2).unwrap() { // Third byte is 2 because Rust is zero-indexed. + // PROPREQ + 10 => { + info!("received property request command"); + sockets.get_mut(&token) + .unwrap() + .write_all(&create_property_update_command()) + .unwrap(); + info!("sent property update"); + } + // SESSINIT + 6 => { + info!("received session initialization command"); + sockets.get_mut(&token) + .unwrap() + .write_all(&create_property_request_command()) + .unwrap(); + info!("sent session initialization command") + } + // PROPSET + 15 => info!("received property set command"), + // BUDDYLISTUPDATE + 29 => { + info!("received buddy list update command"); + + sockets.get_mut(&token) + .unwrap() + .write_all(&create_buddy_list_notify_command("Wirlaburla")) + .unwrap(); + info!("sent buddy notify update command") + } + // ROOMIDRQ + 20 => info!("received room id request command"), + // TEXT + 14 => { + info!("received text command"); + + // TODO: Make this into a command! + let message = from_utf8( + &buffer[6..*&buffer.get(0).unwrap().to_owned() as usize] + ).unwrap(); + info!("message: {}", message); + + for mut socket in &sockets { + socket.1.write_all(&create_text_command(message)).unwrap(); + } + } + // SESSEXIT + 7 => { + info!("received session termination command"); + poll.deregister(sockets.get(&token).unwrap()).unwrap(); + info!("de-registered client: {}", token.0); + } + // Anything else, do nothing. + _ => () + } + }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => + break, + Err(e) => { error!("unexpected error: {}", e); break; } + } + } + + // Unimplemented + // let ready = requests.get(&token).unwrap() + // .windows(4) + // .find(|window| is_double_crnl(*window)) + // .is_some(); + // + // if ready { + // let socket = sockets.get(&token).unwrap(); + // poll.reregister( + // socket, + // token, + // Ready::writable(), + // PollOpt::edge() | PollOpt::oneshot()).unwrap(); + // } + }, + // Unimplemented + // token if event.readiness().is_writable() => { + // println!("writeable"); + // requests.get_mut(&token).unwrap().clear(); + // sockets.get_mut(&token).unwrap().write_all("test".as_bytes()).unwrap(); + // + // // Re-use existing connection ("keep-alive") - switch back to reading + // poll.reregister( + // sockets.get(&token).unwrap(), + // token, + // Ready::readable(), + // PollOpt::edge()).unwrap(); + // }, + _ => () + } + } + } + } +} diff --git a/src/sub/buddy_list.rs b/src/sub/buddy_list.rs new file mode 100644 index 0000000..95b0fdd --- /dev/null +++ b/src/sub/buddy_list.rs @@ -0,0 +1,15 @@ +/// In the future, this will take a `Vec` of buddies and dynamically +/// create a response packet based on the amount of buddies given. +pub fn create_buddy_list_notify_command(buddy: &str) -> Vec<u8> { + let mut buddy_list_notify = Vec::with_capacity(5 + buddy.len()); + buddy_list_notify.push(0x01); // ? + buddy_list_notify.push(0x1E); // BUDDYLISTNOTIFY + // The number of buddies you are being notified of? + buddy_list_notify.push(0x0A); // 01 // ^ + for i in buddy.bytes() { buddy_list_notify.push(i); } // Buddy name + buddy_list_notify.push(0x01); // Is buddy logged on? + // Insert data length as first byte. + buddy_list_notify.insert(0, buddy_list_notify.len() as u8 + 1); // ^ + + buddy_list_notify // Return created array +} diff --git a/src/sub/mod.rs b/src/sub/mod.rs new file mode 100644 index 0000000..05e4344 --- /dev/null +++ b/src/sub/mod.rs @@ -0,0 +1,3 @@ +pub mod buddy_list; +pub mod property; +pub mod text; diff --git a/src/sub/property.rs b/src/sub/property.rs new file mode 100644 index 0000000..6bacc4e --- /dev/null +++ b/src/sub/property.rs @@ -0,0 +1,73 @@ +// struct NetToProperty { +// _prop_id: i32, +// _flags: i32, +// _access: i32, +// _string_value: String, +// _bin_value: Vec<i32>, +// } +// impl NetToProperty { +// fn parse_net_data() -> Self { +// NetToProperty { +// _prop_id: 0, +// _flags: 0, +// _access: 0, +// _string_value: "".to_string(), +// _bin_value: vec![] +// } +// } +// } + +// To be honest, don't care enough to make this function. +// It's not important enough as it stands currently. +pub fn create_property_update_command() -> [u8; 147] { // Vec<u8> + // let mut property = Vec::with_capacity(2); + // property.push(0x01); // ? + // property.push(0x10); // Command type + // + // // Meaningful Data + // property.push(); // Property ID + // property.push(); // Flags + // property.push(); // Access + // + // // Insert data length as first byte. + // property.insert(0, property.len() as u8 + 1); // ^ + // + // property // Return created array + + [ + 0x93, 0xFF, 0x10, 0x1B, 0x80, 0x01, 0x0C, 0x77, 0x6F, + 0x72, 0x6C, 0x64, 0x73, 0x33, 0x64, 0x2E, 0x63, 0x6F, + 0x6D, 0x1A, 0x80, 0x01, 0x12, 0x6D, 0x61, 0x69, 0x6C, + 0x2E, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x73, 0x2E, 0x6E, + 0x65, 0x74, 0x3A, 0x32, 0x35, 0x19, 0x80, 0x01, 0x28, + 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, + 0x77, 0x2D, 0x64, 0x79, 0x6E, 0x61, 0x6D, 0x69, 0x63, + 0x2E, 0x75, 0x73, 0x2E, 0x77, 0x6F, 0x72, 0x6C, 0x64, + 0x73, 0x2E, 0x6E, 0x65, 0x74, 0x2F, 0x63, 0x67, 0x69, + 0x2D, 0x62, 0x69, 0x6E, 0x18, 0x80, 0x01, 0x1F, 0x68, + 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, + 0x2D, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2E, 0x75, + 0x73, 0x2E, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x73, 0x2E, + 0x6E, 0x65, 0x74, 0x0F, 0x80, 0x01, 0x01, 0x31, 0x03, + 0x80, 0x01, 0x02, 0x32, 0x34, 0x01, 0x80, 0x01, 0x0C, + 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x53, 0x4D, 0x41, 0x53, + 0x54, 0x45, 0x52 + ]: [u8; 147] +} + +// src\sub\property.rs:20:1 +pub fn create_property_request_command() -> [u8; 61] { + [ + 0x3D, 0x01, 0x06, 0x04, 0x01, 0x30, 0x01, 0x0C, + 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x53, 0x4D, 0x41, + 0x53, 0x54, 0x45, 0x52, 0x03, 0x02, 0x32, 0x34, + 0x0F, 0x01, 0x31, 0x0A, 0x10, + + // VAR_SERIAL + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x16, 0x01, 0x30, 0x05, 0x0B, 0x64, 0x69, 0x6D, + 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x2D, 0x31 + ]: [u8; 61] +} diff --git a/src/sub/text.rs b/src/sub/text.rs new file mode 100644 index 0000000..b2cecef --- /dev/null +++ b/src/sub/text.rs @@ -0,0 +1,19 @@ +pub fn create_text_command(message: &str) -> Vec<u8> { + let mut text = Vec::with_capacity(6 + message.len()); + text.push(0x01); // ? + text.push(0x0E); // Command type + text.push(0x00); // Assumed to be a divider. + text.push(0x00); // ^ + text.push(message.len() as u8); // `message` length + for i in message.bytes() { text.push(i); } // `message` + text.insert(0, text.len() as u8 + 1); // Insert data length as first byte. + + text // Return created array +} + +// TODO: Get this working! +// pub fn get_message_from_text_command(buffer: &'static [u8; 1024]) -> &'static str { +// from_utf8( +// &buffer[6..*&buffer.get(0).unwrap().to_owned() as usize] +// ).unwrap() +// } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..a834648 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,7 @@ +pub fn _is_double_crnl(window: &[u8]) -> bool { + window.len() >= 4 + && (window[0] == '\r' as u8) + && (window[1] == '\n' as u8) + && (window[2] == '\r' as u8) + && (window[3] == '\n' as u8) +} |