Initialize repository.

This commit is contained in:
yukirij 2024-08-06 20:23:08 -07:00
commit 329b97be9f
46 changed files with 1600 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[workspace]
resolver = "2"
members = [
"game",
"client-web",
"server",
]

2
LICENSE.txt Normal file
View File

@ -0,0 +1,2 @@
Project: Kirisame, Omen
https://ykr.info/license

9
README.md Normal file
View File

@ -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

24
client-web/Cargo.toml Normal file
View File

@ -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',
]

67
client-web/src/app/mod.rs Normal file
View File

@ -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<fn()>,
}
impl App {
pub fn init() -> Result<Self,()>
{
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<S:Scene>(&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 => { }
}
}
}

View File

@ -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 => { }
}
}
}

View File

@ -0,0 +1,3 @@
pub mod util;
pub mod ui;
mod list_online; pub use list_online::*;

View File

@ -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<web_sys::Element,()>
{
match document.create_element("button") {
Ok(element) => {
element.set_text_content(Some(text));
let cl = Closure::wrap(Box::new(callback) as Box<dyn FnMut(_)>);
element.add_event_listener_with_callback("click", cl.as_ref().unchecked_ref()).ok();
cl.forget();
Ok(element)
}
Err(_) => Err(())
}
}

View File

@ -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::<scene::SceneListOnline>(); }
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::<scene::SceneListOnline>(); }
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::<scene::SceneListOnline>(); }
None => { }
}
}) {
Ok(button) => { app.menu.append_child(&button).ok(); }
Err(_) => { }
}
} None => { }
}
}

View File

@ -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],
}
}
}

4
client-web/src/ext.rs Normal file
View File

@ -0,0 +1,4 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" { }

32
client-web/src/lib.rs Normal file
View File

@ -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<Option<App>> = 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::<scene::SceneListOnline>();
} None => { }
}
}
}
Err(_) => { }
}
Ok(())
}

View File

@ -0,0 +1,8 @@
pub trait Scene {
fn load();
fn unload() { }
fn hover() { }
fn click() { }
fn keydown() { }
}

9
game/Cargo.toml Normal file
View File

@ -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"

31
game/src/board/mod.rs Normal file
View File

@ -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],
}
}
}

9
game/src/game/mod.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Game {
}
impl Game {
pub fn new() -> Self
{
Self { }
}
}

22
game/src/history/mod.rs Normal file
View File

@ -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,
}

8
game/src/lib.rs Normal file
View File

@ -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;

119
game/src/piece/mod.rs Normal file
View File

@ -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,
}
}
}

18
game/src/protocol/code.rs Normal file
View File

@ -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;

2
game/src/protocol/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod code;
pub mod packet;

View File

@ -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<u8>
{
[
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<u8>, index:&mut usize) -> Result<Self::Data, ()>
{
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<u8>
{
vec![]
}
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()>
{
Err(())
}
}

View File

@ -0,0 +1,10 @@
mod authenticate; pub use authenticate::*;
mod prelude {
pub trait Packet {
type Data;
fn encode(&self) -> Vec<u8>;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>;
}
} use prelude::*;

1
game/src/util/binary.rs Normal file
View File

@ -0,0 +1 @@
pub const fn bit(v:u32) -> u32 { 1u32 << v }

2
game/src/util/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod binary; pub use binary::*;
mod pack; pub use pack::*;

50
game/src/util/pack.rs Normal file
View File

@ -0,0 +1,50 @@
pub fn pack_u16(value:u16) -> Vec<u8>
{
vec![(value >> 8) as u8, (value & 0xFF) as u8]
}
pub fn unpack_u16(data:&Vec<u8>, 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<u8>
{
vec![
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
(value & 0xFF) as u8,
]
}
pub fn unpack_u32(data:&Vec<u8>, 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
}

19
server/Cargo.toml Normal file
View File

@ -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" }

15
server/src/app/contest.rs Normal file
View File

@ -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<u64>,
}

View File

@ -0,0 +1,3 @@
pub enum Context {
None,
}

37
server/src/app/mod.rs Normal file
View File

@ -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<User>,
salts:Vec<[u8; 16]>,
sessions:Pool<Session>,
contests:Pool<Contest>,
}
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(())
}
}

81
server/src/app/net/mod.rs Normal file
View File

@ -0,0 +1,81 @@
fn handle_packet(data:&Vec<u8>) -> Result<Vec<u8>,()>
{
use game::protocol::{
prelude::*,
code::*,
packet::*,
};
let response = Vec::<u8>::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())
}

19
server/src/app/session.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::app::context::Context;
pub struct Session {
key:u64,
secret:[u8; 16],
user:Option<usize>,
context:Context,
}
impl Session {
pub fn new() -> Self
{
Self {
key:[0; 8],
secret:[0; 16],
user:None,
context:Context::None,
}
}
}

5
server/src/app/user.rs Normal file
View File

@ -0,0 +1,5 @@
pub struct User {
handle:String,
secret:String,
na_key:usize,
}

124
server/src/main.rs Normal file
View File

@ -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<protocol::QRPacket>,
}
async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>)
{
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::<u8>::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::<protocol::QRPacket>::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();
}

View File

@ -0,0 +1,6 @@
use game::protocol::packet::*;
pub enum QRPacket {
QAuth(PacketAuth),
RAuth(PacketAuthResponse),
}

1
server/src/system/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod net;

View File

@ -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<CertificateDer<'static>>,
pub key:PrivateKeyDer<'static>,
}
pub struct CertificateStore {
certs:Trie<Arc<Certificate>>,
}
impl CertificateStore {
pub fn new() -> Self
{
Self {
certs:Trie::<Arc<Certificate>>::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::<CertificateDer>::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<Arc<Certificate>, ()>
{
match self.certs.get(domain) {
Some(certs) => Ok(certs),
None => Err(())
}
}
pub fn to_share(self) -> Arc<RwLock<Self>>
{
Arc::new(RwLock::new(self))
}
}

View File

@ -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<usize, ()>;
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<Output = Result<(),()>>;
//async fn accept(&self) -> Result<Self::Stream, ()>;
fn accept<F, Fut, Args>(&self, callback:F, args:Args) -> impl std::future::Future<Output = Result<JoinHandle<Result<(),()>>,()>>
where
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(),()>> + Send + 'static,
Args: Send + 'static;
fn close(&mut self);
}
pub mod tcp;
pub mod tls;
pub mod certstore;

View File

@ -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<usize, ()> {
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<net::TcpListener>,
}
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<F, Fut, Args>(&self, callback:F, args:Args) -> Result<JoinHandle<Result<(),()>>,()>
where
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(),()>> + 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;
}
}

View File

@ -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<CertificateDer<'static>>,
pub key:PrivateKeyDer<'static>,
}
type CertStorePtr = Arc<RwLock<CertificateStore>>;
pub struct TlsStream {
stream:server::TlsStream<TcpStream>,
}
impl TlsStream { }
impl Stream for TlsStream {
type Socket = server::TlsStream<TcpStream>;
fn recv(&mut self, _buffer:&mut [u8]) -> Result<usize, ()> {
//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<TcpListener>,
}
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<Arc<ServerConfig>>
{
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<F, Fut, Args>(&self, callback:F, args:Args) -> Result<JoinHandle<Result<(),()>>,()>
where
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(),()>> + 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;
}
}

21
server/src/util/color.rs Normal file
View File

@ -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 }
}
}

2
server/src/util/mod.rs Normal file
View File

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

254
server/www/.css Normal file
View File

@ -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; }

15
server/www/.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Omen</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="Omen">
<meta name="description" content="Abstract strategy game mixing chess and shogi on a hexagon grid.">
<link rel="stylesheet" href=".css">
<script src=".js" async></script>
</head>
<body>
<noscript>This application requires JavaScript to function.</noscript>
</body>
</html>

1
server/www/.js Normal file
View File

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