Add registration and authentication.
This commit is contained in:
parent
e47558d6d4
commit
7bae892c2e
@ -1,9 +1,20 @@
|
|||||||
|
use crate::board::Board;
|
||||||
|
|
||||||
|
pub enum GameState {
|
||||||
|
Active,
|
||||||
|
Complete,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
|
pub state:GameState,
|
||||||
|
pub board:Board,
|
||||||
}
|
}
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self { }
|
Self {
|
||||||
|
state:GameState::Active,
|
||||||
|
board:Board::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,4 @@ pub mod util;
|
|||||||
pub mod piece;
|
pub mod piece;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub mod protocol;
|
|
||||||
mod game; pub use game::Game;
|
mod game; pub use game::Game;
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
#![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;
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod code;
|
|
||||||
pub mod packet;
|
|
@ -1,10 +0,0 @@
|
|||||||
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, ()>;
|
|
||||||
}
|
|
||||||
} pub use prelude::*;
|
|
@ -1,2 +1 @@
|
|||||||
mod binary; pub use binary::*;
|
mod binary; pub use binary::*;
|
||||||
mod pack; pub use pack::*;
|
|
||||||
|
@ -5,7 +5,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.39.2", features = ["full"] }
|
tokio = { version = "1.39.2", features = ["full"] }
|
||||||
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"] }
|
tokio-util = { version = "0.7.11", features = ["compat"] }
|
||||||
@ -17,9 +16,13 @@ opaque-ke = "2.0.0"
|
|||||||
hyper = { version = "1.4.1", features = ["full"] }
|
hyper = { version = "1.4.1", features = ["full"] }
|
||||||
hyper-util = { version = "0.1.7", features = ["tokio"] }
|
hyper-util = { version = "0.1.7", features = ["tokio"] }
|
||||||
http-body-util = "0.1.2"
|
http-body-util = "0.1.2"
|
||||||
|
futures = "0.3.30"
|
||||||
|
rust-argon2 = "2.1.0"
|
||||||
|
ring = "0.17.8"
|
||||||
|
|
||||||
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" }
|
sparse = { git = "https://git.tsukiyo.org/Utility/sparse" }
|
||||||
pool = { git = "https://git.tsukiyo.org/Utility/pool" }
|
trie = { git = "https://git.tsukiyo.org/Utility/trie" }
|
||||||
|
pool = { git = "https://git.tsukiyo.org/Utility/pool" }
|
||||||
|
36
server/docs/protocol/requests.md
Normal file
36
server/docs/protocol/requests.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Table
|
||||||
|
|
||||||
|
## User Authentication
|
||||||
|
|
||||||
|
- Register—create a new account.
|
||||||
|
- Handle
|
||||||
|
- Secret
|
||||||
|
- Invite_Code
|
||||||
|
- Authenticate—log into an account and create a new client session.
|
||||||
|
- Handle
|
||||||
|
- Secret
|
||||||
|
- Deauthenticate—revoke the current client session.
|
||||||
|
|
||||||
|
## Sessions
|
||||||
|
|
||||||
|
- List_Sessions—
|
||||||
|
- Page
|
||||||
|
- Ongoing or Complete
|
||||||
|
- Joinable
|
||||||
|
- Live
|
||||||
|
- Update_Sessions—
|
||||||
|
|
||||||
|
## Game
|
||||||
|
|
||||||
|
- Join—
|
||||||
|
- Session_Id
|
||||||
|
- Spectate—
|
||||||
|
- Session_Id
|
||||||
|
- Leave—
|
||||||
|
- Retire—
|
||||||
|
- Play—
|
||||||
|
- Move_Source [ Board, Pool ]
|
||||||
|
- Move_From
|
||||||
|
- Piece_From
|
||||||
|
- Move_To
|
||||||
|
- Piece_To
|
18
server/src/app/authentication.rs
Normal file
18
server/src/app/authentication.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
pub type AuthToken = [u8; 8];
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Authentication {
|
||||||
|
pub key:AuthToken,
|
||||||
|
pub secret:[u8; 16],
|
||||||
|
pub user:u32,
|
||||||
|
}
|
||||||
|
impl Authentication {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
key:[0; 8],
|
||||||
|
secret:[0; 16],
|
||||||
|
user:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
server/src/app/connection.rs
Normal file
15
server/src/app/connection.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use crate::app::authentication::AuthToken;
|
||||||
|
|
||||||
|
pub struct Connection {
|
||||||
|
pub bus:u32,
|
||||||
|
pub auth:Option<AuthToken>,
|
||||||
|
}
|
||||||
|
impl Connection {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bus:0,
|
||||||
|
auth:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
use game::Game;
|
|
||||||
|
|
||||||
pub struct Contest {
|
|
||||||
game:Game,
|
|
||||||
|
|
||||||
p_dawn:u64,
|
|
||||||
p_dusk:u64,
|
|
||||||
specators:Vec<u64>,
|
|
||||||
}
|
|
@ -1,28 +1,42 @@
|
|||||||
use game::util::pack_u32;
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use sparse::Sparse;
|
||||||
use pool::Pool;
|
use pool::Pool;
|
||||||
use trie::Trie;
|
use trie::Trie;
|
||||||
|
use crate::util::pack::pack_u32;
|
||||||
|
|
||||||
|
pub mod connection; use connection::Connection;
|
||||||
pub mod user; use user::User;
|
pub mod user; use user::User;
|
||||||
|
pub mod authentication; use authentication::Authentication;
|
||||||
pub mod session; use session::Session;
|
pub mod session; use session::Session;
|
||||||
pub mod contest; use contest::Contest;
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
users:Pool<User>,
|
pub connections:Pool<Connection>,
|
||||||
handles:Trie<u32>,
|
|
||||||
salts:Vec<[u8; 16]>,
|
pub users:Pool<User>,
|
||||||
sessions:Pool<Session>,
|
pub user_next:u32,
|
||||||
contests:Pool<Contest>,
|
pub user_id:Sparse<usize>,
|
||||||
|
pub user_handle:Trie<u32>,
|
||||||
|
pub salts:Vec<[u8; 16]>,
|
||||||
|
|
||||||
|
pub auths:Trie<Authentication>,
|
||||||
|
pub sessions:Trie<Session>,
|
||||||
}
|
}
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
|
connections:Pool::new(),
|
||||||
|
|
||||||
users:Pool::new(),
|
users:Pool::new(),
|
||||||
handles:Trie::new(),
|
user_next:0,
|
||||||
|
user_id:Sparse::new(),
|
||||||
|
user_handle:Trie::new(),
|
||||||
salts:Vec::new(),
|
salts:Vec::new(),
|
||||||
sessions:Pool::new(),
|
|
||||||
contests:Pool::new(),
|
auths:Trie::new(),
|
||||||
|
sessions:Trie::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
use crate::app::context::Context;
|
use game::Game;
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
key:u64,
|
key:[u8; 8],
|
||||||
secret:[u8; 16],
|
secret:[u8; 8],
|
||||||
user:Option<usize>,
|
|
||||||
context:Context,
|
game:Game,
|
||||||
}
|
|
||||||
impl Session {
|
p_dawn:Option<u32>,
|
||||||
pub fn new() -> Self
|
p_dusk:Option<u32>,
|
||||||
{
|
specators:Vec<u32>,
|
||||||
Self {
|
|
||||||
key:0,
|
|
||||||
secret:[0; 16],
|
|
||||||
user:None,
|
|
||||||
context:Context::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub struct User {
|
pub struct User {
|
||||||
handle:String,
|
pub id:u32,
|
||||||
secret:String,
|
pub handle:String,
|
||||||
na_key:usize,
|
pub secret:Vec<u8>,
|
||||||
|
pub na_key:usize,
|
||||||
}
|
}
|
||||||
|
@ -6,44 +6,12 @@ mod util;
|
|||||||
mod app;
|
mod app;
|
||||||
mod system;
|
mod system;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
mod manager;
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use hyper::{body::Bytes, upgrade::Upgraded};
|
use hyper::body::Bytes;
|
||||||
use system::{cache::WebCache, net::Stream};
|
|
||||||
use tokio_stream::StreamExt;
|
|
||||||
use tokio_tungstenite::WebSocketStream;
|
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
//use tokio_rustls::server::TlsStream;
|
use system::{cache::WebCache, net::Stream};
|
||||||
//use tokio::net::TcpStream;
|
|
||||||
|
|
||||||
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::QAuth(_request) => {
|
|
||||||
let 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,
|
|
||||||
} { }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct HttpServiceArgs {
|
struct HttpServiceArgs {
|
||||||
@ -51,66 +19,12 @@ struct HttpServiceArgs {
|
|||||||
cache:WebCache,
|
cache:WebCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_ws(mut ws_stream:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceArgs)
|
|
||||||
{
|
|
||||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
|
||||||
use game::util::unpack_u16;
|
|
||||||
use protocol::QRPacket;
|
|
||||||
use game::protocol::{
|
|
||||||
code::*,
|
|
||||||
packet::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("ws ready");
|
|
||||||
|
|
||||||
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,
|
|
||||||
} { }
|
|
||||||
|
|
||||||
ws_stream.close(None).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<http_body_util::Full<Bytes>>, std::convert::Infallible>
|
async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<http_body_util::Full<Bytes>>, std::convert::Infallible>
|
||||||
|
// Serve cached files and upgrade websocket connections.
|
||||||
|
//
|
||||||
{
|
{
|
||||||
use hyper::{Response, body::Bytes, header::{CONTENT_TYPE, CACHE_CONTROL}}; //SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_KEY, UPGRADE
|
use hyper::{Response, body::Bytes, header::{CONTENT_TYPE, CACHE_CONTROL}};
|
||||||
use http_body_util::Full;
|
use http_body_util::Full;
|
||||||
//use tokio_tungstenite::accept_async;
|
|
||||||
//use tokio_tungstenite::tungstenite::handshake::derive_accept_key;
|
|
||||||
|
|
||||||
println!("Serving: {}", request.uri().path());
|
println!("Serving: {}", request.uri().path());
|
||||||
|
|
||||||
@ -131,37 +45,12 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if hyper_tungstenite::is_upgrade_request(&request) {
|
if hyper_tungstenite::is_upgrade_request(&request) {
|
||||||
//if request.headers().get(UPGRADE).map(|h| h == "websocket").unwrap_or(false) {
|
|
||||||
//let response = create_response_with_body(&request, || Full::new(Bytes::new())).unwrap();
|
|
||||||
|
|
||||||
//let key = request.headers().get(SEC_WEBSOCKET_KEY)
|
|
||||||
// .and_then(|v| v.to_str().ok())
|
|
||||||
// .map(|k| derive_accept_key(k.as_bytes()))
|
|
||||||
// .unwrap_or_default();
|
|
||||||
|
|
||||||
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
match websocket.await {
|
match websocket.await {
|
||||||
Ok(websocket) => handle_ws(websocket, args).await,
|
Ok(websocket) => manager::handle_ws(websocket, args).await,
|
||||||
Err(_) => { }
|
Err(_) => Err(()),
|
||||||
}
|
}.ok()
|
||||||
//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) => {
|
|
||||||
println!("here");
|
|
||||||
handle_ws(ws_stream, args).await
|
|
||||||
}
|
|
||||||
Err(e) => { println!("ws not accepted: {}", e.to_string()); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => { println!("transfer error"); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Er(e) => { println!("upgrade error: {}", e.to_string()); }*/
|
|
||||||
//}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@ -171,14 +60,6 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
.body(Full::new(Bytes::new()))
|
.body(Full::new(Bytes::new()))
|
||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ok(Response::builder()
|
|
||||||
// .status(101)
|
|
||||||
// .header(CONNECTION, "Upgrade")
|
|
||||||
// .header(UPGRADE, "websocket")
|
|
||||||
// .header(SEC_WEBSOCKET_ACCEPT, key)
|
|
||||||
// .body(Full::new(Bytes::new()))
|
|
||||||
// .unwrap())
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::builder()
|
Ok(Response::builder()
|
||||||
.header(CONTENT_TYPE, "text/html")
|
.header(CONTENT_TYPE, "text/html")
|
||||||
@ -190,6 +71,8 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_http(stream:system::net::tls::TlsStream, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()>
|
async fn handle_http(stream:system::net::tls::TlsStream, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()>
|
||||||
|
// Hand off socket connection to Hyper server.
|
||||||
|
//
|
||||||
{
|
{
|
||||||
use hyper::server::conn::http1;
|
use hyper::server::conn::http1;
|
||||||
use hyper::service::service_fn;
|
use hyper::service::service_fn;
|
||||||
@ -229,7 +112,7 @@ async fn main()
|
|||||||
match b_main.connect() {
|
match b_main.connect() {
|
||||||
Ok(bus) => {
|
Ok(bus) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
thread_datasystem(app, bus).await;
|
manager::thread_system(app, bus).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
199
server/src/manager/data.rs
Normal file
199
server/src/manager/data.rs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
use bus::Bus;
|
||||||
|
use crate::{
|
||||||
|
app::{
|
||||||
|
App,
|
||||||
|
authentication::Authentication,
|
||||||
|
user::User,
|
||||||
|
connection::Connection,
|
||||||
|
},
|
||||||
|
protocol,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||||
|
{
|
||||||
|
use protocol::*;
|
||||||
|
use protocol::QRPacketData::*;
|
||||||
|
use ring::rand::{SecureRandom, SystemRandom};
|
||||||
|
|
||||||
|
let rng = SystemRandom::new();
|
||||||
|
let argon_config = argon2::Config::default();
|
||||||
|
|
||||||
|
while match bus.receive_wait() {
|
||||||
|
Some(packet) => {
|
||||||
|
let qr = &packet.data;
|
||||||
|
match &qr.data {
|
||||||
|
QConn(request) => {
|
||||||
|
let id = app.connections.add(Connection {
|
||||||
|
bus: request.bus_id,
|
||||||
|
auth: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Connect: {}", id);
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(id as u32, RConn)).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
QDisconn => {
|
||||||
|
app.connections.remove(qr.id as usize).ok();
|
||||||
|
|
||||||
|
println!("Disconnect: {}", qr.id);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegister(request) => {
|
||||||
|
let mut response = PacketRegisterResponse::new();
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
|
println!("Request: Register");
|
||||||
|
|
||||||
|
let mut is_valid = true;
|
||||||
|
if request.code != "abc".as_bytes() { response.status = STATUS_BAD_CODE; is_valid = false; }
|
||||||
|
if is_valid && request.handle.len() == 0 { response.status = STATUS_BAD_HANDLE; is_valid = false; }
|
||||||
|
if is_valid && request.secret.len() == 0 { response.status = STATUS_BAD_SECRET; is_valid = false; }
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
match app.user_handle.get(request.handle.as_bytes()) {
|
||||||
|
None => {
|
||||||
|
let mut salt = [0u8; 16];
|
||||||
|
match rng.fill(&mut salt) {
|
||||||
|
Ok(_) => {
|
||||||
|
let salt_id = app.salts.len();
|
||||||
|
app.salts.push(salt);
|
||||||
|
|
||||||
|
if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
||||||
|
let user_id = app.user_next;
|
||||||
|
app.user_next += 1;
|
||||||
|
|
||||||
|
// Create user entry
|
||||||
|
let user_pos = app.users.add(User {
|
||||||
|
id:user_id,
|
||||||
|
handle:request.handle.clone(),
|
||||||
|
secret:hash,
|
||||||
|
na_key:salt_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register user pool id and handle
|
||||||
|
app.user_id.set(user_id as isize, user_pos);
|
||||||
|
app.user_handle.set(request.handle.as_bytes(), user_id);
|
||||||
|
|
||||||
|
println!("Registered user '{}' @ {} with id {}", request.handle, user_pos, user_id);
|
||||||
|
|
||||||
|
// Generate authentication token and secret
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
rng.fill(&mut response.secret).ok();
|
||||||
|
loop {
|
||||||
|
rng.fill(&mut response.token).ok();
|
||||||
|
|
||||||
|
if app.auths.get(&response.token).is_none() {
|
||||||
|
app.auths.set(&response.token, Authentication {
|
||||||
|
key:response.token,
|
||||||
|
secret:response.secret,
|
||||||
|
user:user_id,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach authentication to connection
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
conn.auth = Some(response.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: failed to generate salt.") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
response.status = STATUS_BAD_HANDLE;
|
||||||
|
println!("notice: attempt to register existing handle: '{}'", request.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RRegister(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
QAuth(request) => {
|
||||||
|
let mut response = PacketAuthResponse::new();
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
|
println!("Request: Auth");
|
||||||
|
|
||||||
|
let mut is_valid = true;
|
||||||
|
if is_valid && request.handle.len() == 0 { response.status = STATUS_BAD_HANDLE; is_valid = false; }
|
||||||
|
if is_valid && request.secret.len() == 0 { response.status = STATUS_BAD_SECRET; is_valid = false; }
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
|
||||||
|
// Get user data from handle
|
||||||
|
match app.user_handle.get(request.handle.as_bytes()) {
|
||||||
|
Some(uid) => {
|
||||||
|
if let Some(tuid) = app.user_id.get(*uid as isize) {
|
||||||
|
if let Some(user) = app.users.get(*tuid) {
|
||||||
|
// Get user salt
|
||||||
|
if let Some(salt) = app.salts.get(user.na_key) {
|
||||||
|
|
||||||
|
// Verify salted secret against user data
|
||||||
|
if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) {
|
||||||
|
println!("Authenticated user '{}' id {}", request.handle, *uid);
|
||||||
|
|
||||||
|
// Generate authentication token and secret
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
rng.fill(&mut response.secret).ok();
|
||||||
|
loop {
|
||||||
|
rng.fill(&mut response.token).ok();
|
||||||
|
|
||||||
|
if app.auths.get(&response.token).is_none() {
|
||||||
|
app.auths.set(&response.token, Authentication {
|
||||||
|
key:response.token,
|
||||||
|
secret:response.secret,
|
||||||
|
user:*uid,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach authentication to connection
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
conn.auth = Some(response.token);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("notice: password verification failed.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("error: user salt id '{}' not found.", user.na_key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("error: user with id '{}' not found.", uid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("error: user with id '{}' not found.", uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RAuth(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
QDeauth => {
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
match conn.auth {
|
||||||
|
Some(auth) => {
|
||||||
|
println!("Deauthenticated connection: {}", qr.id);
|
||||||
|
app.auths.unset(&auth);
|
||||||
|
}
|
||||||
|
None => { }
|
||||||
|
}
|
||||||
|
conn.auth = None;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => { true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
} { }
|
||||||
|
}
|
2
server/src/manager/mod.rs
Normal file
2
server/src/manager/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod data; pub use data::*;
|
||||||
|
mod ws; pub use ws::*;
|
127
server/src/manager/ws.rs
Normal file
127
server/src/manager/ws.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use hyper::upgrade::Upgraded;
|
||||||
|
use hyper_util::rt::TokioIo;
|
||||||
|
use tokio_tungstenite::WebSocketStream;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol,
|
||||||
|
util::pack::{pack_u16, unpack_u16},
|
||||||
|
HttpServiceArgs,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
pack_u16(code),
|
||||||
|
data,
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceArgs) -> Result<(),()>
|
||||||
|
// Handle websocket connection.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||||
|
use protocol::{QRPacket, QRPacketData::*, code::*, packet::*};
|
||||||
|
|
||||||
|
let conn_id :u32;
|
||||||
|
|
||||||
|
let bus_ds = args.bus.mailbox(1).unwrap_or(1);
|
||||||
|
|
||||||
|
// Perform connection handshake with data system.
|
||||||
|
// - Provide system with connection/bus pairing.
|
||||||
|
// - Acquire connection id.
|
||||||
|
//
|
||||||
|
args.bus.send(bus_ds, QRPacket::new(0, QConn(LocalPacketConnect {
|
||||||
|
bus_id:args.bus.id(),
|
||||||
|
})))?;
|
||||||
|
match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match qr.data {
|
||||||
|
RConn => { conn_id = qr.id; }
|
||||||
|
_ => { return Err(()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => { return Err(()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode client requests from websocket,
|
||||||
|
// pass requests to data system,
|
||||||
|
// and return responses to client.
|
||||||
|
while match ws.next().await {
|
||||||
|
Some(msg) => match msg {
|
||||||
|
Ok(msg) => match msg {
|
||||||
|
Message::Binary(data) => {
|
||||||
|
let mut index :usize = 0;
|
||||||
|
let code: u16 = unpack_u16(&data, &mut index);
|
||||||
|
match code {
|
||||||
|
|
||||||
|
CODE_REGISTER => match PacketRegister::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QRegister(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RRegister(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { }
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_AUTH => match PacketAuth::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuth(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RAuth(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { }
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_DEAUTH => {
|
||||||
|
args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("notice: received unexpected websocket data.");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
} { }
|
||||||
|
|
||||||
|
args.bus.send(bus_ds, QRPacket::new(conn_id, QDisconn)).ok();
|
||||||
|
ws.close(None).await.ok();
|
||||||
|
Ok(())
|
||||||
|
}
|
24
server/src/protocol/code.rs
Normal file
24
server/src/protocol/code.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub const STATUS_OK :u16 = 0x0000;
|
||||||
|
pub const STATUS_ERROR :u16 = 0x0001;
|
||||||
|
|
||||||
|
pub const STATUS_BAD_HANDLE :u16 = 0x0001;
|
||||||
|
pub const STATUS_BAD_SECRET :u16 = 0x0002;
|
||||||
|
pub const STATUS_BAD_CODE :u16 = 0x0003;
|
||||||
|
|
||||||
|
pub const STATUS_NOT_IMPL :u16 = 0x00FF;
|
||||||
|
|
||||||
|
pub const CODE_REGISTER :u16 = 0x0010;
|
||||||
|
pub const CODE_AUTH :u16 = 0x0011;
|
||||||
|
pub const CODE_DEAUTH :u16 = 0x0013;
|
||||||
|
pub const CODE_AUTH_RESUME :u16 = 0x0012;
|
||||||
|
|
||||||
|
pub const CODE_LIST_SESSION :u16 = 0x0010;
|
||||||
|
|
||||||
|
pub const CODE_SESSION_JOIN :u16 = 0x0020;
|
||||||
|
pub const CODE_SESSION_SPECTATE :u16 = 0x0021;
|
||||||
|
pub const CODE_SESSION_LEAVE :u16 = 0x0022;
|
||||||
|
pub const CODE_SESSION_RETIRE :u16 = 0x0023;
|
||||||
|
|
||||||
|
pub const CODE_GAME_PLAY :u16 = 0x0030;
|
@ -1,7 +1,37 @@
|
|||||||
use game::protocol::packet::*;
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod code; pub use code::*;
|
||||||
|
pub mod packet; pub use packet::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum QRPacket {
|
pub enum QRPacketData {
|
||||||
|
ROk,
|
||||||
|
|
||||||
|
QConn(LocalPacketConnect),
|
||||||
|
RConn,
|
||||||
|
|
||||||
|
QDisconn,
|
||||||
|
|
||||||
|
QRegister(PacketRegister),
|
||||||
|
RRegister(PacketRegisterResponse),
|
||||||
|
|
||||||
QAuth(PacketAuth),
|
QAuth(PacketAuth),
|
||||||
RAuth(PacketAuthResponse),
|
RAuth(PacketAuthResponse),
|
||||||
|
|
||||||
|
QDeauth,
|
||||||
|
|
||||||
|
QAuthResume,
|
||||||
|
RAuthResume,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct QRPacket {
|
||||||
|
pub id:u32,
|
||||||
|
pub data:QRPacketData,
|
||||||
|
}
|
||||||
|
impl QRPacket {
|
||||||
|
pub fn new(id:u32, data:QRPacketData) -> Self
|
||||||
|
{
|
||||||
|
Self { id, data }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::util::{pack_u16, unpack_u16};
|
use crate::util::pack::{pack_u16, unpack_u16};
|
||||||
|
|
||||||
use super::Packet;
|
use super::Packet;
|
||||||
|
|
||||||
@ -19,16 +19,6 @@ impl PacketAuth {
|
|||||||
impl Packet for PacketAuth {
|
impl Packet for PacketAuth {
|
||||||
type Data = Self;
|
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, ()>
|
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||||
{
|
{
|
||||||
let mut result = Self::new();
|
let mut result = Self::new();
|
||||||
@ -65,7 +55,7 @@ impl Packet for PacketAuth {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PacketAuthResponse {
|
pub struct PacketAuthResponse {
|
||||||
pub status:bool,
|
pub status:u16,
|
||||||
pub token:[u8; 8],
|
pub token:[u8; 8],
|
||||||
pub secret:[u8; 16],
|
pub secret:[u8; 16],
|
||||||
}
|
}
|
||||||
@ -73,7 +63,7 @@ impl PacketAuthResponse {
|
|||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
status:false,
|
status:0,
|
||||||
token:[0; 8],
|
token:[0; 8],
|
||||||
secret:[0; 16],
|
secret:[0; 16],
|
||||||
}
|
}
|
||||||
@ -84,11 +74,10 @@ impl Packet for PacketAuthResponse {
|
|||||||
|
|
||||||
fn encode(&self) -> Vec<u8>
|
fn encode(&self) -> Vec<u8>
|
||||||
{
|
{
|
||||||
vec![]
|
[
|
||||||
}
|
pack_u16(self.status),
|
||||||
|
self.token.to_vec(),
|
||||||
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()>
|
self.secret.to_vec(),
|
||||||
{
|
].concat()
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
4
server/src/protocol/packet/connect.rs
Normal file
4
server/src/protocol/packet/connect.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct LocalPacketConnect {
|
||||||
|
pub bus_id:u32,
|
||||||
|
}
|
12
server/src/protocol/packet/mod.rs
Normal file
12
server/src/protocol/packet/mod.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
mod connect; pub use connect::*;
|
||||||
|
mod register; pub use register::*;
|
||||||
|
mod authenticate; pub use authenticate::*;
|
||||||
|
|
||||||
|
mod prelude {
|
||||||
|
pub trait Packet {
|
||||||
|
type Data;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8> { Vec::new() }
|
||||||
|
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()> { Err(()) }
|
||||||
|
}
|
||||||
|
} pub use prelude::*;
|
83
server/src/protocol/packet/register.rs
Normal file
83
server/src/protocol/packet/register.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use crate::util::pack::{pack_u16, unpack_u16};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketRegister {
|
||||||
|
pub handle:String,
|
||||||
|
pub secret:Vec<u8>,
|
||||||
|
pub code:Vec<u8>,
|
||||||
|
}
|
||||||
|
impl PacketRegister {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
handle:String::new(),
|
||||||
|
secret:Vec::new(),
|
||||||
|
code:Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketRegister {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
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 length > 0 && data.len() >= *index + length {
|
||||||
|
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
||||||
|
Ok(text) => {
|
||||||
|
result.handle = text;
|
||||||
|
*index += length;
|
||||||
|
}
|
||||||
|
Err(_) => { return Err(()) }
|
||||||
|
}
|
||||||
|
} else { return Err(()); }
|
||||||
|
|
||||||
|
length = unpack_u16(data, index) as usize;
|
||||||
|
if length > 0 && data.len() >= *index + length {
|
||||||
|
result.secret = data[*index..*index+length].to_vec();
|
||||||
|
*index += length;
|
||||||
|
} else { return Err(()); }
|
||||||
|
|
||||||
|
length = unpack_u16(data, index) as usize;
|
||||||
|
if length > 0 && data.len() >= *index + length {
|
||||||
|
result.code = data[*index..*index+length].to_vec();
|
||||||
|
*index += length;
|
||||||
|
} else { return Err(()); }
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketRegisterResponse {
|
||||||
|
pub status:u16,
|
||||||
|
pub token:[u8; 8],
|
||||||
|
pub secret:[u8; 16],
|
||||||
|
}
|
||||||
|
impl PacketRegisterResponse {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
status:0,
|
||||||
|
token:[0; 8],
|
||||||
|
secret:[0; 16],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketRegisterResponse {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
pack_u16(self.status),
|
||||||
|
self.token.to_vec(),
|
||||||
|
self.secret.to_vec(),
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ impl CertificateStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if certs.is_ok() && key.is_ok() {
|
if certs.is_ok() && key.is_ok() {
|
||||||
self.certs.set(domain, Arc::new(Certificate {
|
self.certs.set(domain.as_bytes(), Arc::new(Certificate {
|
||||||
certs:certs.unwrap(),
|
certs:certs.unwrap(),
|
||||||
key:key.unwrap(),
|
key:key.unwrap(),
|
||||||
}));
|
}));
|
||||||
@ -70,7 +70,7 @@ impl CertificateStore {
|
|||||||
|
|
||||||
pub fn remove(&mut self, domain:&str) -> Result<(),()>
|
pub fn remove(&mut self, domain:&str) -> Result<(),()>
|
||||||
{
|
{
|
||||||
if self.certs.unset(domain) {
|
if self.certs.unset(domain.as_bytes()) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
@ -79,8 +79,8 @@ impl CertificateStore {
|
|||||||
|
|
||||||
pub fn get(&self, domain:&str) -> Result<Arc<Certificate>, ()>
|
pub fn get(&self, domain:&str) -> Result<Arc<Certificate>, ()>
|
||||||
{
|
{
|
||||||
match self.certs.get(domain) {
|
match self.certs.get(domain.as_bytes()) {
|
||||||
Some(certs) => Ok(certs),
|
Some(certs) => Ok(certs.clone()),
|
||||||
None => Err(())
|
None => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
|
pub mod pack;
|
||||||
|
120
www/.css
120
www/.css
@ -47,8 +47,9 @@ body>nav>button{
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
background-color:#282828;
|
background-color:#282828;
|
||||||
border:0;
|
|
||||||
color:#c0c0c0;
|
color:#c0c0c0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
} body>nav>button:hover{
|
} body>nav>button:hover{
|
||||||
background-color:#383838;
|
background-color:#383838;
|
||||||
color:#e0e0e0;
|
color:#e0e0e0;
|
||||||
@ -71,8 +72,11 @@ body>nav>header{
|
|||||||
}
|
}
|
||||||
|
|
||||||
main{
|
main{
|
||||||
display:block;
|
display:flex;
|
||||||
position:relative;
|
position:relative;
|
||||||
|
flex-flow:column nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-start;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
|
||||||
@ -124,8 +128,9 @@ main>nav>section>button{
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
background-color:#282828;
|
background-color:#282828;
|
||||||
border:0;
|
|
||||||
color:#c0c0c0;
|
color:#c0c0c0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
}
|
}
|
||||||
main>nav>section>button:hover{
|
main>nav>section>button:hover{
|
||||||
background-color:#383838;
|
background-color:#383838;
|
||||||
@ -146,14 +151,14 @@ main>nav>section>div{
|
|||||||
color:#e0e0e0;
|
color:#e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
main table{
|
main.list table{
|
||||||
width:100%;
|
width:100%;
|
||||||
border-collapse:collapse;
|
border-collapse:collapse;
|
||||||
}
|
}
|
||||||
main table tr{
|
main.list table tr{
|
||||||
height:2.5rem;
|
height:2.5rem;
|
||||||
}
|
}
|
||||||
main table th{
|
main.list table th{
|
||||||
padding:0 1rem 0 1rem;
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
text-align:center;
|
text-align:center;
|
||||||
@ -164,7 +169,7 @@ main table th{
|
|||||||
color:#f0f0f0;
|
color:#f0f0f0;
|
||||||
border-bottom:1px solid #404040;
|
border-bottom:1px solid #404040;
|
||||||
}
|
}
|
||||||
main table td{
|
main.list table td{
|
||||||
height:100%;
|
height:100%;
|
||||||
padding:0 1rem 0 1rem;
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
@ -173,7 +178,7 @@ main table td{
|
|||||||
background-color:#303030;
|
background-color:#303030;
|
||||||
color:#f0f0f0;
|
color:#f0f0f0;
|
||||||
}
|
}
|
||||||
main table td:last-child{
|
main.list table td:last-child{
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-flow:row nowrap;
|
flex-flow:row nowrap;
|
||||||
align-items:flex-start;
|
align-items:flex-start;
|
||||||
@ -181,12 +186,7 @@ main table td:last-child{
|
|||||||
flex-grow:1;
|
flex-grow:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
main table td>img{
|
main.list table td:last-child>button{
|
||||||
height:2rem;
|
|
||||||
vertical-align:middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
main table td:last-child>button{
|
|
||||||
display:block;
|
display:block;
|
||||||
position:relative;
|
position:relative;
|
||||||
width:auto;
|
width:auto;
|
||||||
@ -198,14 +198,15 @@ main table td:last-child>button{
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
background-color:#303030;
|
background-color:#303030;
|
||||||
border:0;
|
|
||||||
color:#e0e0e0;
|
color:#e0e0e0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
}
|
}
|
||||||
main table td:last-child>button:hover{
|
main.list table td:last-child>button:hover{
|
||||||
background-color:#343434;
|
background-color:#343434;
|
||||||
}
|
}
|
||||||
|
|
||||||
main>canvas{
|
main.game>canvas{
|
||||||
display:block;
|
display:block;
|
||||||
position:relative;
|
position:relative;
|
||||||
width:100%;
|
width:100%;
|
||||||
@ -242,6 +243,7 @@ main.game>div.sidemenu>button{
|
|||||||
background-color:#202020;
|
background-color:#202020;
|
||||||
color:#F0F0F0;
|
color:#F0F0F0;
|
||||||
border:0;
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
|
||||||
font-family:sans-serif;
|
font-family:sans-serif;
|
||||||
font-size:1rem;
|
font-size:1rem;
|
||||||
@ -255,4 +257,88 @@ main.game>div.sidemenu>button.warn:hover{
|
|||||||
background-color:#602020;
|
background-color:#602020;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main.form>section{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:column nowrap;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
width:100%;
|
||||||
|
height:auto;
|
||||||
|
flex-grow:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
max-width:30rem;
|
||||||
|
padding:0.5rem;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table label{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:auto;
|
||||||
|
height:auto;
|
||||||
|
padding:0 0.5rem 0 0.5rem;
|
||||||
|
|
||||||
|
text-align:right;
|
||||||
|
font-size:1.2rem;
|
||||||
|
|
||||||
|
color:#e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table input{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:2.5rem;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.15rem;
|
||||||
|
|
||||||
|
background-color:#202020;
|
||||||
|
color:#e0e0e0;
|
||||||
|
border:1px solid #303030;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main.form>section>div>table input.error{
|
||||||
|
border:1px solid #603030;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>button{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:2.5rem;
|
||||||
|
padding:0.5rem 1rem 0.5rem 1rem;
|
||||||
|
|
||||||
|
font-size:1.2rem;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
color:#e0e0e0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main.form>section>div>button.error{
|
||||||
|
border:1px solid #603030;
|
||||||
|
}
|
||||||
|
main.form>section>div>button:hover{
|
||||||
|
background-color:#383838;
|
||||||
|
}
|
||||||
|
main.form>section>div>button:disabled{
|
||||||
|
background-color:#2c2c2c;
|
||||||
|
color:#606060;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
span.text-system{color:#909090;}
|
span.text-system{color:#909090;}
|
||||||
|
778
www/.js
778
www/.js
@ -4,28 +4,94 @@ let SCENE = null;
|
|||||||
|
|
||||||
let CONNECTED = false;
|
let CONNECTED = false;
|
||||||
let SOCKET = null;
|
let SOCKET = null;
|
||||||
let USER = null;
|
let CONTEXT = {
|
||||||
let CONTEXT = null;
|
Scene: null,
|
||||||
|
Auth: null,
|
||||||
|
Data: null,
|
||||||
|
};
|
||||||
|
|
||||||
let UI = {
|
const Status = {
|
||||||
text:function(value) {
|
Ok: 0,
|
||||||
|
Error: 1,
|
||||||
|
NotImplement: 2,
|
||||||
|
|
||||||
|
BadHandle: 1,
|
||||||
|
BadSecret: 2,
|
||||||
|
BadCode: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpCode = {
|
||||||
|
Register :0x0010,
|
||||||
|
Authenticate :0x0011,
|
||||||
|
Resume :0x0012,
|
||||||
|
Deauthenticate :0x0013,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
constructor(code, data) {
|
||||||
|
this.code = code;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACK = {
|
||||||
|
u8:(value) => {
|
||||||
|
return new Uint8Array([ value & 0xFF ]);
|
||||||
|
},
|
||||||
|
u16:(value) => {
|
||||||
|
return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]);
|
||||||
|
},
|
||||||
|
u32:(value) => {
|
||||||
|
return new Uint8Array([
|
||||||
|
(value >> 24) & 0xFF,
|
||||||
|
(value >> 16) & 0xFF,
|
||||||
|
(value >> 8) & 0xFF,
|
||||||
|
value & 0xFF
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const UI = {
|
||||||
|
text:(value) => {
|
||||||
return document.createTextNode(value);
|
return document.createTextNode(value);
|
||||||
},
|
},
|
||||||
|
|
||||||
button:function(text, callback) {
|
button:(text, callback) => {
|
||||||
let b = document.createElement("button");
|
let button = document.createElement("button");
|
||||||
b.innerText = text;
|
button.innerText = text;
|
||||||
if(callback !== null) { b.addEventListener("click", callback); }
|
if(callback !== null) { button.addEventListener("click", callback); }
|
||||||
return b;
|
return button;
|
||||||
},
|
},
|
||||||
|
|
||||||
div:function(children) {
|
textbox:(id, placeholder) => {
|
||||||
|
let input = document.createElement("input");
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
if(id !== null) { input.setAttribute("id", id); }
|
||||||
|
input.setAttribute("placeholder", placeholder);
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
password:(id) => {
|
||||||
|
let input = document.createElement("input");
|
||||||
|
input.setAttribute("type", "password");
|
||||||
|
if(id !== null) { input.setAttribute("id", id); }
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
label:(name, id) => {
|
||||||
|
let label = document.createElement("label");
|
||||||
|
label.setAttribute("for", id);
|
||||||
|
label.innerText = name;
|
||||||
|
return label;
|
||||||
|
},
|
||||||
|
|
||||||
|
div:(children) => {
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
for(child of children) { div.appendChild(child); }
|
for(child of children) { div.appendChild(child); }
|
||||||
return div;
|
return div;
|
||||||
},
|
},
|
||||||
|
|
||||||
table:function(header, rows) {
|
table:(header, rows) => {
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
let tbody = document.createElement("tbody");
|
let tbody = document.createElement("tbody");
|
||||||
|
|
||||||
@ -54,9 +120,15 @@ let UI = {
|
|||||||
return table;
|
return table;
|
||||||
},
|
},
|
||||||
|
|
||||||
mainnav:function(left_children, right_children) {
|
mainnav:(left_children, right_children) => {
|
||||||
let header = document.createElement("nav");
|
let header = document.createElement("nav");
|
||||||
let left = document.createElement("section");
|
let left = document.createElement("section");
|
||||||
|
|
||||||
|
if(CONTEXT.Auth === null) {
|
||||||
|
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) }));
|
||||||
|
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) }));
|
||||||
|
}
|
||||||
|
|
||||||
for(child of left_children) { left.appendChild(child); }
|
for(child of left_children) { left.appendChild(child); }
|
||||||
|
|
||||||
let right = document.createElement("section");
|
let right = document.createElement("section");
|
||||||
@ -64,27 +136,423 @@ let UI = {
|
|||||||
|
|
||||||
header.appendChild(left);
|
header.appendChild(left);
|
||||||
header.appendChild(right);
|
header.appendChild(right);
|
||||||
return header;
|
|
||||||
|
MAIN.appendChild(header);
|
||||||
},
|
},
|
||||||
|
|
||||||
mainmenu:function() {
|
mainmenu:() => {
|
||||||
if(SOCKET !== null) {
|
if(SOCKET !== null) {
|
||||||
MENU.appendChild(UI.button("Online", function() { LOAD(SCENE.Online); }));
|
MENU.appendChild(UI.button("Online", () => { LOAD(SCENES.Online); }));
|
||||||
MENU.appendChild(UI.button("Continue", function() { LOAD(SCENE.Continue); }));
|
if(CONTEXT.Auth !== null) {
|
||||||
MENU.appendChild(UI.button("Join", function() { LOAD(SCENE.Continue); }));
|
MENU.appendChild(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
||||||
MENU.appendChild(UI.button("Live", function() { LOAD(SCENE.Continue); }));
|
MENU.appendChild(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
||||||
MENU.appendChild(UI.button("History", function() { LOAD(SCENE.Continue); }));
|
}
|
||||||
MENU.appendChild(UI.button("Guide", function() { LOAD(SCENE.Continue); }));
|
MENU.appendChild(UI.button("Live", () => { LOAD(SCENES.Live); }));
|
||||||
MENU.appendChild(UI.button("About", function() { LOAD(SCENE.Continue); }));
|
MENU.appendChild(UI.button("History", () => { LOAD(SCENES.History); }));
|
||||||
|
MENU.appendChild(UI.button("Guide", () => { LOAD(SCENES.Guide); }));
|
||||||
|
MENU.appendChild(UI.button("About", () => { LOAD(SCENES.About); }));
|
||||||
|
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
MENU.appendChild(UI.button("Logout", () => {
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.Deauthenticate),
|
||||||
|
]);
|
||||||
|
CONTEXT.Auth = null;
|
||||||
|
LOAD(SCENE);
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SCENES = {
|
||||||
|
Init:{
|
||||||
|
load:() => {
|
||||||
|
LOAD_OFFLINE();
|
||||||
|
CONTEXT.Scene = SCENES.Online;
|
||||||
|
RECONNECT();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Offline:{
|
||||||
|
load:() => {
|
||||||
|
MENU.appendChild(UI.button("Reconnect", () => { RECONNECT(); }))
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Register:{
|
||||||
|
load:() => {
|
||||||
|
if(CONTEXT.Auth !== null) return false;
|
||||||
|
UI.mainmenu();
|
||||||
|
UI.mainnav([], []);
|
||||||
|
|
||||||
|
let container = document.createElement("section");
|
||||||
|
let form = document.createElement("div");
|
||||||
|
form.appendChild(UI.table(null, [
|
||||||
|
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
|
||||||
|
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
||||||
|
[ UI.label("Code", "code"), UI.password("code") ],
|
||||||
|
]));
|
||||||
|
|
||||||
|
let button = UI.button("Register", (event) => {
|
||||||
|
let handle = document.getElementById("handle");
|
||||||
|
let secret = document.getElementById("secret");
|
||||||
|
let code = document.getElementById("code");
|
||||||
|
|
||||||
|
handle.removeAttribute("class");
|
||||||
|
secret.removeAttribute("class");
|
||||||
|
code.removeAttribute("class");
|
||||||
|
event.target.removeAttribute("class");
|
||||||
|
|
||||||
|
if(handle.value.length > 0 && secret.value.length > 0 && code.value.length > 0) {
|
||||||
|
event.target.setAttribute("disabled", "");
|
||||||
|
|
||||||
|
let enc = new TextEncoder();
|
||||||
|
let enc_handle = enc.encode(handle.value);
|
||||||
|
let enc_secret = enc.encode(secret.value);
|
||||||
|
let enc_code = enc.encode(code.value);
|
||||||
|
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.Register),
|
||||||
|
PACK.u16(enc_handle.length),
|
||||||
|
enc_handle,
|
||||||
|
PACK.u16(enc_secret.length),
|
||||||
|
enc_secret,
|
||||||
|
PACK.u16(enc_code.length),
|
||||||
|
enc_code,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
|
||||||
|
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
|
||||||
|
if(code.value.length == 0) { code.setAttribute("class", "error"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button.setAttribute("id", "submit");
|
||||||
|
form.appendChild(button);
|
||||||
|
|
||||||
|
container.appendChild(form);
|
||||||
|
MAIN.appendChild(container);
|
||||||
|
MAIN.setAttribute("class", "form");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
message:(code, data) => {
|
||||||
|
if(code == OpCode.Register && data !== null) {
|
||||||
|
let submit = document.getElementById("submit");
|
||||||
|
switch(data.status) {
|
||||||
|
case Status.Ok: {
|
||||||
|
CONTEXT.Auth = data;
|
||||||
|
LOAD(SCENES.Online);
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
submit.removeAttribute("disabled");
|
||||||
|
switch(data.status) {
|
||||||
|
case Status.BadHandle: {
|
||||||
|
document.getElementById("handle").setAttribute("class", "error");
|
||||||
|
submit.setAttribute("class", "error");
|
||||||
|
} break;
|
||||||
|
case Status.BadSecret: {
|
||||||
|
document.getElementById("secret").setAttribute("class", "error");
|
||||||
|
submit.setAttribute("class", "error");
|
||||||
|
} break;
|
||||||
|
case Status.BadCode: {
|
||||||
|
document.getElementById("code").setAttribute("class", "error");
|
||||||
|
submit.setAttribute("class", "error");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Authenticate:{
|
||||||
|
load:() => {
|
||||||
|
if(CONTEXT.Auth !== null) return false;
|
||||||
|
UI.mainmenu();
|
||||||
|
UI.mainnav([], []);
|
||||||
|
|
||||||
|
let container = document.createElement("section");
|
||||||
|
let form = document.createElement("div");
|
||||||
|
form.appendChild(UI.table(null, [
|
||||||
|
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
|
||||||
|
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
||||||
|
]));
|
||||||
|
|
||||||
|
let button = UI.button("Register", (event) => {
|
||||||
|
let handle = document.getElementById("handle");
|
||||||
|
let secret = document.getElementById("secret");
|
||||||
|
|
||||||
|
handle.removeAttribute("class");
|
||||||
|
secret.removeAttribute("class");
|
||||||
|
event.target.removeAttribute("class");
|
||||||
|
|
||||||
|
if(handle.value.length > 0 && secret.value.length > 0) {
|
||||||
|
event.target.setAttribute("disabled", "");
|
||||||
|
|
||||||
|
let enc = new TextEncoder();
|
||||||
|
let enc_handle = enc.encode(handle.value);
|
||||||
|
let enc_secret = enc.encode(secret.value);
|
||||||
|
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.Authenticate),
|
||||||
|
PACK.u16(enc_handle.length),
|
||||||
|
enc_handle,
|
||||||
|
PACK.u16(enc_secret.length),
|
||||||
|
enc_secret,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
|
||||||
|
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button.setAttribute("id", "submit");
|
||||||
|
form.appendChild(button);
|
||||||
|
|
||||||
|
container.appendChild(form);
|
||||||
|
MAIN.appendChild(container);
|
||||||
|
MAIN.setAttribute("class", "form");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
message:(code, data) => {
|
||||||
|
if(code == OpCode.Authenticate && data !== null) {
|
||||||
|
let submit = document.getElementById("submit");
|
||||||
|
switch(data.status) {
|
||||||
|
case Status.Ok: {
|
||||||
|
CONTEXT.Auth = data;
|
||||||
|
LOAD(SCENES.Online);
|
||||||
|
} break;
|
||||||
|
case Status.Error: {
|
||||||
|
submit.removeAttribute("disabled");
|
||||||
|
|
||||||
|
document.getElementById("handle").setAttribute("class", "error");
|
||||||
|
document.getElementById("secret").setAttribute("class", "error");
|
||||||
|
submit.setAttribute("class", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Online:{
|
||||||
|
load:() => {
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
|
let left_buttons = [ ];
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
left_buttons.push(UI.button("Start", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.mainnav(
|
||||||
|
left_buttons,
|
||||||
|
[
|
||||||
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
|
UI.button("◀", null),
|
||||||
|
UI.button("▶", null),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.setAttribute("id", "content");
|
||||||
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
|
MAIN.setAttribute("class", "list");
|
||||||
|
|
||||||
|
SCENE.refresh();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
let request = new Uint8Array();
|
||||||
|
|
||||||
|
//SERVER.send()
|
||||||
|
|
||||||
|
SCENE.message(0, 0, null);
|
||||||
|
},
|
||||||
|
message:(code, data) => {
|
||||||
|
let table = document.getElementById("content");
|
||||||
|
MAIN.removeChild(table);
|
||||||
|
|
||||||
|
let rows = [
|
||||||
|
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
|
||||||
|
];
|
||||||
|
|
||||||
|
MAIN.appendChild(UI.table(
|
||||||
|
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
|
||||||
|
rows,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Continue:{
|
||||||
|
load:() => {
|
||||||
|
if(CONTEXT.Auth === null) return false;
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
|
let left_buttons = [ ];
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
left_buttons.push(UI.button("Start", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.mainnav(
|
||||||
|
left_buttons,
|
||||||
|
[
|
||||||
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
|
UI.button("◀", null),
|
||||||
|
UI.button("▶", null),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.setAttribute("id", "content");
|
||||||
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
|
MAIN.setAttribute("class", "list");
|
||||||
|
|
||||||
|
SCENE.refresh();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Join:{
|
||||||
|
load:() => {
|
||||||
|
if(CONTEXT.Auth === null) return false;
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
|
let left_buttons = [ ];
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
left_buttons.push(UI.button("Start", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.mainnav(
|
||||||
|
left_buttons,
|
||||||
|
[
|
||||||
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
|
UI.button("◀", null),
|
||||||
|
UI.button("▶", null),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.setAttribute("id", "content");
|
||||||
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
|
MAIN.setAttribute("class", "list");
|
||||||
|
|
||||||
|
SCENE.refresh();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Live:{
|
||||||
|
load:() => {
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
|
let left_buttons = [ ];
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
left_buttons.push(UI.button("Start", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.mainnav(
|
||||||
|
left_buttons,
|
||||||
|
[
|
||||||
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
|
UI.button("◀", null),
|
||||||
|
UI.button("▶", null),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.setAttribute("id", "content");
|
||||||
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
|
MAIN.setAttribute("class", "list");
|
||||||
|
|
||||||
|
SCENE.refresh();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
History:{
|
||||||
|
load:() => {
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
|
UI.mainnav(
|
||||||
|
[ ],
|
||||||
|
[
|
||||||
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
|
UI.button("◀", null),
|
||||||
|
UI.button("▶", null),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.setAttribute("id", "content");
|
||||||
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
|
MAIN.setAttribute("class", "list");
|
||||||
|
|
||||||
|
SCENE.refresh();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Guide:{
|
||||||
|
load:() => {
|
||||||
|
UI.mainmenu();
|
||||||
|
UI.mainnav([], []);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
About:{
|
||||||
|
load:() => {
|
||||||
|
UI.mainmenu();
|
||||||
|
UI.mainnav([], []);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Game:{
|
||||||
|
load:() => {
|
||||||
|
MENU.appendChild(UI.button("Back", () => { LOAD(SCENES.Online) }));
|
||||||
|
MENU.appendChild(UI.button("Retire", () => { }));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
unload:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
refresh:() => {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function RECONNECT() {
|
function RECONNECT() {
|
||||||
if(SOCKET === null) {
|
if(SOCKET === null) {
|
||||||
console.log("Websocket connecting..");
|
console.log("Websocket connecting..");
|
||||||
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
||||||
SOCKET.binaryType = 'blob';
|
SOCKET.binaryType = "arraybuffer";
|
||||||
SOCKET.addEventListener("error", (event) => {
|
SOCKET.addEventListener("error", (event) => {
|
||||||
SOCKET = null;
|
SOCKET = null;
|
||||||
LOAD(SCENES.Offline)
|
LOAD(SCENES.Offline)
|
||||||
@ -93,9 +561,7 @@ function RECONNECT() {
|
|||||||
if(SOCKET.readyState === WebSocket.OPEN) {
|
if(SOCKET.readyState === WebSocket.OPEN) {
|
||||||
console.log("Websocket connected.");
|
console.log("Websocket connected.");
|
||||||
|
|
||||||
SOCKET.addEventListener("message", (event) => {
|
SOCKET.addEventListener("message", MESSAGE);
|
||||||
MESSAGE(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
SOCKET.addEventListener("close", (event) => {
|
SOCKET.addEventListener("close", (event) => {
|
||||||
console.log("Websocket closed.");
|
console.log("Websocket closed.");
|
||||||
@ -104,171 +570,15 @@ function RECONNECT() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
RESUME();
|
RESUME();
|
||||||
SOCKET.send(new Uint8Array([ 0 ]));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function RESUME() {
|
function RESUME() {
|
||||||
LOAD(SCENES.Online);
|
LOAD(CONTEXT.Scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCENES = {
|
|
||||||
Init:{
|
|
||||||
load:function() {
|
|
||||||
LOAD(SCENES.Offline);
|
|
||||||
RECONNECT();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Offline:{
|
|
||||||
load:function() {
|
|
||||||
MENU.appendChild(UI.button("Reconnect", function() { RECONNECT(); }))
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Online:{
|
|
||||||
load:function() {
|
|
||||||
UI.mainmenu();
|
|
||||||
MAIN.appendChild(UI.mainnav([
|
|
||||||
UI.button("Start", null),
|
|
||||||
], [
|
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
|
||||||
UI.button("◀", null),
|
|
||||||
UI.button("▶", null),
|
|
||||||
]));
|
|
||||||
|
|
||||||
let table = document.createElement("table");
|
|
||||||
table.setAttribute("id", "content");
|
|
||||||
MAIN.appendChild(table);
|
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
let request = new Uint8Array();
|
|
||||||
|
|
||||||
//SERVER.send()
|
|
||||||
|
|
||||||
let cb = function() {
|
|
||||||
let table = document.getElementById("content");
|
|
||||||
MAIN.removeChild(table);
|
|
||||||
|
|
||||||
let data = [
|
|
||||||
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
|
|
||||||
];
|
|
||||||
|
|
||||||
MAIN.appendChild(UI.table(
|
|
||||||
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
|
|
||||||
data,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Continue:{
|
|
||||||
load:function() {
|
|
||||||
if(USER === null) return false;
|
|
||||||
UI.mainmenu();
|
|
||||||
MAIN.appendChild(UI.mainnav([
|
|
||||||
UI.button("Start", null),
|
|
||||||
], [
|
|
||||||
UI.button("◀", null),
|
|
||||||
UI.button("▶", null),
|
|
||||||
]));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Join:{
|
|
||||||
load:function() {
|
|
||||||
if(USER === null) return false;
|
|
||||||
UI.mainmenu();
|
|
||||||
MAIN.appendChild(UI.mainnav([
|
|
||||||
UI.button("Start", null),
|
|
||||||
], [
|
|
||||||
UI.button("◀", null),
|
|
||||||
UI.button("▶", null),
|
|
||||||
]));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Live:{
|
|
||||||
load:function() {
|
|
||||||
UI.mainmenu();
|
|
||||||
MAIN.appendChild(UI.mainnav([
|
|
||||||
UI.button("Start", null),
|
|
||||||
], [
|
|
||||||
UI.button("◀", null),
|
|
||||||
UI.button("▶", null),
|
|
||||||
]));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
History:{
|
|
||||||
load:function() {
|
|
||||||
UI.mainmenu();
|
|
||||||
MAIN.appendChild(UI.mainnav([ ], [
|
|
||||||
UI.button("◀", null),
|
|
||||||
UI.button("▶", null),
|
|
||||||
]));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Guide:{
|
|
||||||
load:function() {
|
|
||||||
UI.mainmenu();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
About:{
|
|
||||||
load:function() {
|
|
||||||
UI.mainmenu();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Game:{
|
|
||||||
load:function() {
|
|
||||||
MENU.appendChild(UI.button("Back", function() { LOAD(SCENES.Online) }));
|
|
||||||
MENU.appendChild(UI.button("Retire", function() { }));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
unload:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
refresh:function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function REBUILD() {
|
function REBUILD() {
|
||||||
MENU = document.createElement("nav");
|
MENU = document.createElement("nav");
|
||||||
let title = document.createElement("header");
|
let title = document.createElement("header");
|
||||||
@ -281,8 +591,99 @@ function REBUILD() {
|
|||||||
document.body.appendChild(MAIN);
|
document.body.appendChild(MAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MESSAGE() {
|
function MESSAGE(event) {
|
||||||
|
console.log("Message received.");
|
||||||
|
|
||||||
|
if(SCENE.message !== undefined) {
|
||||||
|
let bytes = new Uint8Array(event.data);
|
||||||
|
let code = 0;
|
||||||
|
let index = 2;
|
||||||
|
let data = null;
|
||||||
|
|
||||||
|
if(bytes.length >= 2) {
|
||||||
|
code = (bytes[0] << 8) + bytes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(code) {
|
||||||
|
case OpCode.Register: {
|
||||||
|
console.log("Register response.");
|
||||||
|
|
||||||
|
if(bytes.length == 28) {
|
||||||
|
console.log("Good size");
|
||||||
|
data = {
|
||||||
|
status:(bytes[2] << 8) + bytes[3],
|
||||||
|
token:new Uint8Array(),
|
||||||
|
secret:new Uint8Array(),
|
||||||
|
};
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
for(let i = 0; i < 8; ++i) {
|
||||||
|
data.token += bytes[index++];
|
||||||
|
}
|
||||||
|
for(let i = 0; i < 16; ++i) {
|
||||||
|
data.secret += bytes[index++];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Register bad length:" + bytes.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Authenticate: {
|
||||||
|
console.log("Authenticate response.");
|
||||||
|
|
||||||
|
if(bytes.length == 28) {
|
||||||
|
console.log("Good size");
|
||||||
|
data = {
|
||||||
|
status:(bytes[2] << 8) + bytes[3],
|
||||||
|
token:new Uint8Array(),
|
||||||
|
secret:new Uint8Array(),
|
||||||
|
};
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
for(let i = 0; i < 8; ++i) {
|
||||||
|
data.token += bytes[index++];
|
||||||
|
}
|
||||||
|
for(let i = 0; i < 16; ++i) {
|
||||||
|
data.secret += bytes[index++];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Authenticate bad length:" + bytes.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Resume: {
|
||||||
|
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Deauthenticate: {
|
||||||
|
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCENE.message(code, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MESSAGE_COMPOSE(data) {
|
||||||
|
if(SOCKET !== null) {
|
||||||
|
let length = 0;
|
||||||
|
for(let i = 0; i < data.length; ++i) {
|
||||||
|
length += data[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = new Uint8Array(length);
|
||||||
|
length = 0;
|
||||||
|
for(let i = 0; i < data.length; ++i) {
|
||||||
|
raw.set(data[i], length);
|
||||||
|
length += data[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKET.send(raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function LOAD(scene) {
|
function LOAD(scene) {
|
||||||
@ -290,10 +691,19 @@ function LOAD(scene) {
|
|||||||
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
||||||
REBUILD();
|
REBUILD();
|
||||||
SCENE = scene;
|
SCENE = scene;
|
||||||
|
CONTEXT.Scene = SCENE;
|
||||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
function LOAD_OFFLINE() {
|
||||||
|
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||||
|
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
||||||
|
REBUILD();
|
||||||
|
SCENE = SCENES.Offline;
|
||||||
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
SCENE = SCENES.Offline;
|
SCENE = SCENES.Offline;
|
||||||
LOAD(SCENES.Init);
|
LOAD(SCENES.Init);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user