Add persistent storage for salt and handle.
This commit is contained in:
parent
d5c0d74016
commit
ee12d74c67
@ -19,6 +19,7 @@ http-body-util = "0.1.2"
|
||||
futures = "0.3.30"
|
||||
rust-argon2 = "2.1.0"
|
||||
ring = "0.17.8"
|
||||
const_format = "0.2.32"
|
||||
|
||||
game = { path = "../game" }
|
||||
|
||||
|
@ -1,36 +0,0 @@
|
||||
# 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
|
143
server/docs/protocol/requests.szun
Normal file
143
server/docs/protocol/requests.szun
Normal file
@ -0,0 +1,143 @@
|
||||
### Status Codes ##
|
||||
|
||||
# General
|
||||
|
||||
0000 OK
|
||||
0001 ERROR
|
||||
0002 NOAUTH
|
||||
00FE SERVER_ERROR
|
||||
00FF NOT_IMPLEMENTED
|
||||
|
||||
# Context Specific
|
||||
|
||||
0010 BAD_HANDLE
|
||||
0011 BAD_SECRET
|
||||
0012 BAD_CODE
|
||||
|
||||
###
|
||||
|
||||
## Authentication ##
|
||||
|
||||
# 0010 REGISTER
|
||||
Req_Register {
|
||||
handle :sequence # username
|
||||
secret :sequence # password
|
||||
code :sequence # invitation code
|
||||
}
|
||||
Res_Register {
|
||||
status :block<2>
|
||||
# OK - registration accepted
|
||||
# BAD_HANDLE - handle empty or not vacant
|
||||
# BAD_SECRET - secret is empty
|
||||
# BAD_CODE - code not accepted
|
||||
token :block<8> # auth key
|
||||
secret :block<16> # resume code
|
||||
}
|
||||
|
||||
# 0011 AUTH
|
||||
Req_Auth {
|
||||
handle :sequence # username
|
||||
secret :sequence # password
|
||||
}
|
||||
Res_Auth {
|
||||
status :block<2>
|
||||
# STATUS_OK - authentication successful
|
||||
# ERROR - authentication failed
|
||||
token :block<8> # auth key
|
||||
secret :block<16> # resume code
|
||||
}
|
||||
|
||||
# 0012 AUTH_RESUME
|
||||
Req_AuthResume {
|
||||
token :block<8> # auth key
|
||||
secret :block<16> # resume code
|
||||
}
|
||||
Res_AuthResume {
|
||||
status :block<2>
|
||||
# OK - resume accepted
|
||||
# ERROR - auth/code pair not accepted
|
||||
}
|
||||
|
||||
# 0013 AUTH_REVOKE
|
||||
Req_AuthRevoke { }
|
||||
Res_AuthRevoke { }
|
||||
|
||||
|
||||
## Session ##
|
||||
|
||||
# 0020 SESSION_LIST
|
||||
Req_SessionList {
|
||||
flags :block<2>
|
||||
# 0[2] Game State
|
||||
# 2[1] Is Player
|
||||
# 3[1] Is Live
|
||||
page :block<2>
|
||||
}
|
||||
Res_SessionList {
|
||||
records :list<{
|
||||
token :block<8> # session key
|
||||
handle_dawn :sequence # username of dawn player
|
||||
handle_dusk :sequence # username of dusk player
|
||||
turn :block<2> # turn number
|
||||
last_move :block<3> # most recent move code
|
||||
viewers :block<4> # number of viewers
|
||||
player :bool # user is player
|
||||
}>
|
||||
}
|
||||
|
||||
# 0021 SESSION_CREATE
|
||||
Req_SessionCreate { }
|
||||
Res_SessionCreate {
|
||||
status :block<2>
|
||||
token :block<8>
|
||||
mode :block<1>
|
||||
# 0: Player Dawn
|
||||
# 1: Player Dusk
|
||||
# 2: Spectator
|
||||
}
|
||||
|
||||
# 0022 SESSION_JOIN
|
||||
Res_SessionJoin {
|
||||
token :block<8> # session key
|
||||
}
|
||||
Req_SessionJoin {
|
||||
status :block<2>
|
||||
# OK
|
||||
# NOAUTH
|
||||
token :block<8> # session key
|
||||
mode :block<1>
|
||||
# 0: Player Dawn
|
||||
# 1: Player Dusk
|
||||
# 2: Spectator
|
||||
}
|
||||
|
||||
# 002E SESSION_LEAVE
|
||||
Req_SessionLeave { }
|
||||
Res_SessionLeave { }
|
||||
|
||||
# 002F SESSION_RETIRE
|
||||
Req_SessionRetire {
|
||||
token :block<8> # session key
|
||||
}
|
||||
Res_SessionRetire {
|
||||
status:block<2>
|
||||
}
|
||||
|
||||
|
||||
## Game ##
|
||||
|
||||
# 0030 GAME_STATE
|
||||
Req_GameState {
|
||||
token :block<8> # session token
|
||||
}
|
||||
Res_GameState {
|
||||
|
||||
}
|
||||
|
||||
# 0031 GAME_PLAY
|
||||
Req_GamePlay {
|
||||
token :block<8> # session token
|
||||
}
|
||||
Res_GamePlay {
|
||||
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use futures::stream::SplitSink;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
|
||||
|
||||
use crate::app::authentication::AuthToken;
|
||||
|
||||
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
|
||||
|
||||
pub struct Connection {
|
||||
pub bus:u32,
|
||||
pub stream:StreamType,
|
||||
pub auth:Option<AuthToken>,
|
||||
}
|
||||
impl Connection {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
bus:0,
|
||||
auth:None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
use sparse::Sparse;
|
||||
use pool::Pool;
|
||||
use trie::Trie;
|
||||
use crate::util::{
|
||||
Chain,
|
||||
pack::pack_u32,
|
||||
use crate::{
|
||||
system::filesystem::FileSystem,
|
||||
util::Chain,
|
||||
};
|
||||
|
||||
pub mod connection; use connection::Connection;
|
||||
@ -15,13 +15,15 @@ pub mod session; use session::{Session, SessionToken};
|
||||
pub mod context;
|
||||
|
||||
pub struct App {
|
||||
pub filesystem:FileSystem,
|
||||
|
||||
pub connections:Pool<Connection>,
|
||||
|
||||
pub users:Pool<User>,
|
||||
pub user_next:u32,
|
||||
pub user_id:Sparse<usize>,
|
||||
pub user_handle:Trie<u32>,
|
||||
pub salts:Vec<[u8; 16]>,
|
||||
pub salts:Sparse<[u8; 16]>,
|
||||
|
||||
pub auths:Trie<Authentication>,
|
||||
pub sessions:Trie<Session>,
|
||||
@ -29,39 +31,45 @@ pub struct App {
|
||||
pub session_time:Chain<SessionToken>,
|
||||
}
|
||||
impl App {
|
||||
pub fn new() -> Self
|
||||
pub fn init() -> Result<Self, ()>
|
||||
{
|
||||
Self {
|
||||
if let Ok(mut filesystem) = FileSystem::init() {
|
||||
|
||||
// Load salts
|
||||
let mut salts = Sparse::new();
|
||||
let salt_count = filesystem.salt_count()?;
|
||||
for id in 0..salt_count {
|
||||
let salt = filesystem.salt_fetch(id as u32).unwrap();
|
||||
salts.set(id as isize, salt);
|
||||
}
|
||||
|
||||
// Load handles
|
||||
let mut user_handle = Trie::new();
|
||||
let handle_count = filesystem.handle_count()?;
|
||||
for id in 0..handle_count {
|
||||
let (handle, user_id) = filesystem.handle_fetch(id as u32).unwrap();
|
||||
println!("got: {} = {}", handle, user_id);
|
||||
user_handle.set(handle.as_bytes(), user_id);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
filesystem:filesystem,
|
||||
|
||||
connections:Pool::new(),
|
||||
|
||||
users:Pool::new(),
|
||||
user_next:0,
|
||||
user_id:Sparse::new(),
|
||||
user_handle:Trie::new(),
|
||||
salts:Vec::new(),
|
||||
salts,
|
||||
|
||||
auths:Trie::new(),
|
||||
sessions:Trie::new(),
|
||||
|
||||
session_time:Chain::new(),
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
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("c"))?;
|
||||
fs::create_dir(path_data.join("s"))?;
|
||||
fs::create_dir(path_data.join("u"))?;
|
||||
|
||||
fs::write(path_data.join("c/.i"), pack_u32(0)).ok();
|
||||
fs::write(path_data.join("s/.i"), pack_u32(0)).ok();
|
||||
fs::write(path_data.join("u/.i"), pack_u32(0)).ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ pub struct User {
|
||||
pub id:u32,
|
||||
pub handle:String,
|
||||
pub secret:Vec<u8>,
|
||||
pub na_key:usize,
|
||||
pub na_key:u32,
|
||||
}
|
||||
|
@ -100,13 +100,14 @@ async fn main()
|
||||
};
|
||||
|
||||
// Initialize application data.
|
||||
let mut app = App::new();
|
||||
if app.init().is_err() {
|
||||
let app;
|
||||
if let Ok(result) = App::init() {
|
||||
app = result;
|
||||
} else {
|
||||
println!("fatal: failed to initialize server.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Initialize central bus and data serivce.
|
||||
let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
|
||||
match b_main.connect() {
|
||||
|
@ -1,3 +1,6 @@
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use futures::SinkExt;
|
||||
|
||||
use bus::Bus;
|
||||
use crate::{
|
||||
app::{
|
||||
@ -7,20 +10,28 @@ use crate::{
|
||||
connection::Connection,
|
||||
},
|
||||
protocol,
|
||||
util::pack::pack_u16,
|
||||
};
|
||||
|
||||
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8>
|
||||
{
|
||||
[
|
||||
pack_u16(code),
|
||||
data,
|
||||
].concat()
|
||||
}
|
||||
|
||||
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() {
|
||||
while let Some(response) = match bus.receive_wait() {
|
||||
Some(packet) => {
|
||||
let qr = &packet.data;
|
||||
let qr = packet.data;
|
||||
|
||||
let mut user_id = None;
|
||||
if let Some(conn) = app.connections.get(qr.id as usize) {
|
||||
@ -31,28 +42,41 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
match &qr.data {
|
||||
QConn(request) => {
|
||||
match qr.data {
|
||||
QRPacketData::QConn(request) => {
|
||||
let id = app.connections.add(Connection {
|
||||
bus: request.bus_id,
|
||||
stream: request.stream,
|
||||
auth: None,
|
||||
});
|
||||
|
||||
println!("Connect: {}", id);
|
||||
|
||||
bus.send(packet.from, QRPacket::new(id as u32, RConn)).is_ok()
|
||||
bus.send(
|
||||
packet.from,
|
||||
QRPacket::new(id as u32, QRPacketData::RConn)
|
||||
).ok();
|
||||
Some(QRPacket::new(0, QRPacketData::None))
|
||||
}
|
||||
|
||||
QDisconn => {
|
||||
QRPacketData::QDisconn => {
|
||||
// Close socket and remove connection if valid
|
||||
//
|
||||
if if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||
let mut socket = conn.stream.write().await;
|
||||
socket.close().await.ok();
|
||||
true
|
||||
} else { false } {
|
||||
app.connections.remove(qr.id as usize).ok();
|
||||
|
||||
println!("Disconnect: {}", qr.id);
|
||||
true
|
||||
}
|
||||
Some(QRPacket::new(0, QRPacketData::None))
|
||||
}
|
||||
|
||||
QRegister(request) => {
|
||||
QRPacketData::QRegister(request) => {
|
||||
let mut response = PacketRegisterResponse::new();
|
||||
response.status = STATUS_ERROR;
|
||||
response.status = STATUS_SERVER_ERROR;
|
||||
|
||||
println!("Request: Register");
|
||||
|
||||
@ -67,8 +91,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
let mut salt = [0u8; 16];
|
||||
match rng.fill(&mut salt) {
|
||||
Ok(_) => {
|
||||
let salt_id = app.salts.len();
|
||||
app.salts.push(salt);
|
||||
let salt_id = app.filesystem.salt_store(salt).unwrap();
|
||||
app.salts.set(salt_id as isize, salt);
|
||||
|
||||
if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
||||
let user_id = app.user_next;
|
||||
@ -83,6 +107,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
});
|
||||
|
||||
// Register user pool id and handle
|
||||
app.filesystem.handle_store(&request.handle, user_id).ok();
|
||||
app.user_id.set(user_id as isize, user_pos);
|
||||
app.user_handle.set(request.handle.as_bytes(), user_id);
|
||||
|
||||
@ -120,10 +145,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RRegister(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RRegister(response)))
|
||||
}
|
||||
|
||||
QAuth(request) => {
|
||||
QRPacketData::QAuth(request) => {
|
||||
let mut response = PacketAuthResponse::new();
|
||||
response.status = STATUS_ERROR;
|
||||
|
||||
@ -141,7 +166,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
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) {
|
||||
if let Some(salt) = app.salts.get(user.na_key as isize) {
|
||||
|
||||
// Verify salted secret against user data
|
||||
if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) {
|
||||
@ -184,10 +209,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RAuth(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RAuth(response)))
|
||||
}
|
||||
|
||||
QAuthResume(request) => {
|
||||
QRPacketData::QAuthResume(request) => {
|
||||
let mut response = PacketAuthResumeResponse::new();
|
||||
response.status = STATUS_ERROR;
|
||||
|
||||
@ -209,10 +234,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RAuthResume(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response)))
|
||||
}
|
||||
|
||||
QDeauth => {
|
||||
QRPacketData::QAuthRevoke => {
|
||||
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||
match conn.auth {
|
||||
Some(auth) => {
|
||||
@ -223,10 +248,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
conn.auth = None;
|
||||
}
|
||||
true
|
||||
Some(QRPacket::new(0, QRPacketData::None))
|
||||
}
|
||||
|
||||
QSessionList(request) => {
|
||||
QRPacketData::QSessionList(request) => {
|
||||
use game::game::GameState;
|
||||
|
||||
println!("Request: Session List");
|
||||
@ -277,7 +302,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
dusk_handle,
|
||||
],
|
||||
turn:0,
|
||||
last_move:[0; 5],
|
||||
last_move:[0; 3],
|
||||
viewers:0,
|
||||
player:is_player,
|
||||
});
|
||||
@ -290,10 +315,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
next_id = app.session_time.next(id);
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RSessionList(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response)))
|
||||
}
|
||||
|
||||
QSessionCreate(_request) => {
|
||||
QRPacketData::QSessionCreate(_request) => {
|
||||
use crate::app::session::*;
|
||||
|
||||
println!("Request: Session Create");
|
||||
@ -336,10 +361,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
response.token = token;
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RSessionCreate(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RSessionCreate(response)))
|
||||
}
|
||||
|
||||
QSessionJoin(request) => {
|
||||
QRPacketData::QSessionJoin(request) => {
|
||||
println!("Request: Session Join");
|
||||
|
||||
let mut response = PacketSessionJoinResponse::new();
|
||||
@ -390,12 +415,58 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
bus.send(packet.from, QRPacket::new(qr.id, RSessionJoin(response))).is_ok()
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response)))
|
||||
}
|
||||
|
||||
_ => { true }
|
||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
} {
|
||||
if match response.data { QRPacketData::None => false, _ => true } {
|
||||
if let Some(conn) = app.connections.get_mut(response.id as usize) {
|
||||
let mut socket = conn.stream.write().await;
|
||||
|
||||
match response.data {
|
||||
QRPacketData::RRegister(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_REGISTER, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RAuth(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_AUTH, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RAuthResume(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_AUTH_RESUME, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RSessionList(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_SESSION_LIST, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RSessionCreate(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_SESSION_CREATE, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RSessionJoin(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_SESSION_JOIN, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => false,
|
||||
} { }
|
||||
}
|
||||
|
@ -1,45 +1,42 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::{
|
||||
protocol,
|
||||
util::pack::{pack_u16, unpack_u16},
|
||||
util::pack::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<(),()>
|
||||
pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceArgs) -> Result<(),()>
|
||||
// Handle websocket connection.
|
||||
//
|
||||
{
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
use protocol::{QRPacket, QRPacketData::*, code::*, packet::*};
|
||||
use protocol::{QRPacket, QRPacketData, code::*, packet::*};
|
||||
|
||||
let conn_id :u32;
|
||||
|
||||
let (sink, mut stream) = ws.split();
|
||||
|
||||
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 {
|
||||
args.bus.send(bus_ds, QRPacket::new(0, QRPacketData::QConn(LocalPacketConnect {
|
||||
bus_id:args.bus.id(),
|
||||
stream:Arc::new(RwLock::new(sink)),
|
||||
})))?;
|
||||
match args.bus.receive_wait() {
|
||||
Some(resp) => {
|
||||
let qr = &resp.data;
|
||||
match qr.data {
|
||||
RConn => { conn_id = qr.id; }
|
||||
QRPacketData::RConn => { conn_id = qr.id; }
|
||||
_ => { return Err(()); }
|
||||
}
|
||||
}
|
||||
@ -49,7 +46,7 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
|
||||
// Decode client requests from websocket,
|
||||
// pass requests to data system,
|
||||
// and return responses to client.
|
||||
while match ws.next().await {
|
||||
while match stream.next().await {
|
||||
Some(msg) => match msg {
|
||||
Ok(msg) => match msg {
|
||||
Message::Binary(data) => {
|
||||
@ -62,142 +59,67 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
|
||||
|
||||
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,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QRegister(packet))
|
||||
).ok();
|
||||
}
|
||||
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,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QAuth(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) {
|
||||
Ok(packet) => {
|
||||
if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuthResume(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,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QAuthResume(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
CODE_DEAUTH => {
|
||||
args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok();
|
||||
CODE_AUTH_REVOKE => {
|
||||
args.bus.send(
|
||||
bus_ds, QRPacket::new(conn_id,
|
||||
QRPacketData::QAuthRevoke)
|
||||
).ok();
|
||||
}
|
||||
|
||||
CODE_SESSION_LIST => match PacketSessionList::decode(&data, &mut index) {
|
||||
Ok(packet) => {
|
||||
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionList(packet))).is_ok() {
|
||||
while match args.bus.receive_wait() {
|
||||
Some(resp) => {
|
||||
let qr = &resp.data;
|
||||
match &qr.data {
|
||||
RSessionList(resp) => {
|
||||
ws.send(Message::Binary(
|
||||
encode_response(code, resp.encode())
|
||||
)).await.ok();
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QSessionList(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { println!("error: packet decode failed."); }
|
||||
}
|
||||
|
||||
CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) {
|
||||
Ok(packet) => {
|
||||
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionCreate(packet))).is_ok() {
|
||||
while match args.bus.receive_wait() {
|
||||
Some(resp) => {
|
||||
let qr = &resp.data;
|
||||
match &qr.data {
|
||||
RSessionCreate(resp) => {
|
||||
ws.send(Message::Binary(
|
||||
encode_response(code, resp.encode())
|
||||
)).await.ok();
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QSessionCreate(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { println!("error: packet decode failed."); }
|
||||
}
|
||||
|
||||
CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) {
|
||||
Ok(packet) => {
|
||||
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionJoin(packet))).is_ok() {
|
||||
while match args.bus.receive_wait() {
|
||||
Some(resp) => {
|
||||
let qr = &resp.data;
|
||||
match &qr.data {
|
||||
RSessionJoin(resp) => {
|
||||
ws.send(Message::Binary(
|
||||
encode_response(code, resp.encode())
|
||||
)).await.ok();
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
} { }
|
||||
}
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QSessionJoin(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { println!("error: packet decode failed."); }
|
||||
}
|
||||
@ -216,7 +138,6 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
|
||||
None => false,
|
||||
} { }
|
||||
|
||||
args.bus.send(bus_ds, QRPacket::new(conn_id, QDisconn)).ok();
|
||||
ws.close(None).await.ok();
|
||||
args.bus.send(bus_ds, QRPacket::new(conn_id, QRPacketData::QDisconn)).ok();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ 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_AUTH_REVOKE :u16 = 0x0013;
|
||||
|
||||
pub const CODE_SESSION_LIST :u16 = 0x0020;
|
||||
pub const CODE_SESSION_CREATE :u16 = 0x0021;
|
||||
@ -29,4 +29,5 @@ pub const CODE_SESSION_JOIN :u16 = 0x0022;
|
||||
pub const CODE_SESSION_RETIRE :u16 = 0x002E;
|
||||
pub const CODE_SESSION_LEAVE :u16 = 0x002F;
|
||||
|
||||
pub const CODE_GAME_PLAY :u16 = 0x0030;
|
||||
pub const CODE_GAME_STATE :u16 = 0x0030;
|
||||
pub const CODE_GAME_PLAY :u16 = 0x0031;
|
||||
|
@ -5,7 +5,7 @@ pub mod packet; pub use packet::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum QRPacketData {
|
||||
ROk,
|
||||
None,
|
||||
|
||||
QConn(LocalPacketConnect),
|
||||
RConn,
|
||||
@ -21,7 +21,7 @@ pub enum QRPacketData {
|
||||
QAuthResume(PacketAuthResume),
|
||||
RAuthResume(PacketAuthResumeResponse),
|
||||
|
||||
QDeauth,
|
||||
QAuthRevoke,
|
||||
|
||||
QSessionList(PacketSessionList),
|
||||
RSessionList(PacketSessionListResponse),
|
||||
|
@ -1,4 +1,12 @@
|
||||
#[derive(Clone, Copy)]
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use futures::stream::SplitSink;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalPacketConnect {
|
||||
pub bus_id:u32,
|
||||
pub stream:Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>,
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ pub struct PacketSessionListResponseRecord {
|
||||
pub token:SessionToken,
|
||||
pub handles:[String; 2],
|
||||
pub turn:u16,
|
||||
pub last_move:[u8; 5],
|
||||
pub last_move:[u8; 3],
|
||||
pub viewers:u32,
|
||||
pub player:bool,
|
||||
}
|
||||
|
244
server/src/system/filesystem/mod.rs
Normal file
244
server/src/system/filesystem/mod.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use std::{
|
||||
fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::session::Session,
|
||||
app::user::User,
|
||||
util::pack::{pack_u32, unpack_u32}
|
||||
};
|
||||
|
||||
const HANDLE_BUCKET_MASK :u32 = 0xFF;
|
||||
const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1;
|
||||
|
||||
const DIR_DATA :&str = "data";
|
||||
const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA);
|
||||
const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA);
|
||||
const DIR_USER :&str = const_format::formatcp!("{}/u", DIR_DATA);
|
||||
|
||||
const INDEX_HANDLE :&str = const_format::formatcp!("{}/i.bin", DIR_HANDLE);
|
||||
const INDEX_SESSION :&str = const_format::formatcp!("{}/i.bin", DIR_SESSION);
|
||||
const INDEX_USER :&str = const_format::formatcp!("{}/i.bin", DIR_USER);
|
||||
|
||||
pub const FILE_SALT :&str = const_format::formatcp!("{}/x.bin", DIR_DATA);
|
||||
|
||||
pub struct FileSystem {
|
||||
index_handle:File,
|
||||
index_session:File,
|
||||
index_user:File,
|
||||
|
||||
table_salt:File,
|
||||
}
|
||||
impl FileSystem {
|
||||
pub fn init() -> Result<Self, std::io::Error>
|
||||
{
|
||||
//
|
||||
// TEMPORARY: REMOVE AFTER TESTING
|
||||
//
|
||||
//fs::remove_dir_all(DIR_DATA).ok();
|
||||
|
||||
// Initialize filesystem if does not exist.
|
||||
//
|
||||
// Note: does not currently check for corruption.
|
||||
//
|
||||
if !Path::new(DIR_DATA).exists() {
|
||||
fs::create_dir(DIR_DATA)?;
|
||||
fs::create_dir(DIR_HANDLE)?;
|
||||
fs::create_dir(DIR_SESSION)?;
|
||||
fs::create_dir(DIR_USER)?;
|
||||
|
||||
fs::write(INDEX_HANDLE, pack_u32(0))?;
|
||||
fs::write(INDEX_SESSION, pack_u32(0))?;
|
||||
fs::write(INDEX_USER, pack_u32(0))?;
|
||||
|
||||
fs::write(FILE_SALT, pack_u32(0))?;
|
||||
}
|
||||
|
||||
let index_handle = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(INDEX_HANDLE)?;
|
||||
let index_session = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(INDEX_SESSION)?;
|
||||
let index_user = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(INDEX_USER)?;
|
||||
|
||||
let table_salt = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(FILE_SALT)?;
|
||||
|
||||
Ok(Self {
|
||||
index_handle,
|
||||
index_session,
|
||||
index_user,
|
||||
|
||||
table_salt,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn session_store(&mut self, _session:&Session) -> Result<(),()>
|
||||
{
|
||||
Err(())
|
||||
}
|
||||
|
||||
|
||||
pub fn user_store(&mut self, _user:&User) -> Result<(),()>
|
||||
{
|
||||
Err(())
|
||||
}
|
||||
|
||||
|
||||
pub fn handle_store(&mut self, handle:&String, user_id:u32) -> Result<u32,()>
|
||||
// Add a salt to store.
|
||||
//
|
||||
{
|
||||
let size = self.handle_count()? as u32;
|
||||
let data = handle.as_bytes();
|
||||
let length = data.len();
|
||||
|
||||
let bucket_index = size & !HANDLE_BUCKET_MASK;
|
||||
let record_index = size & HANDLE_BUCKET_MASK;
|
||||
let record_offset = 12 * record_index as u64;
|
||||
|
||||
// Update size record
|
||||
self.index_handle.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||
|
||||
// Create bucket file if not exists
|
||||
let bucket_path = Path::new(DIR_HANDLE).join(format!("{:x}.bin", bucket_index));
|
||||
if !bucket_path.exists() {
|
||||
fs::write(bucket_path.clone(), vec![0u8; 12 * HANDLE_BUCKET_SIZE as usize]).map_err(|_| ())?;
|
||||
}
|
||||
|
||||
// Open bucket file for record
|
||||
if let Ok(mut file) = File::options().read(true).write(true).open(bucket_path) {
|
||||
let offset = file.seek(SeekFrom::End(0)).map_err(|_| ())?;
|
||||
|
||||
// Write record header to table
|
||||
let buffer_header = [
|
||||
pack_u32(offset as u32),
|
||||
pack_u32(length as u32),
|
||||
pack_u32(user_id),
|
||||
].concat();
|
||||
|
||||
file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?;
|
||||
file.write(&buffer_header).map_err(|_| ())?;
|
||||
|
||||
|
||||
// Write handle data to end of file
|
||||
file.seek(SeekFrom::End(0)).map_err(|_| ())?;
|
||||
file.write(data).map_err(|_| ())?;
|
||||
|
||||
Ok(size)
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn handle_fetch(&mut self, id:u32) -> Result<(String, u32),()>
|
||||
// Retrieve a salt from store.
|
||||
//
|
||||
{
|
||||
let size = self.salt_count()? as u32;
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
// Get location of handle by index
|
||||
if id < size {
|
||||
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
||||
let record_index = id & HANDLE_BUCKET_MASK;
|
||||
let record_offset = 12 * record_index as u64;
|
||||
|
||||
let path = Path::new(DIR_HANDLE)
|
||||
.join(format!("{:x}.bin", bucket_index));
|
||||
|
||||
// Read bucket file for record
|
||||
if let Ok(mut file) = File::open(path) {
|
||||
|
||||
// Get record offset, length, and user association
|
||||
let mut buffer_header = [0u8; 12];
|
||||
file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?;
|
||||
if file.read_exact(&mut buffer_header).is_ok() {
|
||||
let offset = unpack_u32(&buffer_header, &mut 0);
|
||||
let length = unpack_u32(&buffer_header, &mut 4);
|
||||
let user_id = unpack_u32(&buffer_header, &mut 8);
|
||||
|
||||
if offset != 0 {
|
||||
buffer.resize(length as usize, 0);
|
||||
|
||||
// Read handle data from offset
|
||||
file.seek(SeekFrom::Start(offset as u64)).map_err(|_| ())?;
|
||||
match file.read_exact(&mut buffer) {
|
||||
Ok(_) => {
|
||||
Ok((String::from_utf8(buffer).map_err(|_| ())?, user_id))
|
||||
}
|
||||
Err(e) => { println!("e: {}", e.to_string()); Err(()) }
|
||||
}
|
||||
} else { Err(()) }
|
||||
} else { Err(()) }
|
||||
} else { Err(()) }
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn handle_count(&mut self) -> Result<usize, ()>
|
||||
// Get number of salts in store.
|
||||
//
|
||||
{
|
||||
Self::get_header_size(&mut self.index_handle)
|
||||
}
|
||||
|
||||
|
||||
pub fn salt_store(&mut self, salt:[u8; 16]) -> Result<u32,()>
|
||||
// Add a salt to store.
|
||||
//
|
||||
{
|
||||
let size = self.salt_count()? as u32;
|
||||
|
||||
// Update size record
|
||||
self.table_salt.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
self.table_salt.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||
|
||||
// Write salt to store
|
||||
self.table_salt.seek(SeekFrom::End(0)).map_err(|_| ())?;
|
||||
self.table_salt.write(&salt).map_err(|_| ())?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
pub fn salt_fetch(&mut self, id:u32) -> Result<[u8; 16],()>
|
||||
// Retrieve a salt from store.
|
||||
//
|
||||
{
|
||||
let size = self.salt_count()? as u32;
|
||||
let mut buffer = [0u8; 16];
|
||||
|
||||
if id < size {
|
||||
let offset = 4 + (16 * id as u64);
|
||||
|
||||
self.table_salt.seek(SeekFrom::Start(offset)).map_err(|_| ())?;
|
||||
if self.table_salt.read_exact(&mut buffer).is_ok() {
|
||||
Ok(buffer)
|
||||
} else { Err(()) }
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn salt_count(&mut self) -> Result<usize, ()>
|
||||
// Get number of salts in store.
|
||||
//
|
||||
{
|
||||
Self::get_header_size(&mut self.table_salt)
|
||||
}
|
||||
|
||||
fn get_header_size(file:&mut File) -> Result<usize, ()>
|
||||
{
|
||||
let mut size_buffer = [0u8; 4];
|
||||
|
||||
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
if file.read_exact(&mut size_buffer).is_ok() {
|
||||
Ok(unpack_u32(&size_buffer, &mut 0) as usize)
|
||||
} else { Err(()) }
|
||||
}
|
||||
}
|
@ -2,3 +2,4 @@
|
||||
|
||||
pub mod net;
|
||||
pub mod cache;
|
||||
pub mod filesystem;
|
||||
|
@ -3,7 +3,7 @@ pub fn pack_u8(value:u8) -> Vec<u8>
|
||||
vec![value]
|
||||
}
|
||||
|
||||
pub fn unpack_u8(data:&Vec<u8>, index:&mut usize) -> u8
|
||||
pub fn unpack_u8(data:&[u8], index:&mut usize) -> u8
|
||||
{
|
||||
let mut result :u8 = 0;
|
||||
if *index < data.len() {
|
||||
@ -18,7 +18,7 @@ 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
|
||||
pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
|
||||
{
|
||||
let mut result :u16 = 0;
|
||||
if *index < data.len() {
|
||||
@ -42,7 +42,7 @@ pub fn pack_u32(value:u32) -> Vec<u8>
|
||||
]
|
||||
}
|
||||
|
||||
pub fn unpack_u32(data:&Vec<u8>, index:&mut usize) -> u32
|
||||
pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32
|
||||
{
|
||||
let mut result :u32 = 0;
|
||||
if *index < data.len() {
|
||||
|
@ -11,13 +11,13 @@ let CONTEXT = {
|
||||
};
|
||||
|
||||
const Status = {
|
||||
Ok: 0,
|
||||
Error: 1,
|
||||
NotImplement: 2,
|
||||
Ok: 0x0000,
|
||||
Error: 0x0001,
|
||||
NotImplemented: 0x0002,
|
||||
|
||||
BadHandle: 1,
|
||||
BadSecret: 2,
|
||||
BadCode: 3,
|
||||
BadHandle: 0x0010,
|
||||
BadSecret: 0x0011,
|
||||
BadCode: 0x0012,
|
||||
};
|
||||
|
||||
const OpCode = {
|
||||
@ -29,8 +29,11 @@ const OpCode = {
|
||||
SessionList :0x0020,
|
||||
SessionCreate :0x0021,
|
||||
SessionJoin :0x0022,
|
||||
SessionRetire :0x002E,
|
||||
SessionLeave :0x002F,
|
||||
|
||||
GameState :0x0030,
|
||||
GamePlay :0x0031,
|
||||
};
|
||||
|
||||
const GameState = {
|
||||
|
@ -1,23 +1,68 @@
|
||||
class GamePieceMove {
|
||||
const GAME_CONST = {
|
||||
PLAYER_DAWN: 0,
|
||||
PLAYER_DUSK: 1,
|
||||
|
||||
}
|
||||
SOURCE_BOARD: 0,
|
||||
SOURCE_POOL: 1,
|
||||
};
|
||||
|
||||
class GamePiece {
|
||||
const GAME_CLASS = {
|
||||
Board: class {
|
||||
constructor() {
|
||||
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME_CLASS.Tile()); }
|
||||
this.dawn = new GAME_CLASS.Player();
|
||||
}
|
||||
},
|
||||
|
||||
Player: class {
|
||||
constructor() {
|
||||
this.handle = "";
|
||||
this.pool = new GAME_CLASS.Pool();
|
||||
}
|
||||
},
|
||||
|
||||
Pool: class {
|
||||
constructor() {
|
||||
this.pieces = [ ]; for(let i = 0; i < 6; ++i) { this.pieces.push(0); }
|
||||
}
|
||||
},
|
||||
|
||||
Tile: class {
|
||||
constructor() {
|
||||
this.piece = 0;
|
||||
}
|
||||
},
|
||||
|
||||
Move: class {
|
||||
constructor(source, from, to) {
|
||||
this.source = source;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
},
|
||||
|
||||
GamePiece: class {
|
||||
constructor(name, assets, moves, promote_moves) {
|
||||
this.name = name;
|
||||
this.assets = assets;
|
||||
this.moves = moves;
|
||||
this.pmoves = promote_moves;
|
||||
}
|
||||
}
|
||||
|
||||
let GAME_DATA = {
|
||||
board: {
|
||||
tiles: [ ],
|
||||
},
|
||||
|
||||
pieces: [
|
||||
new PieceDef("Militia「兵」", "♟︎", // ♟︎士
|
||||
Piece: class {
|
||||
constructor(piece, player) {
|
||||
this.piece = piece;
|
||||
this.player = player;
|
||||
this.promoted = false;
|
||||
}
|
||||
},
|
||||
|
||||
Game: class {
|
||||
constructor() {
|
||||
this.board = new GAME_CLASS.Board();
|
||||
this.pieces = [
|
||||
new GAME_CLASS.GamePiece("Militia",
|
||||
["asset/militia_dusk.svg", "asset/militia_dawn.svg"],
|
||||
new Move()
|
||||
.add(0)
|
||||
@ -31,7 +76,7 @@ let GAME_DATA = {
|
||||
.add(4)
|
||||
.add(5)
|
||||
),
|
||||
new PieceDef("Knight「騎」", "♞", // ♞馬
|
||||
new GAME_CLASS.GamePiece("Knight",
|
||||
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
||||
new Move()
|
||||
.add(3)
|
||||
@ -51,7 +96,7 @@ let GAME_DATA = {
|
||||
.add(16)
|
||||
.add(17)
|
||||
),
|
||||
new PieceDef("Lance「槍」", "♛", // ♛槍
|
||||
new GAME_CLASS.GamePiece("Lance",
|
||||
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
||||
new Move()
|
||||
.add(0, true)
|
||||
@ -66,7 +111,7 @@ let GAME_DATA = {
|
||||
.add(4, true)
|
||||
.add(5, true)
|
||||
),
|
||||
new PieceDef("Tower「楼」", "♖", // ♖高
|
||||
new GAME_CLASS.GamePiece("Tower",
|
||||
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
||||
new Move()
|
||||
.add(0)
|
||||
@ -88,7 +133,7 @@ let GAME_DATA = {
|
||||
.add(9)
|
||||
.add(11)
|
||||
),
|
||||
new PieceDef("Castle「城」", "♜", // ♜城
|
||||
new GAME_CLASS.GamePiece("Castle",
|
||||
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
||||
new Move()
|
||||
.add(0)
|
||||
@ -109,7 +154,7 @@ let GAME_DATA = {
|
||||
.add(7, true)
|
||||
.add(10, true)
|
||||
),
|
||||
new PieceDef("Dragon「竜」", "♝", // ♝竜
|
||||
new GAME_CLASS.GamePiece("Dragon",
|
||||
["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"],
|
||||
new Move()
|
||||
.add(6, true)
|
||||
@ -133,7 +178,7 @@ let GAME_DATA = {
|
||||
.add(10, true)
|
||||
.add(11, true)
|
||||
),
|
||||
new PieceDef("King「王」", "♚", // ♚王
|
||||
new GAME_CLASS.GamePiece("Omen",
|
||||
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
||||
new Move()
|
||||
.add(0)
|
||||
@ -145,13 +190,23 @@ let GAME_DATA = {
|
||||
.add(7)
|
||||
.add(10)
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let GAME_DATA = null;
|
||||
|
||||
const GAME = {
|
||||
init() {
|
||||
GAME_DATA.board.tiles
|
||||
GAME_DATA = new GAME_CLASS.Game();
|
||||
},
|
||||
|
||||
process(move) {
|
||||
|
||||
},
|
||||
|
||||
validate(move) {
|
||||
|
||||
},
|
||||
};
|
||||
|
@ -53,7 +53,11 @@ const INTERFACE = {
|
||||
},
|
||||
|
||||
init() {
|
||||
GAME.init();
|
||||
|
||||
INTERFACE_DATA.canvas = document.getElementById("game");
|
||||
let canvas = INTERFACE_DATA.canvas;
|
||||
|
||||
if(canvas !== undefined) {
|
||||
INTERFACE_DATA.context = canvas.getContext("2d");
|
||||
|
||||
@ -68,4 +72,8 @@ const INTERFACE = {
|
||||
INTERFACE_DATA.canvas = null;
|
||||
INTERFACE_DATA.context = null;
|
||||
},
|
||||
|
||||
message(data) {
|
||||
|
||||
},
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
SCENE = SCENES.Offline;
|
||||
LOAD(SCENES.Init);
|
||||
|
||||
document.addEventListener("beforeunload", UNLOAD);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
const SCENES = {
|
||||
Init:{
|
||||
load() {
|
||||
LOAD_OFFLINE();
|
||||
LOAD_STACK(SCENES.Offline);
|
||||
CONTEXT.Scene = SCENES.Online;
|
||||
RECONNECT();
|
||||
return true;
|
||||
@ -210,6 +210,7 @@ const SCENES = {
|
||||
MESSAGE_SESSION_LIST(0, 0, false, false);
|
||||
},
|
||||
message(code, data) {
|
||||
if(code == OpCode.SessionList) {
|
||||
let table = document.getElementById("content");
|
||||
UI.clear(table);
|
||||
|
||||
@ -217,6 +218,7 @@ const SCENES = {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Continue:{
|
||||
@ -250,6 +252,16 @@ const SCENES = {
|
||||
refresh() {
|
||||
|
||||
},
|
||||
message(code, data) {
|
||||
if(code == OpCode.SessionList) {
|
||||
let table = document.getElementById("content");
|
||||
UI.clear(table);
|
||||
|
||||
if(data !== null) {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Join:{
|
||||
@ -283,6 +295,16 @@ const SCENES = {
|
||||
refresh() {
|
||||
|
||||
},
|
||||
message(code, data) {
|
||||
if(code == OpCode.SessionList) {
|
||||
let table = document.getElementById("content");
|
||||
UI.clear(table);
|
||||
|
||||
if(data !== null) {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Live:{
|
||||
@ -315,6 +337,16 @@ const SCENES = {
|
||||
refresh() {
|
||||
|
||||
},
|
||||
message(code, data) {
|
||||
if(code == OpCode.SessionList) {
|
||||
let table = document.getElementById("content");
|
||||
UI.clear(table);
|
||||
|
||||
if(data !== null) {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
History:{
|
||||
@ -342,12 +374,25 @@ const SCENES = {
|
||||
refresh() {
|
||||
|
||||
},
|
||||
message(code, data) {
|
||||
if(code == OpCode.SessionList) {
|
||||
let table = document.getElementById("content");
|
||||
UI.clear(table);
|
||||
|
||||
if(data !== null) {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Guide:{
|
||||
load() {
|
||||
UI.mainmenu();
|
||||
UI.mainnav([], []);
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
refresh() {
|
||||
@ -359,6 +404,9 @@ const SCENES = {
|
||||
load() {
|
||||
UI.mainmenu();
|
||||
UI.mainnav([], []);
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
refresh() {
|
||||
@ -377,7 +425,6 @@ const SCENES = {
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("id", "game");
|
||||
|
||||
MAIN.appendChild(canvas);
|
||||
|
||||
INTERFACE.init();
|
||||
@ -385,29 +432,33 @@ const SCENES = {
|
||||
return true;
|
||||
},
|
||||
unload() {
|
||||
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.SessionLeave),
|
||||
]);
|
||||
},
|
||||
refresh() {
|
||||
|
||||
},
|
||||
message() {
|
||||
|
||||
message(code, data) {
|
||||
if(code == OpCode.GameState || code == OpCode.GamePlay) {
|
||||
INTERFACE.message(code, data);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function LOAD(scene) {
|
||||
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||
UNLOAD();
|
||||
UI.rebuild();
|
||||
SCENE = scene;
|
||||
CONTEXT.Scene = SCENE;
|
||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||
}
|
||||
|
||||
function LOAD_OFFLINE() {
|
||||
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
||||
function UNLOAD() {
|
||||
if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
|
||||
}
|
||||
|
||||
function LOAD_STACK(scene) {
|
||||
UNLOAD();
|
||||
UI.rebuild();
|
||||
SCENE = SCENES.Offline;
|
||||
SCENE = scene;
|
||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ function RECONNECT() {
|
||||
SOCKET.binaryType = "arraybuffer";
|
||||
SOCKET.addEventListener("error", (event) => {
|
||||
SOCKET = null;
|
||||
LOAD_OFFLINE()
|
||||
LOAD_STACK(SCENES.Offline);
|
||||
});
|
||||
SOCKET.addEventListener("open", (event) => {
|
||||
if(SOCKET.readyState === WebSocket.OPEN) {
|
||||
|
@ -84,8 +84,8 @@ const UI = {
|
||||
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) }));
|
||||
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register) }));
|
||||
left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate) }));
|
||||
}
|
||||
|
||||
for(child of left_children) { left.appendChild(child); }
|
||||
|
Loading…
x
Reference in New Issue
Block a user