From 329b97be9f3837dde191418b5482bd5780b2dfd3 Mon Sep 17 00:00:00 2001 From: yukirij Date: Tue, 6 Aug 2024 20:23:08 -0700 Subject: [PATCH] Initialize repository. --- .gitignore | 2 + Cargo.toml | 8 + LICENSE.txt | 2 + README.md | 9 + client-web/Cargo.toml | 24 +++ client-web/src/app/mod.rs | 67 ++++++ client-web/src/app/scene/list_online.rs | 24 +++ client-web/src/app/scene/mod.rs | 3 + client-web/src/app/scene/ui/mod.rs | 16 ++ client-web/src/app/scene/util.rs | 56 +++++ client-web/src/app/session/mod.rs | 23 ++ client-web/src/ext.rs | 4 + client-web/src/lib.rs | 32 +++ client-web/src/prelude.rs | 8 + game/Cargo.toml | 9 + game/src/board/mod.rs | 31 +++ game/src/game/mod.rs | 9 + game/src/history/mod.rs | 22 ++ game/src/lib.rs | 8 + game/src/piece/mod.rs | 119 +++++++++++ game/src/protocol/code.rs | 18 ++ game/src/protocol/mod.rs | 2 + game/src/protocol/packet/authenticate.rs | 92 ++++++++ game/src/protocol/packet/mod.rs | 10 + game/src/util/binary.rs | 1 + game/src/util/mod.rs | 2 + game/src/util/pack.rs | 50 +++++ server/Cargo.toml | 19 ++ server/src/app/contest.rs | 15 ++ server/src/app/context.rs | 3 + server/src/app/mod.rs | 37 ++++ server/src/app/net/mod.rs | 81 ++++++++ server/src/app/session.rs | 19 ++ server/src/app/user.rs | 5 + server/src/main.rs | 124 +++++++++++ server/src/protocol/mod.rs | 6 + server/src/system/mod.rs | 1 + server/src/system/net/certstore/mod.rs | 94 +++++++++ server/src/system/net/mod.rs | 27 +++ server/src/system/net/tcp.rs | 73 +++++++ server/src/system/net/tls.rs | 152 ++++++++++++++ server/src/util/color.rs | 21 ++ server/src/util/mod.rs | 2 + server/www/.css | 254 +++++++++++++++++++++++ server/www/.html | 15 ++ server/www/.js | 1 + 46 files changed, 1600 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 client-web/Cargo.toml create mode 100644 client-web/src/app/mod.rs create mode 100644 client-web/src/app/scene/list_online.rs create mode 100644 client-web/src/app/scene/mod.rs create mode 100644 client-web/src/app/scene/ui/mod.rs create mode 100644 client-web/src/app/scene/util.rs create mode 100644 client-web/src/app/session/mod.rs create mode 100644 client-web/src/ext.rs create mode 100644 client-web/src/lib.rs create mode 100644 client-web/src/prelude.rs create mode 100644 game/Cargo.toml create mode 100644 game/src/board/mod.rs create mode 100644 game/src/game/mod.rs create mode 100644 game/src/history/mod.rs create mode 100644 game/src/lib.rs create mode 100644 game/src/piece/mod.rs create mode 100644 game/src/protocol/code.rs create mode 100644 game/src/protocol/mod.rs create mode 100644 game/src/protocol/packet/authenticate.rs create mode 100644 game/src/protocol/packet/mod.rs create mode 100644 game/src/util/binary.rs create mode 100644 game/src/util/mod.rs create mode 100644 game/src/util/pack.rs create mode 100644 server/Cargo.toml create mode 100644 server/src/app/contest.rs create mode 100644 server/src/app/context.rs create mode 100644 server/src/app/mod.rs create mode 100644 server/src/app/net/mod.rs create mode 100644 server/src/app/session.rs create mode 100644 server/src/app/user.rs create mode 100644 server/src/main.rs create mode 100644 server/src/protocol/mod.rs create mode 100644 server/src/system/mod.rs create mode 100644 server/src/system/net/certstore/mod.rs create mode 100644 server/src/system/net/mod.rs create mode 100644 server/src/system/net/tcp.rs create mode 100644 server/src/system/net/tls.rs create mode 100644 server/src/util/color.rs create mode 100644 server/src/util/mod.rs create mode 100644 server/www/.css create mode 100644 server/www/.html create mode 100644 server/www/.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..79163af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +resolver = "2" + +members = [ + "game", + "client-web", + "server", +] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ea2bc1b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,2 @@ +Project: Kirisame, Omen +https://ykr.info/license diff --git a/README.md b/README.md new file mode 100644 index 0000000..d374993 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Omen + +Omen is an abstract strategy game—similar to chess and shogi—where players take turns moving pieces across a hexagon grid to capture their opponents' king. + +## Installation + +The project is composed of two core applications, a web client and a server. + +### Building diff --git a/client-web/Cargo.toml b/client-web/Cargo.toml new file mode 100644 index 0000000..d80dd46 --- /dev/null +++ b/client-web/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "client-web" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +wasm-bindgen = "0.2.92" + +game = { path = "../game" } + +[dependencies.web-sys] +version = "0.3.69" +features = [ + 'Document', + 'Element', + 'HtmlElement', + 'Node', + 'Window', + + 'Event', +] diff --git a/client-web/src/app/mod.rs b/client-web/src/app/mod.rs new file mode 100644 index 0000000..4d89c3a --- /dev/null +++ b/client-web/src/app/mod.rs @@ -0,0 +1,67 @@ +use crate::prelude::Scene; + +pub mod session; use session::Session; +pub mod scene; + +pub struct App { + pub session:Session, + + pub document:web_sys::Document, + pub menu:web_sys::Element, + pub container:web_sys::Element, + + unload:Option, +} +impl App { + pub fn init() -> Result + { + match web_sys::window() { + Some(window) => match window.document() { + Some(document) => { + let menu = document.create_element("nav"); + let container = document.create_element("main"); + + if menu.is_ok() && container.is_ok() { + Ok(Self { + session:Session::new(), + + document:document, + menu:menu.unwrap(), + container:container.unwrap(), + + unload:None, + }) + } else { + Err(()) + } + } + None => Err(()) + } + None => Err(()) + } + } + + pub fn load(&mut self) + { + match self.unload { + Some(proc) => { proc(); } + None => { } + } + + self.document_clear(); + S::load(); + self.unload = Some(S::unload); + } + + pub fn document_clear(&self) + { + match self.document.body() { + Some(body) => { + while let Some(child) = body.last_child() { + body.remove_child(&child).ok(); + } + } + None => { } + } + } +} diff --git a/client-web/src/app/scene/list_online.rs b/client-web/src/app/scene/list_online.rs new file mode 100644 index 0000000..5fb37d6 --- /dev/null +++ b/client-web/src/app/scene/list_online.rs @@ -0,0 +1,24 @@ +use crate::{ + scene::util, + APP, +}; + +/* List Online +** +** Displays a table of recent, ongoing contests. +*/ + +pub struct SceneListOnline { } +impl crate::prelude::Scene for SceneListOnline { + fn load() + { + let mut app = unsafe {APP.borrow_mut()}; + match &mut *app { + Some(_app) => { + util::load_mainmenu(); + + } + None => { } + } + } +} diff --git a/client-web/src/app/scene/mod.rs b/client-web/src/app/scene/mod.rs new file mode 100644 index 0000000..c0e1d3b --- /dev/null +++ b/client-web/src/app/scene/mod.rs @@ -0,0 +1,3 @@ +pub mod util; +pub mod ui; +mod list_online; pub use list_online::*; diff --git a/client-web/src/app/scene/ui/mod.rs b/client-web/src/app/scene/ui/mod.rs new file mode 100644 index 0000000..6ab5622 --- /dev/null +++ b/client-web/src/app/scene/ui/mod.rs @@ -0,0 +1,16 @@ +use wasm_bindgen::{prelude::Closure, JsCast}; + +pub fn button(document:&web_sys::Document, text:&str, callback:fn(web_sys::Event)) -> Result +{ + match document.create_element("button") { + Ok(element) => { + element.set_text_content(Some(text)); + + let cl = Closure::wrap(Box::new(callback) as Box); + element.add_event_listener_with_callback("click", cl.as_ref().unchecked_ref()).ok(); + cl.forget(); + Ok(element) + } + Err(_) => Err(()) + } +} diff --git a/client-web/src/app/scene/util.rs b/client-web/src/app/scene/util.rs new file mode 100644 index 0000000..c22a9f9 --- /dev/null +++ b/client-web/src/app/scene/util.rs @@ -0,0 +1,56 @@ +use crate::{ + scene::{self, ui}, + session::SessionState, + APP +}; + +pub fn load_mainmenu() +{ + let mut app = unsafe {APP.borrow_mut()}; + match &mut *app { + Some(app) => { + + if app.session.state == SessionState::User { + // Button: Online + match ui::button(&app.document, "Online", |_e|{ + let mut app = unsafe {APP.borrow_mut()}; + match &mut *app { + Some(app) => { app.load::(); } + None => { } + } + }) { + Ok(button) => { app.menu.append_child(&button).ok(); } + Err(_) => { } + } + } + + + // Button: Continue + match ui::button(&app.document, "Continue", |_e|{ + let mut app = unsafe {APP.borrow_mut()}; + match &mut *app { + Some(app) => { app.load::(); } + None => { } + } + }) { + Ok(button) => { app.menu.append_child(&button).ok(); } + Err(_) => { } + } + + + // Button: Join + match ui::button(&app.document, "Continue", |_e|{ + let mut app = unsafe {APP.borrow_mut()}; + match &mut *app { + Some(app) => { app.load::(); } + None => { } + } + }) { + Ok(button) => { app.menu.append_child(&button).ok(); } + Err(_) => { } + } + + + } None => { } + } +} diff --git a/client-web/src/app/session/mod.rs b/client-web/src/app/session/mod.rs new file mode 100644 index 0000000..c8157dc --- /dev/null +++ b/client-web/src/app/session/mod.rs @@ -0,0 +1,23 @@ +#[derive(Clone, Copy, PartialEq)] +pub enum SessionState { + Anonymous, + User, +} + +pub struct Session { + pub state:SessionState, + pub handle:String, + pub token:[u8; 8], + pub secret:[u8; 16], +} +impl Session { + pub fn new() -> Self + { + Self { + state:SessionState::Anonymous, + handle:String::new(), + token:[0; 8], + secret:[0; 16], + } + } +} diff --git a/client-web/src/ext.rs b/client-web/src/ext.rs new file mode 100644 index 0000000..dd2eed0 --- /dev/null +++ b/client-web/src/ext.rs @@ -0,0 +1,4 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { } diff --git a/client-web/src/lib.rs b/client-web/src/lib.rs new file mode 100644 index 0000000..be58054 --- /dev/null +++ b/client-web/src/lib.rs @@ -0,0 +1,32 @@ +#![allow(dead_code)] + +use std::cell::RefCell; + +use wasm_bindgen::prelude::*; + +mod ext; +mod prelude; +mod app; use app::*; + +static mut APP :RefCell> = RefCell::new(None); + +#[wasm_bindgen(start)] +fn main() -> Result<(),JsValue> +{ + match App::init() { + Ok(app) => { + unsafe { + APP = RefCell::new(Some(app)); + + let mut app = APP.borrow_mut(); + match &mut *app { + Some(app) => { + app.load::(); + } None => { } + } + } + } + Err(_) => { } + } + Ok(()) +} diff --git a/client-web/src/prelude.rs b/client-web/src/prelude.rs new file mode 100644 index 0000000..c1113e9 --- /dev/null +++ b/client-web/src/prelude.rs @@ -0,0 +1,8 @@ +pub trait Scene { + fn load(); + fn unload() { } + + fn hover() { } + fn click() { } + fn keydown() { } +} diff --git a/game/Cargo.toml b/game/Cargo.toml new file mode 100644 index 0000000..505fb48 --- /dev/null +++ b/game/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "game" +version = "0.1.0" +edition = "2021" + +[dependencies] +wasm-bindgen = "0.2.92" +web-sys = "0.3.69" +js-sys = "0.3.69" diff --git a/game/src/board/mod.rs b/game/src/board/mod.rs new file mode 100644 index 0000000..f4975c6 --- /dev/null +++ b/game/src/board/mod.rs @@ -0,0 +1,31 @@ +use crate::piece::{Piece, PIECE_NONE}; + +#[derive(Clone, Copy)] +pub struct Tile { + piece:u8, +} +impl Tile { + pub fn new() -> Self + { + Self { + piece:PIECE_NONE, + } + } +} + +#[derive(Clone, Copy)] +pub struct Board { + tiles:[Tile; 61], + pieces:[Piece; 38], + pool:[[usize; 6]; 2], +} +impl Board { + pub fn new() -> Self + { + Self { + tiles:[Tile::new(); 61], + pieces:[Piece::new(); 38], + pool:[[0; 6]; 2], + } + } +} diff --git a/game/src/game/mod.rs b/game/src/game/mod.rs new file mode 100644 index 0000000..c5d521c --- /dev/null +++ b/game/src/game/mod.rs @@ -0,0 +1,9 @@ +pub struct Game { + +} +impl Game { + pub fn new() -> Self + { + Self { } + } +} diff --git a/game/src/history/mod.rs b/game/src/history/mod.rs new file mode 100644 index 0000000..c3bc5a0 --- /dev/null +++ b/game/src/history/mod.rs @@ -0,0 +1,22 @@ +#[derive(Clone, Copy)] +pub enum PlayType { + Move = 0, + Place = 1, +} + +#[derive(Clone, Copy)] +pub enum PlayState { + Normal = 0, + Check = 1, + Checkmate = 2, +} + +#[derive(Clone, Copy)] +pub struct Play { + form:PlayType, + from:u8, + to:u8, + piece:u8, + capture:u8, + state:PlayState, +} diff --git a/game/src/lib.rs b/game/src/lib.rs new file mode 100644 index 0000000..b1cb4bd --- /dev/null +++ b/game/src/lib.rs @@ -0,0 +1,8 @@ +#![allow(dead_code)] + +pub mod util; +pub mod piece; +pub mod board; +pub mod history; +pub mod protocol; +mod game; pub use game::Game; diff --git a/game/src/piece/mod.rs b/game/src/piece/mod.rs new file mode 100644 index 0000000..ba7ba9c --- /dev/null +++ b/game/src/piece/mod.rs @@ -0,0 +1,119 @@ +use crate::util::bit; + +#[derive(Clone, Copy)] +pub struct MoveSet { + direction:u32, + distance:u32, +} + +#[derive(Clone, Copy)] +pub struct PieceClass { + name:&'static str, + moves:MoveSet, + pmoves:MoveSet, +} + +pub const PIECES_COUNT :usize = 7; +pub const PIECE_NONE :u8 = PIECES_COUNT as u8 + 1; +pub const PIECE_MILITIA :u8 = 0; +pub const PIECE_KNIGHT :u8 = 1; +pub const PIECE_LANCE :u8 = 2; +pub const PIECE_TOWER :u8 = 3; +pub const PIECE_CASTLE :u8 = 4; +pub const PIECE_DRAGON :u8 = 5; +pub const PIECE_KING :u8 = 6; + +pub const PIECES :[PieceClass; PIECES_COUNT] = [ + PieceClass { + name: "Militia", + moves: MoveSet { + direction:bit(0) | bit(1) | bit(5), + distance:0, + }, + pmoves: MoveSet{ + direction:bit(0) | bit(1) | bit(2) | bit(4) | bit(5), + distance:0, + }, + }, + PieceClass { + name: "Knight", + moves: MoveSet { + direction:bit(3) | bit(6) | bit(11) | bit(13) | bit(17), + distance:0, + }, + pmoves: MoveSet{ + direction:bit(3) | bit(6) | bit(7) | bit(10) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17), + distance:0, + }, + }, + PieceClass { + name: "Lance", + moves: MoveSet { + direction:0, + distance:0, + }, + pmoves: MoveSet { + direction:0, + distance:0, + }, + }, + PieceClass { + name: "Tower", + moves: MoveSet { + direction:0, + distance:0, + }, + pmoves: MoveSet { + direction:0, + distance:0, + }, + }, + PieceClass { + name: "Castle", + moves: MoveSet { + direction:0, + distance:0, + }, + pmoves: MoveSet { + direction:0, + distance:0, + }, + }, + PieceClass { + name: "Dragon", + moves: MoveSet { + direction:0, + distance:0, + }, + pmoves: MoveSet { + direction:0, + distance:0, + }, + }, + PieceClass { + name: "King", + moves: MoveSet { + direction:0, + distance:0, + }, + pmoves: MoveSet { + direction:0, + distance:0, + }, + }, +]; + +#[derive(Clone, Copy)] +pub struct Piece { + class:u8, + promoted:bool, +} +impl Piece { + pub fn new() -> Self + { + Self { + class:PIECE_NONE, + promoted:false, + } + } +} diff --git a/game/src/protocol/code.rs b/game/src/protocol/code.rs new file mode 100644 index 0000000..0e7586f --- /dev/null +++ b/game/src/protocol/code.rs @@ -0,0 +1,18 @@ +#![allow(dead_code)] + +pub const STATUS_OK :u16 = 0x0000; +pub const STATUS_BAD :u16 = 0x0001; +pub const STATUS_NOT_IMPL :u16 = 0x0002; + +pub const CODE_AUTH :u16 = 0x0000; +pub const CODE_REGISTER :u16 = 0x0001; +pub const CODE_RESUME :u16 = 0x0002; +pub const CODE_EXIT :u16 = 0x0003; +pub const CODE_LIST_GAME :u16 = 0x0010; +pub const CODE_LIST_OPEN :u16 = 0x0011; +pub const CODE_LIST_LIVE :u16 = 0x0012; +pub const CODE_JOIN :u16 = 0x0020; +pub const CODE_SPECTATE :u16 = 0x0021; +pub const CODE_LEAVE :u16 = 0x0022; +pub const CODE_RETIRE :u16 = 0x0023; +pub const CODE_PLAY :u16 = 0x0030; diff --git a/game/src/protocol/mod.rs b/game/src/protocol/mod.rs new file mode 100644 index 0000000..4cdb2d4 --- /dev/null +++ b/game/src/protocol/mod.rs @@ -0,0 +1,2 @@ +pub mod code; +pub mod packet; diff --git a/game/src/protocol/packet/authenticate.rs b/game/src/protocol/packet/authenticate.rs new file mode 100644 index 0000000..dee8c1d --- /dev/null +++ b/game/src/protocol/packet/authenticate.rs @@ -0,0 +1,92 @@ +use crate::util::{pack_u16, unpack_u16}; + +use super::Packet; + +pub struct PacketAuth { + pub handle:String, + pub secret:String, +} +impl PacketAuth { + pub fn new() -> Self + { + Self { + handle:String::new(), + secret:String::new(), + } + } +} +impl Packet for PacketAuth { + type Data = Self; + + fn encode(&self) -> Vec + { + [ + pack_u16(self.handle.len() as u16), + self.handle.as_bytes().to_vec(), + pack_u16(self.secret.len() as u16), + self.secret.as_bytes().to_vec(), + ].concat() + } + + fn decode(data:&Vec, index:&mut usize) -> Result + { + let mut result = Self::new(); + + let mut length = unpack_u16(data, index) as usize; + if data.len() >= *index + length { + match String::from_utf8(data[*index..*index+length].to_vec()) { + Ok(text) => { + *index += length; + result.handle = text; + + length = unpack_u16(data, index) as usize; + if data.len() >= *index + length { + + match String::from_utf8(data[*index..*index+length].to_vec()) { + Ok(text) => { + *index += length; + result.secret = text; + + return Ok(result); + } + Err(_) => { } + } + } + } + Err(_) => { } + } + } + + Err(()) + } +} + + +pub struct PacketAuthResponse { + pub status:bool, + pub token:[u8; 8], + pub secret:[u8; 16], +} +impl PacketAuthResponse { + pub fn new() -> Self + { + Self { + status:false, + token:[0; 8], + secret:[0; 16], + } + } +} +impl Packet for PacketAuthResponse { + type Data = Self; + + fn encode(&self) -> Vec + { + vec![] + } + + fn decode(_data:&Vec, _index:&mut usize) -> Result + { + Err(()) + } +} diff --git a/game/src/protocol/packet/mod.rs b/game/src/protocol/packet/mod.rs new file mode 100644 index 0000000..71f30f4 --- /dev/null +++ b/game/src/protocol/packet/mod.rs @@ -0,0 +1,10 @@ +mod authenticate; pub use authenticate::*; + +mod prelude { + pub trait Packet { + type Data; + + fn encode(&self) -> Vec; + fn decode(data:&Vec, index:&mut usize) -> Result; + } +} use prelude::*; diff --git a/game/src/util/binary.rs b/game/src/util/binary.rs new file mode 100644 index 0000000..5dc3ea8 --- /dev/null +++ b/game/src/util/binary.rs @@ -0,0 +1 @@ +pub const fn bit(v:u32) -> u32 { 1u32 << v } diff --git a/game/src/util/mod.rs b/game/src/util/mod.rs new file mode 100644 index 0000000..7c193fc --- /dev/null +++ b/game/src/util/mod.rs @@ -0,0 +1,2 @@ +mod binary; pub use binary::*; +mod pack; pub use pack::*; diff --git a/game/src/util/pack.rs b/game/src/util/pack.rs new file mode 100644 index 0000000..b3a222e --- /dev/null +++ b/game/src/util/pack.rs @@ -0,0 +1,50 @@ +pub fn pack_u16(value:u16) -> Vec +{ + vec![(value >> 8) as u8, (value & 0xFF) as u8] +} + +pub fn unpack_u16(data:&Vec, index:&mut usize) -> u16 +{ + let mut result :u16 = 0; + if *index < data.len() { + result = (data[*index] as u16) << 8; + *index += 1; + } + if *index < data.len() { + result |= data[*index] as u16; + *index += 1; + } + result +} + +pub fn pack_u32(value:u32) -> Vec +{ + vec![ + (value >> 24) as u8, + (value >> 16) as u8, + (value >> 8) as u8, + (value & 0xFF) as u8, + ] +} + +pub fn unpack_u32(data:&Vec, index:&mut usize) -> u32 +{ + let mut result :u32 = 0; + if *index < data.len() { + result = (data[*index] as u32) << 24; + *index += 1; + } + if *index < data.len() { + result = (data[*index] as u32) << 16; + *index += 1; + } + if *index < data.len() { + result = (data[*index] as u32) << 8; + *index += 1; + } + if *index < data.len() { + result |= data[*index] as u32; + *index += 1; + } + result +} diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..e49434c --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1.39.2", features = ["full"] } +tokio-stream = "0.1.15" +tokio-tungstenite = "0.23.1" +tokio-rustls = "0.26.0" +rustls = "0.23.5" +rustls-pemfile = "2.1.2" +webpki-roots = "0.26" +opaque-ke = "2.0.0" + +game = { path = "../game" } + +bus = { git = "https://git.tsukiyo.org/Utility/bus" } +trie = { git = "https://git.tsukiyo.org/Utility/trie" } diff --git a/server/src/app/contest.rs b/server/src/app/contest.rs new file mode 100644 index 0000000..02af18e --- /dev/null +++ b/server/src/app/contest.rs @@ -0,0 +1,15 @@ +use game::Game; + +#[derive(Clone, Copy)] +pub struct User { + id:usize, + online:bool, +} + +pub struct Match { + game:Game, + + p_dawn:u64, + p_dusk:u64, + specators:Vec, +} diff --git a/server/src/app/context.rs b/server/src/app/context.rs new file mode 100644 index 0000000..aaed5cf --- /dev/null +++ b/server/src/app/context.rs @@ -0,0 +1,3 @@ +pub enum Context { + None, +} diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs new file mode 100644 index 0000000..edc8ce8 --- /dev/null +++ b/server/src/app/mod.rs @@ -0,0 +1,37 @@ +pub mod user; use user::User; +pub mod session; use session::Session; +pub mod contest; use contest::Contest; +pub mod context; + +pub struct App { + users:Pool, + salts:Vec<[u8; 16]>, + sessions:Pool, + contests:Pool, +} +impl App { + pub fn new() -> Self + { + Self { + users:Pool::new(), + salts:Vec::new(), + sessions:Pool::new(), + contests:Pool::new(), + } + } + + pub fn init(&mut self) -> Result<(),std::io::Error> + { use std::{path::Path, fs}; + + 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::write(path_data.join("user/index"), pack_u32(0)); + } + + Ok(()) + } +} diff --git a/server/src/app/net/mod.rs b/server/src/app/net/mod.rs new file mode 100644 index 0000000..150f6e7 --- /dev/null +++ b/server/src/app/net/mod.rs @@ -0,0 +1,81 @@ +fn handle_packet(data:&Vec) -> Result,()> +{ + use game::protocol::{ + prelude::*, + code::*, + packet::*, + }; + + let response = Vec::::new(); + let mut status = STATUS_OK; + + let mut index = 2; + if data.len() < 2 { return Err(()); } + match ((data[0] as u16) << 8) + data[1] as u16 { + CODE_AUTH => { + status = STATUS_BAD; + + match PacketAuth::decode(data, index) { + Ok(data) => { + // check if handle exists + + status = STATUS_OK; + } + Err(_) => { } + } + } + + CODE_REGISTER => { + + } + + CODE_RESUME => { + + } + + CODE_EXIT => { + + } + + CODE_LIST_GAME => { + + } + + CODE_LIST_OPEN => { + + } + + CODE_LIST_LIVE => { + + } + + CODE_JOIN => { + + } + + CODE_SPECTATE => { + + } + + CODE_LEAVE => { + + } + + CODE_RETIRE => { + + } + + CODE_PLAY => { + + } + + _ => { + status = STATUS_NOT_IMPL; + } + } + + Ok([ + pack_u16(status), + response, + ].concat()) +} diff --git a/server/src/app/session.rs b/server/src/app/session.rs new file mode 100644 index 0000000..347ce7c --- /dev/null +++ b/server/src/app/session.rs @@ -0,0 +1,19 @@ +use crate::app::context::Context; + +pub struct Session { + key:u64, + secret:[u8; 16], + user:Option, + context:Context, +} +impl Session { + pub fn new() -> Self + { + Self { + key:[0; 8], + secret:[0; 16], + user:None, + context:Context::None, + } + } +} diff --git a/server/src/app/user.rs b/server/src/app/user.rs new file mode 100644 index 0000000..de8676a --- /dev/null +++ b/server/src/app/user.rs @@ -0,0 +1,5 @@ +pub struct User { + handle:String, + secret:String, + na_key:usize, +} diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..ab9ec4b --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,124 @@ +use std::net::SocketAddr; + +use bus::Bus; + +mod util; +mod app; +mod system; +mod protocol; + +use app::App; +use net::Server; +use protocol::QRPacket; + +struct ServiceArgs { + bus:Bus, +} + +async fn thread_datasystem(mut _app:App, bus:Bus) +{ + use protocol::QRPacket; + use game::protocol::packet::*; + + while match bus.receive_wait() { + Some(packet) => { + match packet.data { + QRPacket::QAuthenticate(request) => { + let mut response = PacketAuthResponse::new(); + + // get user id from handle + + // get user salt + + // hash request secret + + // compare hash to user secret + + // return response + bus.send(packet.from, QRPacket::RAuth(response)).is_ok() + } + _ => { true } + } + } + None => false, + } { } +} + +fn handle_tcp(stream:net::tls::TlsStream, addr:SocketAddr, args:SericeArgs) +{ + use game::util::unpack_u32; + + 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) { + + } + } + + Ok(_) => { + + } + + Err(_) => { } + } +} + +#[tokio::main] +async fn main() +{ + use system::net::{ + tls::*, + certstore::CertStore, + }; + + // Initialize application data. + let mut app = App::new(); + if app.init().is_err() { + println!("fatal: failed to initialize server."); + return; + } + + + // 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() { + Ok(bus) => { + tokio::spawn(move || { thread_datasystem(app, bus); }); + } + Err(_) => { + println!("fatal: failed to initialize bus."); + return; + } + } + + + // Initialize HTTPS service. + match bus.connect() { + Ok(bus) => { + // bind 38611 + } + 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 new file mode 100644 index 0000000..fe67a5a --- /dev/null +++ b/server/src/protocol/mod.rs @@ -0,0 +1,6 @@ +use game::protocol::packet::*; + +pub enum QRPacket { + QAuth(PacketAuth), + RAuth(PacketAuthResponse), +} diff --git a/server/src/system/mod.rs b/server/src/system/mod.rs new file mode 100644 index 0000000..f9faf2f --- /dev/null +++ b/server/src/system/mod.rs @@ -0,0 +1 @@ +pub mod net; diff --git a/server/src/system/net/certstore/mod.rs b/server/src/system/net/certstore/mod.rs new file mode 100644 index 0000000..7d44285 --- /dev/null +++ b/server/src/system/net/certstore/mod.rs @@ -0,0 +1,94 @@ +use std::{ + fs::File, io, sync::Arc +}; +use tokio::sync::RwLock; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + +use trie::Trie; + +pub struct Certificate { + pub certs:Vec>, + pub key:PrivateKeyDer<'static>, +} + +pub struct CertificateStore { + certs:Trie>, +} +impl CertificateStore { + pub fn new() -> Self + { + Self { + certs:Trie::>::new(), + } + } + + pub fn add(&mut self, domain:&str, cert_file:&str, key_file:&str) -> Result<(),()> + { + // load certificate file + let certs = match File::open(cert_file) { + Ok(fcert) => { + let mut reader = io::BufReader::new(fcert); + let mut certificates = Vec::::new(); + for result in rustls_pemfile::certs(&mut reader) { + match result { + Ok(certificate) => { + certificates.push(certificate); + } + Err(_) => { } + } + } + Ok(certificates) + } + Err(_) => Err(()) + }; + + // load private key file + let key = match File::open(key_file) { + Ok(fkey) => { + let mut reader = io::BufReader::new(fkey); + match rustls_pemfile::private_key(&mut reader) { + Ok(result) => match result { + Some(key) => Ok(key), + None => Err(()) + } + Err(_) => Err(()) + } + } + Err(_) => Err(()) + }; + + if certs.is_ok() && key.is_ok() { + self.certs.set(domain, Arc::new(Certificate { + certs:certs.unwrap(), + key:key.unwrap(), + })); + Ok(()) + } + else { + Err(()) + } + } + + pub fn remove(&mut self, domain:&str) -> Result<(),()> + { + if self.certs.unset(domain) { + Ok(()) + } + else { + Err(()) + } + } + + pub fn get(&self, domain:&str) -> Result, ()> + { + match self.certs.get(domain) { + Some(certs) => Ok(certs), + None => Err(()) + } + } + + pub fn to_share(self) -> Arc> + { + Arc::new(RwLock::new(self)) + } +} diff --git a/server/src/system/net/mod.rs b/server/src/system/net/mod.rs new file mode 100644 index 0000000..4cd5098 --- /dev/null +++ b/server/src/system/net/mod.rs @@ -0,0 +1,27 @@ +use tokio::task::JoinHandle; +use std::net::SocketAddr; + +pub trait Stream { + type Socket; + + fn recv(&mut self, buffer:&mut [u8]) -> Result; + fn send(&mut self, buffer:&mut [u8]) -> Result<(), ()>; + fn stream(&mut self) -> &mut Self::Socket; +} + +pub trait Server { + type Stream :Stream; + + fn bind(&mut self, interface:&str) -> impl std::future::Future>; + //async fn accept(&self) -> Result; + fn accept(&self, callback:F, args:Args) -> impl std::future::Future>,()>> + where + F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static, + Fut: std::future::Future> + Send + 'static, + Args: Send + 'static; + fn close(&mut self); +} + +pub mod tcp; +pub mod tls; +pub mod certstore; diff --git a/server/src/system/net/tcp.rs b/server/src/system/net/tcp.rs new file mode 100644 index 0000000..0c208c6 --- /dev/null +++ b/server/src/system/net/tcp.rs @@ -0,0 +1,73 @@ +use super::*; + +use tokio::net::{self, TcpListener}; + +pub struct TcpStream { + stream:net::TcpStream, +} +impl Stream for TcpStream { + type Socket = net::TcpStream; + + fn recv(&mut self, _buffer:&mut [u8]) -> Result { + Err(()) + } + + fn send(&mut self, _buffer:&mut [u8]) -> Result<(), ()> { + Err(()) + } + + fn stream(&mut self) -> &mut Self::Socket { + &mut self.stream + } +} + +pub struct TcpServer { + listener:Option, +} +impl TcpServer { + pub fn new() -> Self + { + Self { + listener:None, + } + } +} +impl Server for TcpServer { + type Stream = TcpStream; + + async fn bind(&mut self, interface:&str) -> Result<(), ()> + { + match TcpListener::bind(interface).await { + Ok(listener) => { + self.listener = Some(listener); + Ok(()) + } + Err(_) => Err(()) + } + } + + async fn accept(&self, callback:F, args:Args) -> Result>,()> + where + F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static, + Fut: std::future::Future> + Send + 'static, + Args: Send + 'static + { + match &self.listener { + Some(listener) => { + match listener.accept().await { + Ok((stream, addr)) => { + Ok(tokio::spawn(async move { + callback(Self::Stream { stream:stream }, addr, args).await + })) + } + Err(_) => Err(()) + } + } + None => Err(()) + } + } + + fn close(&mut self) { + self.listener = None; + } +} diff --git a/server/src/system/net/tls.rs b/server/src/system/net/tls.rs new file mode 100644 index 0000000..29fc407 --- /dev/null +++ b/server/src/system/net/tls.rs @@ -0,0 +1,152 @@ +use super::*; + +use std::{ + net::SocketAddr, sync::Arc +}; +use tokio::{net::{TcpListener, TcpStream}, sync::RwLock, task::JoinHandle}; +use rustls::{ + pki_types::{CertificateDer, PrivateKeyDer}, server::Acceptor, ServerConfig +}; +use tokio_rustls::{server, LazyConfigAcceptor}; + +use certstore::CertificateStore; + +struct PKP { + pub certs:Vec>, + pub key:PrivateKeyDer<'static>, +} + +type CertStorePtr = Arc>; + +pub struct TlsStream { + stream:server::TlsStream, +} +impl TlsStream { } +impl Stream for TlsStream { + type Socket = server::TlsStream; + + fn recv(&mut self, _buffer:&mut [u8]) -> Result { + //match self.stream.read(&mut buffer) { + // Ok(size) => Ok(size), + // Err(_) => Err(()) + //} + Err(()) + } + + fn send(&mut self, _buffer:&mut [u8]) -> Result<(), ()> { + Err(()) + } + + fn stream(&mut self) -> &mut Self::Socket { + &mut self.stream + } +} + +pub struct TlsServer { + certificates:CertStorePtr, + listener:Option, +} +impl TlsServer { +pub fn new() -> Self + { + Self { + certificates:Arc::new(RwLock::new(CertificateStore::new())), + listener:None, + } + } + + pub async fn add_cert(&mut self, domain:&str, cert_file:&str, key_file:&str) -> Result<(),()> + { + let mut cert_handle = self.certificates.write().await; + match cert_handle.add(domain, cert_file, key_file) { + Ok(_) => Ok(()), + Err(_) => Err(()) + } + } + + pub async fn remove_cert(&mut self, domain:&str) -> Result<(),()> + { + let mut cert_handle = self.certificates.write().await; + match cert_handle.remove(domain) { + Ok(_) => Ok(()), + Err(_) => Err(()) + } + } + + async fn get_server_config(certificates:CertStorePtr, domain :&str) -> Option> + { + match certificates.read().await.get(domain) { + Ok(config) => { + let server_config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(config.certs.clone(), config.key.clone_key()); + match server_config { + Ok(config) => Some(Arc::new(config)), + Err(_) => None + } + } + Err(_) => None + } + } +} +impl Server for TlsServer { + type Stream = TlsStream; + + async fn bind(&mut self, interface:&str) -> Result<(), ()> { + match tokio::net::TcpListener::bind(interface).await { + Ok(listener) => { + self.listener = Some(listener); + Ok(()) + } + Err(_) => Err(()) + } + } + + async fn accept(&self, callback:F, args:Args) -> Result>,()> + where + F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static, + Fut: std::future::Future> + Send + 'static, + Args: Send + 'static + { + match &self.listener { + Some(listener) => { + if let Ok((socket, client_addr)) = listener.accept().await { + let acceptor = LazyConfigAcceptor::new(Acceptor::default(), socket); + let certificates = self.certificates.clone(); + + Ok(tokio::spawn(async move { + match acceptor.await { + Ok(hs) => { + let hello = hs.client_hello(); + if let Some(server_name) = hello.server_name() { + match Self::get_server_config(certificates, server_name).await { + Some(server_config) => { + match hs.into_stream(server_config).await { + Ok(tlsstream) => { + callback(Self::Stream { stream:tlsstream }, client_addr, args).await + } + Err(_) => Err(()) + } + } + None => Err(()) + } + } + else { Err(()) } + } + Err(e) => { + println!("[conn failure] {:}", e.to_string()); + Err(()) + } + } + })) + } + else { Err(()) } + } + None => Err(()) + } + } + + fn close(&mut self) { + self.listener = None; + } +} diff --git a/server/src/util/color.rs b/server/src/util/color.rs new file mode 100644 index 0000000..8d7fdf2 --- /dev/null +++ b/server/src/util/color.rs @@ -0,0 +1,21 @@ +#[derive(Clone, Copy)] +pub struct Color { + r:f32, + g:f32, + b:f32, +} +impl Color { + pub fn rgb(r:u8, g:u8, b:u8) -> Self + { + Self { + r: r as f32 / 255., + g: g as f32 / 255., + b: b as f32 / 255., + } + } + + pub fn frgb(r:f32, g:f32, b:f32) -> Self + { + Self { r, g, b } + } +} diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs new file mode 100644 index 0000000..ed0dd32 --- /dev/null +++ b/server/src/util/mod.rs @@ -0,0 +1,2 @@ +#![allow(dead_code)] +pub mod color; diff --git a/server/www/.css b/server/www/.css new file mode 100644 index 0000000..3accc10 --- /dev/null +++ b/server/www/.css @@ -0,0 +1,254 @@ +* { + box-sizing:border-box; +} + +html { + display:block; + position:relative; + width:100%; + height:100%; + padding:0; + margin:0; + overflow:hidden; +} + +body { + display:flex; + position:relative; + flex-flow:row nowrap; + align-items:flex-start; + justify-content:flex-start; + width:100%; + height:100%; + padding:0; + margin:0; + overflow:hidden; + + font-family:sans-serif; +} + +body>nav { + display:block; + position:relative; + width:9rem; + height:100%; + + background-color:#282828; +} +body>nav>button { + display:block; + position:relative; + width:100%; + height:2.5rem; + padding:0 1rem 0 1rem; + + text-align:left; + font-size:1.25rem; + cursor:pointer; + + background-color:#282828; + border:0; + color:#c0c0c0; +} body>nav>button:hover { + background-color:#383838; + color:#e0e0e0; +} + +body>nav>header { + display:block; + position:relative; + width:100%; + height:3rem; + + line-height:3rem; + text-align:center; + font-size:1.8rem; + font-weight:bold; + font-variant:small-caps; + + color:#d0d0d0; + border-bottom:1px solid #404040; +} + +main { + display:block; + position:relative; + width:100%; + height:100%; + + flex-grow:1; + + background-color:#202020; +} + +main>nav { + display:flex; + position:relative; + flex-flow:row nowrap; + align-items:flex-start; + justify-content:flex-start; + width:100%; + height:3rem; + + background-color:#282828; +} + +main>nav>section:first-child { + display:flex; + position:relative; + flex-flow:row nowrap; + align-items:flex-start; + justify-content:flex-start; + height:100%; +} + +main>nav>section:last-child { + display:flex; + position:relative; + flex-flow:row nowrap; + align-items:flex-start; + justify-content:flex-end; + height:100%; + flex-grow:1; +} + +main>nav>section>button { + display:block; + position:relative; + width:auto; + height:100%; + padding:0 1rem 0 1rem; + text-align:left; + font-size:1.25rem; + cursor:pointer; + + background-color:#282828; + border:0; + color:#c0c0c0; +} main>nav>section>button:hover { + background-color:#383838; + color:#e0e0e0; +} + +main>nav>section>div { + display:block; + position:relative; + width:auto; + height:100%; + padding:0 1rem 0 1rem; + + line-height:3rem; + text-align:left; + font-size:1.25rem; + + color:#e0e0e0; +} + +main table { + width:100%; + border-collapse:collapse; +} +main table tr { + height:2.5rem; +} +main table th { + padding:0 1rem 0 1rem; + + text-align:center; + font-size:1.2rem; + font-weight:bold; + + background-color:#383838; + color:#f0f0f0; + border-bottom:1px solid #404040; +} +main table td { + height:100%; + padding:0 1rem 0 1rem; + + text-align:center; + + background-color:#303030; + color:#f0f0f0; +} +main table td:last-child { + display:flex; + flex-flow:row nowrap; + align-items:flex-start; + justify-content:flex-end; + flex-grow:1; +} + +main table td>img { + height:2rem; + vertical-align:middle; +} + +main table td:last-child>button { + display:block; + position:relative; + width:auto; + height:100%; + padding:0 1rem 0 1rem; + + text-align:left; + font-size:1.25rem; + cursor:pointer; + + background-color:#303030; + border:0; + color:#e0e0e0; +} main table td:last-child>button:hover { + background-color:#343434; +} + +main>canvas { + display:block; + position:relative; + width:100%; + height:100%; + padding:0; + margin:0; + + background-color:#202020; +} + +/* Scene:game */ +main.game>div.sidemenu { + display:flex; + position:absolute; + bottom:0px; + right:1px; + z-index:10; + + flex-flow:column nowrap; + + width:auto; + height:auto; + + border:0; + border-top-left-radius:1rem; + + overflow:hidden; +} + +main.game>div.sidemenu>button { + display:block; + position:relative; + padding:1rem; + margin:0 0 1px 0; + background-color:#202020; + color:#F0F0F0; + border:0; + + font-family:sans-serif; + font-size:1rem; + + cursor:pointer; +} main.game>div.sidemenu>button:hover { + background-color:#404040; +} main.game>div.sidemenu>button.warn:hover { + background-color:#602020; +} + +span.text-system { color:#909090; } diff --git a/server/www/.html b/server/www/.html new file mode 100644 index 0000000..302352f --- /dev/null +++ b/server/www/.html @@ -0,0 +1,15 @@ + + + + Omen + + + + + + + + + + + diff --git a/server/www/.js b/server/www/.js new file mode 100644 index 0000000..e143dfc --- /dev/null +++ b/server/www/.js @@ -0,0 +1 @@ +import(".wasm");//.then(module=>{module.main();})