Implement server networking.

This commit is contained in:
yukirij 2024-08-07 12:58:04 -07:00
parent 329b97be9f
commit 26fe6c86e7
24 changed files with 348 additions and 59 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target /target
Cargo.lock Cargo.lock
*.pem

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
wasm-bindgen = "0.2.92" wasm-bindgen = "0.2.92"

View File

@ -8,7 +8,7 @@ pub struct App {
pub document:web_sys::Document, pub document:web_sys::Document,
pub menu:web_sys::Element, pub menu:web_sys::Element,
pub container:web_sys::Element, pub main:web_sys::Element,
unload:Option<fn()>, unload:Option<fn()>,
} }
@ -27,7 +27,7 @@ impl App {
document:document, document:document,
menu:menu.unwrap(), menu:menu.unwrap(),
container:container.unwrap(), main:container.unwrap(),
unload:None, unload:None,
}) })

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
scene::util, scene::{ui, util},
session::SessionState,
APP, APP,
}; };
@ -14,9 +15,63 @@ impl crate::prelude::Scene for SceneListOnline {
{ {
let mut app = unsafe {APP.borrow_mut()}; let mut app = unsafe {APP.borrow_mut()};
match &mut *app { match &mut *app {
Some(_app) => { Some(app) => {
util::load_mainmenu(); 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(&section_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 => { } None => { }
} }

View File

@ -14,3 +14,14 @@ pub fn button(document:&web_sys::Document, text:&str, callback:fn(web_sys::Event
Err(_) => Err(()) Err(_) => Err(())
} }
} }
pub fn text_block(document:&web_sys::Document, text:&str) -> Result<web_sys::Element,()>
{
match document.create_element("div") {
Ok(element) => {
element.set_text_content(Some(text));
Ok(element)
}
Err(_) => Err(())
}
}

View File

@ -2,6 +2,7 @@ use crate::util::{pack_u16, unpack_u16};
use super::Packet; use super::Packet;
#[derive(Clone)]
pub struct PacketAuth { pub struct PacketAuth {
pub handle:String, pub handle:String,
pub secret:String, pub secret:String,
@ -62,6 +63,7 @@ impl Packet for PacketAuth {
} }
#[derive(Clone)]
pub struct PacketAuthResponse { pub struct PacketAuthResponse {
pub status:bool, pub status:bool,
pub token:[u8; 8], pub token:[u8; 8],

View File

@ -7,4 +7,4 @@ mod prelude {
fn encode(&self) -> Vec<u8>; fn encode(&self) -> Vec<u8>;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>; fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>;
} }
} use prelude::*; } pub use prelude::*;

View File

@ -8,12 +8,16 @@ tokio = { version = "1.39.2", features = ["full"] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-tungstenite = "0.23.1" tokio-tungstenite = "0.23.1"
tokio-rustls = "0.26.0" tokio-rustls = "0.26.0"
tokio-util = { version = "0.7.11", features = ["compat"] }
rustls = "0.23.5" rustls = "0.23.5"
rustls-pemfile = "2.1.2" rustls-pemfile = "2.1.2"
webpki-roots = "0.26" webpki-roots = "0.26"
opaque-ke = "2.0.0" opaque-ke = "2.0.0"
hyper = { version = "1.4.1", features = ["full"] }
hyper-util = { version = "0.1.7", features = ["tokio"] }
game = { path = "../game" } game = { path = "../game" }
bus = { git = "https://git.tsukiyo.org/Utility/bus" } bus = { git = "https://git.tsukiyo.org/Utility/bus" }
trie = { git = "https://git.tsukiyo.org/Utility/trie" } trie = { git = "https://git.tsukiyo.org/Utility/trie" }
pool = { git = "https://git.tsukiyo.org/Utility/pool" }

View File

@ -1,12 +1,6 @@
use game::Game; use game::Game;
#[derive(Clone, Copy)] pub struct Contest {
pub struct User {
id:usize,
online:bool,
}
pub struct Match {
game:Game, game:Game,
p_dawn:u64, p_dawn:u64,

View File

@ -1,3 +1,7 @@
use game::util::pack_u32;
use pool::Pool;
use trie::Trie;
pub mod user; use user::User; pub mod user; use user::User;
pub mod session; use session::Session; pub mod session; use session::Session;
pub mod contest; use contest::Contest; pub mod contest; use contest::Contest;
@ -5,6 +9,7 @@ pub mod context;
pub struct App { pub struct App {
users:Pool<User>, users:Pool<User>,
handles:Trie<u32>,
salts:Vec<[u8; 16]>, salts:Vec<[u8; 16]>,
sessions:Pool<Session>, sessions:Pool<Session>,
contests:Pool<Contest>, contests:Pool<Contest>,
@ -14,6 +19,7 @@ impl App {
{ {
Self { Self {
users:Pool::new(), users:Pool::new(),
handles:Trie::new(),
salts:Vec::new(), salts:Vec::new(),
sessions:Pool::new(), sessions:Pool::new(),
contests:Pool::new(), contests:Pool::new(),
@ -26,10 +32,13 @@ impl App {
let path_data = Path::new("data"); let path_data = Path::new("data");
if !path_data.exists() { if !path_data.exists() {
fs::create_dir(path_data)?; fs::create_dir(path_data)?;
fs::create_dir(path_data.join("session"))?; fs::create_dir(path_data.join("c"))?;
fs::create_dir(path_data.join("user"))?; 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(()) Ok(())

View File

@ -10,7 +10,7 @@ impl Session {
pub fn new() -> Self pub fn new() -> Self
{ {
Self { Self {
key:[0; 8], key:0,
secret:[0; 16], secret:[0; 16],
user:None, user:None,
context:Context::None, context:Context::None,

View File

@ -8,12 +8,12 @@ mod system;
mod protocol; mod protocol;
use app::App; use app::App;
use net::Server; use system::{cache::WebCache, net::Stream};
use protocol::QRPacket; use tokio_stream::StreamExt;
use tokio_tungstenite::WebSocketStream;
struct ServiceArgs { use hyper_util::rt::TokioIo;
bus:Bus<protocol::QRPacket>, use tokio_rustls::server::TlsStream;
} use tokio::net::TcpStream;
async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>) async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>)
{ {
@ -23,8 +23,8 @@ async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>)
while match bus.receive_wait() { while match bus.receive_wait() {
Some(packet) => { Some(packet) => {
match packet.data { match packet.data {
QRPacket::QAuthenticate(request) => { QRPacket::QAuth(_request) => {
let mut response = PacketAuthResponse::new(); let response = PacketAuthResponse::new();
// get user id from handle // get user id from handle
@ -44,33 +44,126 @@ async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>)
} { } } { }
} }
fn handle_tcp(stream:net::tls::TlsStream, addr:SocketAddr, args:SericeArgs) #[derive(Clone)]
struct HttpServiceArgs {
bus:Bus<protocol::QRPacket>,
cache:WebCache,
}
async fn handle_ws(mut ws_stream:WebSocketStream<TlsStream<TcpStream>>, 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]; let bus_ds = args.bus.mailbox(1).unwrap_or(1);
match stream.recv(&mut length) {
Ok(4) => {
let buffer = Vec::<u8>::with_capacity(unpack_u32(&length, &mut 0) as usize);
match stream.recv(&mut buffer) {
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<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<String>, 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::<TokioIo<TlsStream<TcpStream>>>() {
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())
} }
_ => Ok(Response::new(String::new())),
Err(_) => { }
} }
} }
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] #[tokio::main]
async fn main() async fn main()
{ {
use system::net::{ use system::net::{
Server,
tls::*, tls::*,
certstore::CertStore,
}; };
// Initialize application data. // 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. // Initialize central bus and data serivce.
let bus = Bus::<protocol::QRPacket>::new_as(bus::Mode::Transmitter); let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
match bus.connect() { match b_main.connect() {
Ok(bus) => { Ok(bus) => {
tokio::spawn(move || { thread_datasystem(app, bus); }); tokio::spawn(async move {
thread_datasystem(app, bus).await;
});
} }
Err(_) => { Err(_) => {
println!("fatal: failed to initialize bus."); println!("fatal: failed to initialize bus.");
@ -101,24 +190,30 @@ async fn main()
// Initialize HTTPS service. // Initialize HTTPS service.
match bus.connect() { match b_main.connect() {
Ok(bus) => { 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(_) => { Err(_) => {
println!("error: failed to initialize HTTPS service."); 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(); std::thread::park();
} }

View File

@ -1,5 +1,6 @@
use game::protocol::packet::*; use game::protocol::packet::*;
#[derive(Clone)]
pub enum QRPacket { pub enum QRPacket {
QAuth(PacketAuth), QAuth(PacketAuth),
RAuth(PacketAuthResponse), RAuth(PacketAuthResponse),

100
server/src/system/cache/mod.rs vendored Normal file
View File

@ -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<RwLock<WebCacheData>>,
}
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(),
}
}
}

View File

@ -1 +1,4 @@
#![allow(dead_code)]
pub mod net; pub mod net;
pub mod cache;

View File

@ -7,6 +7,7 @@ pub trait Stream {
fn recv(&mut self, buffer:&mut [u8]) -> Result<usize, ()>; fn recv(&mut self, buffer:&mut [u8]) -> Result<usize, ()>;
fn send(&mut self, buffer:&mut [u8]) -> Result<(), ()>; fn send(&mut self, buffer:&mut [u8]) -> Result<(), ()>;
fn stream(&mut self) -> &mut Self::Socket; fn stream(&mut self) -> &mut Self::Socket;
fn to_stream(self) -> Self::Socket;
} }
pub trait Server { pub trait Server {

View File

@ -19,6 +19,10 @@ impl Stream for TcpStream {
fn stream(&mut self) -> &mut Self::Socket { fn stream(&mut self) -> &mut Self::Socket {
&mut self.stream &mut self.stream
} }
fn to_stream(self) -> Self::Socket {
self.stream
}
} }
pub struct TcpServer { pub struct TcpServer {

View File

@ -40,6 +40,10 @@ impl Stream for TlsStream {
fn stream(&mut self) -> &mut Self::Socket { fn stream(&mut self) -> &mut Self::Socket {
&mut self.stream &mut self.stream
} }
fn to_stream(self) -> Self::Socket {
self.stream
}
} }
pub struct TlsServer { pub struct TlsServer {

View File

@ -1,2 +1,3 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod color; pub mod color;
pub mod string;

View File

@ -0,0 +1,4 @@
pub fn minimize_whitespace(s:&str) -> String
{
s.lines().map(|line| line.trim()).collect::<String>()
}

View File

@ -1 +0,0 @@
import(".wasm");//.then(module=>{module.main();})

1
www/.js Normal file
View File

@ -0,0 +1 @@
WebAssembly.instantiateStreaming(fetch(".wasm"),ap);