From 26fe6c86e7f8795de1f43c3ecddd5a5cc3922376 Mon Sep 17 00:00:00 2001 From: yukirij Date: Wed, 7 Aug 2024 12:58:04 -0700 Subject: [PATCH] Implement server networking. --- .gitignore | 1 + client-web/Cargo.toml | 2 +- client-web/src/app/mod.rs | 4 +- client-web/src/app/scene/list_online.rs | 59 +++++++- client-web/src/app/scene/ui/mod.rs | 11 ++ game/src/protocol/packet/authenticate.rs | 2 + game/src/protocol/packet/mod.rs | 2 +- server/Cargo.toml | 4 + server/src/app/contest.rs | 8 +- server/src/app/mod.rs | 15 +- server/src/app/session.rs | 2 +- server/src/main.rs | 177 +++++++++++++++++------ server/src/protocol/mod.rs | 1 + server/src/system/cache/mod.rs | 100 +++++++++++++ server/src/system/mod.rs | 3 + server/src/system/net/mod.rs | 1 + server/src/system/net/tcp.rs | 4 + server/src/system/net/tls.rs | 4 + server/src/util/mod.rs | 1 + server/src/util/string.rs | 4 + server/www/.js | 1 - {server/www => www}/.css | 0 {server/www => www}/.html | 0 www/.js | 1 + 24 files changed, 348 insertions(+), 59 deletions(-) create mode 100644 server/src/system/cache/mod.rs create mode 100644 server/src/util/string.rs delete mode 100644 server/www/.js rename {server/www => www}/.css (100%) rename {server/www => www}/.html (100%) create mode 100644 www/.js diff --git a/.gitignore b/.gitignore index 96ef6c0..8f96d6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +*.pem diff --git a/client-web/Cargo.toml b/client-web/Cargo.toml index d80dd46..cd50bd7 100644 --- a/client-web/Cargo.toml +++ b/client-web/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.92" diff --git a/client-web/src/app/mod.rs b/client-web/src/app/mod.rs index 4d89c3a..1834186 100644 --- a/client-web/src/app/mod.rs +++ b/client-web/src/app/mod.rs @@ -8,7 +8,7 @@ pub struct App { pub document:web_sys::Document, pub menu:web_sys::Element, - pub container:web_sys::Element, + pub main:web_sys::Element, unload:Option, } @@ -27,7 +27,7 @@ impl App { document:document, menu:menu.unwrap(), - container:container.unwrap(), + main:container.unwrap(), unload:None, }) diff --git a/client-web/src/app/scene/list_online.rs b/client-web/src/app/scene/list_online.rs index 5fb37d6..5819865 100644 --- a/client-web/src/app/scene/list_online.rs +++ b/client-web/src/app/scene/list_online.rs @@ -1,5 +1,6 @@ use crate::{ - scene::util, + scene::{ui, util}, + session::SessionState, APP, }; @@ -14,9 +15,63 @@ impl crate::prelude::Scene for SceneListOnline { { let mut app = unsafe {APP.borrow_mut()}; match &mut *app { - Some(_app) => { + Some(app) => { util::load_mainmenu(); + // Create header + match app.document.create_element("header") { + Ok(header) => { + + // Left-side header + match app.document.create_element("section") { + Ok(section_left) => { + + // Button: Start + if app.session.state == SessionState::User { + match ui::button(&app.document, "Start", |_e|{ }) { + Ok(button) => { section_left.append_child(&button).ok(); } + Err(_) => { } + } + } + + header.append_child(§ion_left).ok(); + } + Err(_) => { } + } + + // Right-side header + match app.document.create_element("section") { + Ok(section_right) => { + + // Button: Previous Page + match ui::button(&app.document, "◀", |_e|{ }) { + Ok(button) => { section_right.append_child(&button).ok(); } + Err(_) => { } + } + + // Text: Page Number + match ui::button(&app.document, "", |_e|{ }) { + Ok(elem) => { + elem.set_id("pn"); + section_right.append_child(&elem).ok(); + } + Err(_) => { } + } + + // Button: Next Page + match ui::button(&app.document, "▶", |_e|{ }) { + Ok(button) => { section_right.append_child(&button).ok(); } + Err(_) => { } + } + + } + Err(_) => { } + } + + app.main.append_child(&header).ok(); + } + Err(_) => { } + } } None => { } } diff --git a/client-web/src/app/scene/ui/mod.rs b/client-web/src/app/scene/ui/mod.rs index 6ab5622..a047b25 100644 --- a/client-web/src/app/scene/ui/mod.rs +++ b/client-web/src/app/scene/ui/mod.rs @@ -14,3 +14,14 @@ pub fn button(document:&web_sys::Document, text:&str, callback:fn(web_sys::Event Err(_) => Err(()) } } + +pub fn text_block(document:&web_sys::Document, text:&str) -> Result +{ + match document.create_element("div") { + Ok(element) => { + element.set_text_content(Some(text)); + Ok(element) + } + Err(_) => Err(()) + } +} diff --git a/game/src/protocol/packet/authenticate.rs b/game/src/protocol/packet/authenticate.rs index dee8c1d..1025aac 100644 --- a/game/src/protocol/packet/authenticate.rs +++ b/game/src/protocol/packet/authenticate.rs @@ -2,6 +2,7 @@ use crate::util::{pack_u16, unpack_u16}; use super::Packet; +#[derive(Clone)] pub struct PacketAuth { pub handle:String, pub secret:String, @@ -62,6 +63,7 @@ impl Packet for PacketAuth { } +#[derive(Clone)] pub struct PacketAuthResponse { pub status:bool, pub token:[u8; 8], diff --git a/game/src/protocol/packet/mod.rs b/game/src/protocol/packet/mod.rs index 71f30f4..b77a506 100644 --- a/game/src/protocol/packet/mod.rs +++ b/game/src/protocol/packet/mod.rs @@ -7,4 +7,4 @@ mod prelude { fn encode(&self) -> Vec; fn decode(data:&Vec, index:&mut usize) -> Result; } -} use prelude::*; +} pub use prelude::*; diff --git a/server/Cargo.toml b/server/Cargo.toml index e49434c..5aff0f6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,12 +8,16 @@ tokio = { version = "1.39.2", features = ["full"] } tokio-stream = "0.1.15" tokio-tungstenite = "0.23.1" tokio-rustls = "0.26.0" +tokio-util = { version = "0.7.11", features = ["compat"] } rustls = "0.23.5" rustls-pemfile = "2.1.2" webpki-roots = "0.26" opaque-ke = "2.0.0" +hyper = { version = "1.4.1", features = ["full"] } +hyper-util = { version = "0.1.7", features = ["tokio"] } game = { path = "../game" } bus = { git = "https://git.tsukiyo.org/Utility/bus" } trie = { git = "https://git.tsukiyo.org/Utility/trie" } +pool = { git = "https://git.tsukiyo.org/Utility/pool" } diff --git a/server/src/app/contest.rs b/server/src/app/contest.rs index 02af18e..b6869b8 100644 --- a/server/src/app/contest.rs +++ b/server/src/app/contest.rs @@ -1,12 +1,6 @@ use game::Game; -#[derive(Clone, Copy)] -pub struct User { - id:usize, - online:bool, -} - -pub struct Match { +pub struct Contest { game:Game, p_dawn:u64, diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index edc8ce8..ae15e7a 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -1,3 +1,7 @@ +use game::util::pack_u32; +use pool::Pool; +use trie::Trie; + pub mod user; use user::User; pub mod session; use session::Session; pub mod contest; use contest::Contest; @@ -5,6 +9,7 @@ pub mod context; pub struct App { users:Pool, + handles:Trie, salts:Vec<[u8; 16]>, sessions:Pool, contests:Pool, @@ -14,6 +19,7 @@ impl App { { Self { users:Pool::new(), + handles:Trie::new(), salts:Vec::new(), sessions:Pool::new(), contests:Pool::new(), @@ -26,10 +32,13 @@ impl App { let path_data = Path::new("data"); if !path_data.exists() { fs::create_dir(path_data)?; - fs::create_dir(path_data.join("session"))?; - fs::create_dir(path_data.join("user"))?; + fs::create_dir(path_data.join("c"))?; + fs::create_dir(path_data.join("s"))?; + fs::create_dir(path_data.join("u"))?; - fs::write(path_data.join("user/index"), pack_u32(0)); + fs::write(path_data.join("c/.i"), pack_u32(0)).ok(); + fs::write(path_data.join("s/.i"), pack_u32(0)).ok(); + fs::write(path_data.join("u/.i"), pack_u32(0)).ok(); } Ok(()) diff --git a/server/src/app/session.rs b/server/src/app/session.rs index 347ce7c..b4f1148 100644 --- a/server/src/app/session.rs +++ b/server/src/app/session.rs @@ -10,7 +10,7 @@ impl Session { pub fn new() -> Self { Self { - key:[0; 8], + key:0, secret:[0; 16], user:None, context:Context::None, diff --git a/server/src/main.rs b/server/src/main.rs index ab9ec4b..2bcc9e9 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,12 +8,12 @@ mod system; mod protocol; use app::App; -use net::Server; -use protocol::QRPacket; - -struct ServiceArgs { - bus:Bus, -} +use system::{cache::WebCache, net::Stream}; +use tokio_stream::StreamExt; +use tokio_tungstenite::WebSocketStream; +use hyper_util::rt::TokioIo; +use tokio_rustls::server::TlsStream; +use tokio::net::TcpStream; async fn thread_datasystem(mut _app:App, bus:Bus) { @@ -23,8 +23,8 @@ async fn thread_datasystem(mut _app:App, bus:Bus) while match bus.receive_wait() { Some(packet) => { match packet.data { - QRPacket::QAuthenticate(request) => { - let mut response = PacketAuthResponse::new(); + QRPacket::QAuth(_request) => { + let response = PacketAuthResponse::new(); // get user id from handle @@ -44,33 +44,126 @@ async fn thread_datasystem(mut _app:App, bus:Bus) } { } } -fn handle_tcp(stream:net::tls::TlsStream, addr:SocketAddr, args:SericeArgs) +#[derive(Clone)] +struct HttpServiceArgs { + bus:Bus, + cache:WebCache, +} + +async fn handle_ws(mut ws_stream:WebSocketStream>, args:HttpServiceArgs) { - use game::util::unpack_u32; + use tokio_tungstenite::tungstenite::protocol::Message; + use game::util::unpack_u16; + use protocol::QRPacket; + use game::protocol::{ + code::*, + packet::*, + }; - let length = vec![0u8; 4]; - match stream.recv(&mut length) { - Ok(4) => { - let buffer = Vec::::with_capacity(unpack_u32(&length, &mut 0) as usize); - match stream.recv(&mut buffer) { + let bus_ds = args.bus.mailbox(1).unwrap_or(1); + while match ws_stream.try_next().await { + Ok(msg) => match msg { + Some(Message::Binary(data)) => { + + let mut index :usize = 0; + let code: u16 = unpack_u16(&data, &mut index); + + match code { + CODE_AUTH => { + match PacketAuth::decode(&data, &mut index) { + Ok(packet) => { + if args.bus.send(bus_ds, QRPacket::QAuth(packet)).is_ok() { + match args.bus.receive_wait() { + Some(resp) => match resp.data { + QRPacket::RAuth(_resp) => { + + } + _ => { } + } + None => { } + } + } + } + Err(_) => { } + } + } + _ => { } + } + + true } + Some(_) => true, + None => false } + Err(_) => false, + } { } - Ok(_) => { - + ws_stream.close(None).await.ok(); +} + +async fn service_http(request:hyper::Request, args:HttpServiceArgs) -> Result, std::convert::Infallible> +{ + use hyper::Response; + use tokio_tungstenite::accept_async; + + match request.uri().path() { + "" => Ok(Response::new(args.cache.html())), + ".css" => Ok(Response::new(args.cache.css())), + ".js" => Ok(Response::new(args.cache.js())), + ".wasm" => Ok(Response::new(args.cache.wasm())), + //"favicon.png" => Ok(Response::new(String::new())), + "ws" => { + if request.headers().get(hyper::header::UPGRADE).map(|h| h == "websocket").unwrap_or(false) { + match hyper::upgrade::on(request).await { + Ok(upgraded) => { + match upgraded.downcast::>>() { + Ok(parts) => { + match accept_async(parts.io.into_inner()).await { + Ok(ws_stream) => { handle_ws(ws_stream, args).await } + Err(_) => { } + } + } + Err(_) => { } + } + } + Err(_) => { } + } + } + Ok(Response::builder() + .status(101) + .header(hyper::header::UPGRADE, "websocket") + .body(String::new()) + .unwrap()) } - - Err(_) => { } + _ => Ok(Response::new(String::new())), } } +async fn handle_http(stream:system::net::tls::TlsStream, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()> +{ + use hyper::server::conn::http1; + use hyper::service::service_fn; + + println!("Connection from {}", addr.to_string()); + + let io = TokioIo::new(stream.to_stream()); + + http1::Builder::new() + .serve_connection(io, service_fn(move |req| { + service_http(req, args.clone()) + })) + .await.ok(); + Ok(()) +} + + #[tokio::main] async fn main() { use system::net::{ + Server, tls::*, - certstore::CertStore, }; // Initialize application data. @@ -81,17 +174,13 @@ async fn main() } - // Initialize service data. - //let cs = CertStore::new(); - //cs.add("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").ok(); - //let cs = cs.to_share(); - - // Initialize central bus and data serivce. - let bus = Bus::::new_as(bus::Mode::Transmitter); - match bus.connect() { + let b_main :Bus = Bus::new_as(bus::Mode::Transmitter); + match b_main.connect() { Ok(bus) => { - tokio::spawn(move || { thread_datasystem(app, bus); }); + tokio::spawn(async move { + thread_datasystem(app, bus).await; + }); } Err(_) => { println!("fatal: failed to initialize bus."); @@ -101,24 +190,30 @@ async fn main() // Initialize HTTPS service. - match bus.connect() { + match b_main.connect() { Ok(bus) => { - // bind 38611 + let cache = WebCache::init(); + + let mut server = TlsServer::new(); + server.add_cert("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").await.ok(); + match server.bind("0.0.0.0:38612").await { + Ok(_) => { + tokio::spawn(async move { + while server.accept(handle_http, HttpServiceArgs { + bus:bus.connect().unwrap(), + cache:cache.clone(), + }).await.is_ok() { } + }); + } + Err(_) => { + println!("error: failed to bind port 38612."); + } + } } Err(_) => { println!("error: failed to initialize HTTPS service."); } } - // Initialize TLS service. - match bus.connect() { - Ok(bus) => { - // bind 38612 - } - Err(_) => { - println!("error: failed to initialize TLS service."); - } - } - std::thread::park(); } diff --git a/server/src/protocol/mod.rs b/server/src/protocol/mod.rs index fe67a5a..018a8f8 100644 --- a/server/src/protocol/mod.rs +++ b/server/src/protocol/mod.rs @@ -1,5 +1,6 @@ use game::protocol::packet::*; +#[derive(Clone)] pub enum QRPacket { QAuth(PacketAuth), RAuth(PacketAuthResponse), diff --git a/server/src/system/cache/mod.rs b/server/src/system/cache/mod.rs new file mode 100644 index 0000000..1de2a17 --- /dev/null +++ b/server/src/system/cache/mod.rs @@ -0,0 +1,100 @@ +use std::{io::Read, sync::{Arc, RwLock}}; + +use crate::util::string::minimize_whitespace; + +struct WebCacheData { + html:String, + css:String, + js:String, + wasm:String, +} + +#[derive(Clone)] +pub struct WebCache { + data:Arc>, +} +impl WebCache { + pub fn init() -> Self + { + use std::fs::File; + + let mut html = String::new(); + let mut css = String::new(); + let mut js = String::new(); + let mut wasm = String::new(); + + // Cache html file + match File::open("www/.html") { + Ok(mut file) => { + file.read_to_string(&mut html).ok(); + html = minimize_whitespace(&html); + } + Err(_) => { } + } + + // Cache css file + match File::open("www/.css") { + Ok(mut file) => { + file.read_to_string(&mut css).ok(); + css = minimize_whitespace(&css); + } + Err(_) => { } + } + + // Cache js file + match File::open("www/.js") { + Ok(mut file) => { + file.read_to_string(&mut js).ok(); + js = minimize_whitespace(&js); + } + Err(_) => { } + } + + // Cache wasm file + match File::open("www/.wasm") { + Ok(mut file) => { + file.read_to_string(&mut wasm).ok(); + wasm = minimize_whitespace(&wasm); + } + Err(_) => { } + } + + Self { + data:Arc::new(RwLock::new(WebCacheData { + html, css, js, wasm + })), + } + } + + pub fn html(&self) -> String + { + match self.data.read() { + Ok(reader) => reader.html.to_string(), + Err(_) => String::new(), + } + } + + pub fn css(&self) -> String + { + match self.data.read() { + Ok(reader) => reader.css.to_string(), + Err(_) => String::new(), + } + } + + pub fn js(&self) -> String + { + match self.data.read() { + Ok(reader) => reader.js.to_string(), + Err(_) => String::new(), + } + } + + pub fn wasm(&self) -> String + { + match self.data.read() { + Ok(reader) => reader.wasm.to_string(), + Err(_) => String::new(), + } + } +} diff --git a/server/src/system/mod.rs b/server/src/system/mod.rs index f9faf2f..9057889 100644 --- a/server/src/system/mod.rs +++ b/server/src/system/mod.rs @@ -1 +1,4 @@ +#![allow(dead_code)] + pub mod net; +pub mod cache; diff --git a/server/src/system/net/mod.rs b/server/src/system/net/mod.rs index 4cd5098..b99293e 100644 --- a/server/src/system/net/mod.rs +++ b/server/src/system/net/mod.rs @@ -7,6 +7,7 @@ pub trait Stream { fn recv(&mut self, buffer:&mut [u8]) -> Result; fn send(&mut self, buffer:&mut [u8]) -> Result<(), ()>; fn stream(&mut self) -> &mut Self::Socket; + fn to_stream(self) -> Self::Socket; } pub trait Server { diff --git a/server/src/system/net/tcp.rs b/server/src/system/net/tcp.rs index 0c208c6..ea5dbd5 100644 --- a/server/src/system/net/tcp.rs +++ b/server/src/system/net/tcp.rs @@ -19,6 +19,10 @@ impl Stream for TcpStream { fn stream(&mut self) -> &mut Self::Socket { &mut self.stream } + + fn to_stream(self) -> Self::Socket { + self.stream + } } pub struct TcpServer { diff --git a/server/src/system/net/tls.rs b/server/src/system/net/tls.rs index 29fc407..b6918b6 100644 --- a/server/src/system/net/tls.rs +++ b/server/src/system/net/tls.rs @@ -40,6 +40,10 @@ impl Stream for TlsStream { fn stream(&mut self) -> &mut Self::Socket { &mut self.stream } + + fn to_stream(self) -> Self::Socket { + self.stream + } } pub struct TlsServer { diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs index ed0dd32..cca2ae2 100644 --- a/server/src/util/mod.rs +++ b/server/src/util/mod.rs @@ -1,2 +1,3 @@ #![allow(dead_code)] pub mod color; +pub mod string; diff --git a/server/src/util/string.rs b/server/src/util/string.rs new file mode 100644 index 0000000..7148c2a --- /dev/null +++ b/server/src/util/string.rs @@ -0,0 +1,4 @@ +pub fn minimize_whitespace(s:&str) -> String +{ + s.lines().map(|line| line.trim()).collect::() +} diff --git a/server/www/.js b/server/www/.js deleted file mode 100644 index e143dfc..0000000 --- a/server/www/.js +++ /dev/null @@ -1 +0,0 @@ -import(".wasm");//.then(module=>{module.main();}) diff --git a/server/www/.css b/www/.css similarity index 100% rename from server/www/.css rename to www/.css diff --git a/server/www/.html b/www/.html similarity index 100% rename from server/www/.html rename to www/.html diff --git a/www/.js b/www/.js new file mode 100644 index 0000000..3162079 --- /dev/null +++ b/www/.js @@ -0,0 +1 @@ +WebAssembly.instantiateStreaming(fetch(".wasm"),ap);