638 lines
29 KiB
Rust
638 lines
29 KiB
Rust
use bus::Bus;
|
|
use crate::{
|
|
app::{
|
|
authentication::Authentication,
|
|
connection::Connection,
|
|
user::User,
|
|
App,
|
|
session::Session,
|
|
},
|
|
protocol,
|
|
};
|
|
|
|
pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|
{
|
|
use futures::SinkExt;
|
|
use protocol::*;
|
|
use ring::rand::{SecureRandom, SystemRandom};
|
|
|
|
let rng = SystemRandom::new();
|
|
let argon_config = argon2::Config::default();
|
|
|
|
while let Some(response) = match bus.receive_wait() {
|
|
Some(packet) => {
|
|
let qr = packet.data;
|
|
|
|
let mut user_id = None;
|
|
let mut session_id = None;
|
|
if let Some(conn) = app.connections.get(qr.id as usize) {
|
|
session_id = conn.session;
|
|
|
|
if let Some(auth_id) = conn.auth {
|
|
if let Some(auth) = app.auths.get(&auth_id) {
|
|
user_id = Some(auth.user);
|
|
}
|
|
}
|
|
}
|
|
|
|
match qr.data {
|
|
QRPacketData::QConn(request) => {
|
|
let id = app.connections.add(Connection {
|
|
bus: request.bus_id,
|
|
stream: request.stream,
|
|
auth: None,
|
|
session: None,
|
|
});
|
|
|
|
println!("Connect: {}", id);
|
|
|
|
bus.send(
|
|
packet.from,
|
|
QRPacket::new(id as u32, QRPacketData::RConn)
|
|
).ok();
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QDisconn => {
|
|
// Uninitialize connection
|
|
if if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
|
|
// Disassociate session if present
|
|
if let Some(session_token) = conn.session {
|
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
|
if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); }
|
|
else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); }
|
|
else { session.remove_connection(2, qr.id); }
|
|
}
|
|
}
|
|
|
|
// Close socket
|
|
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);
|
|
}
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QRegister(request) => {
|
|
let mut response = PacketRegisterResponse::new();
|
|
response.status = STATUS_SERVER_ERROR;
|
|
|
|
println!("Request: Register");
|
|
|
|
let mut is_valid = true;
|
|
if request.code != crate::config::REGISTER_CODE.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.filesystem.salt_store(salt).unwrap();
|
|
app.salts.set(salt_id as isize, salt);
|
|
|
|
if let Ok(secret) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
|
let user_id = app.filesystem.user_count().unwrap() as u32;
|
|
|
|
// Register user pool id and handle
|
|
let handle_id = app.filesystem.handle_store(&request.handle, user_id).unwrap();
|
|
app.user_handle.set(request.handle.as_bytes(), user_id);
|
|
|
|
let user_data = User {
|
|
id:user_id,
|
|
handle_id,
|
|
handle:request.handle.clone(),
|
|
secret,
|
|
na_key:salt_id,
|
|
};
|
|
|
|
app.filesystem.user_store(&user_data).ok();
|
|
|
|
// Create user entry
|
|
let user_pos = app.users.add(user_data);
|
|
|
|
app.user_id.set(user_id as isize, user_pos);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RRegister(response)))
|
|
}
|
|
|
|
QRPacketData::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 as isize) {
|
|
|
|
// 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 => { }
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RAuth(response)))
|
|
}
|
|
|
|
QRPacketData::QAuthResume(request) => {
|
|
let mut response = PacketAuthResumeResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
|
|
if let Some(auth) = app.auths.get(&request.token) {
|
|
|
|
// Compare full secret length to reduce time-based attacks.
|
|
let mut valid = true;
|
|
for i in 0..16 {
|
|
valid |= auth.secret[i] == request.secret[i];
|
|
}
|
|
|
|
if valid {
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.auth = Some(request.token);
|
|
response.status = STATUS_OK;
|
|
} else {
|
|
response.status = STATUS_SERVER_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response)))
|
|
}
|
|
|
|
QRPacketData::QAuthRevoke => {
|
|
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;
|
|
}
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QSessionList(request) => {
|
|
println!("Request: Session List");
|
|
|
|
let mut response = PacketSessionListResponse::new();
|
|
|
|
let mut count = 0;
|
|
|
|
let mut next_id = app.session_time.begin();
|
|
while let Some(id) = next_id {
|
|
let token = app.session_time.get(id).unwrap();
|
|
if let Some(session) = app.sessions.get(token) {
|
|
|
|
// Requirements:
|
|
// - GameState must be None or match state of session.
|
|
// - Joinable must have either player slot empty.
|
|
// - IsPlayer must have the current user in either player slot.
|
|
// - IsLive must have both users connected to the session.
|
|
|
|
let mut valid = match request.game_state {
|
|
1 => session.p_dawn.user.is_none() || session.p_dusk.user.is_none(),
|
|
2 => session.p_dawn.user.is_some() && session.p_dusk.user.is_some(),
|
|
3 => session.game.complete,
|
|
_ => true,
|
|
};
|
|
valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id;
|
|
valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0);
|
|
|
|
if valid {
|
|
let is_player = user_id.is_some() && (session.p_dawn.user == user_id || session.p_dusk.user == user_id);
|
|
|
|
let dawn_handle = if let Some(uid) = session.p_dawn.user {
|
|
if let Some(user) = app.get_user_by_id(uid) {
|
|
user.handle.clone()
|
|
} else { String::new() }
|
|
} else { String::new() };
|
|
|
|
let dusk_handle = if let Some(uid) = session.p_dusk.user {
|
|
if let Some(user) = app.get_user_by_id(uid) {
|
|
user.handle.clone()
|
|
} else { String::new() }
|
|
} else { String::new() };
|
|
|
|
response.records.push(PacketSessionListResponseRecord {
|
|
token:session.token,
|
|
handles:[
|
|
dawn_handle,
|
|
dusk_handle,
|
|
],
|
|
turn:session.game.turn,
|
|
last_move:[0; 3],
|
|
viewers:session.connections.len() as u32,
|
|
player:is_player,
|
|
});
|
|
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
if count >= 60 { break; }
|
|
next_id = app.session_time.next(id);
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response)))
|
|
}
|
|
|
|
QRPacketData::QSessionCreate(_request) => {
|
|
use crate::app::session::*;
|
|
|
|
println!("Request: Session Create");
|
|
|
|
let mut response = PacketSessionCreateResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
|
|
if let Some(uid) = user_id {
|
|
// Generate session token
|
|
let mut token = SessionToken::default();
|
|
let mut secret = SessionSecret::default();
|
|
loop {
|
|
rng.fill(&mut token).ok();
|
|
if app.sessions.get(&token).is_none() { break; }
|
|
}
|
|
rng.fill(&mut secret).ok();
|
|
|
|
let chain_id = app.session_time.add(token);
|
|
|
|
let mut session = Session {
|
|
id:0,
|
|
token,
|
|
secret,
|
|
game:game::Game::new(),
|
|
p_dawn:Player {
|
|
user:Some(uid),
|
|
connections:vec![qr.id],
|
|
},
|
|
p_dusk:Player {
|
|
user:None,
|
|
connections:Vec::new(),
|
|
},
|
|
connections:Vec::new(),
|
|
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
|
|
chain_id,
|
|
};
|
|
session.game.init();
|
|
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.session = Some(session.token);
|
|
}
|
|
|
|
session.id = app.filesystem.session_store(&session).unwrap();
|
|
|
|
app.sessions.set(&token, session);
|
|
app.session_time.set(chain_id, token);
|
|
|
|
// Set player to Dawn.
|
|
response.mode = 0;
|
|
|
|
response.status = STATUS_OK;
|
|
response.token = token;
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionCreate(response)))
|
|
}
|
|
|
|
QRPacketData::QSessionJoin(request) => {
|
|
println!("Request: Session Join");
|
|
|
|
let mut response = PacketSessionJoinResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
response.token = request.token;
|
|
|
|
let mut send_gamestate = false;
|
|
|
|
// Verify that session exists
|
|
if let Some(session) = app.sessions.get_mut(&request.token) {
|
|
|
|
// Join game as player
|
|
if if request.join {
|
|
|
|
// Verify client is authenticated
|
|
if let Some(uid) = user_id {
|
|
|
|
// User must not already be player
|
|
if session.p_dawn.user != user_id && session.p_dusk.user != user_id {
|
|
|
|
// Add user to session and randomize seats.
|
|
if if session.p_dusk.user.is_none() {
|
|
let mut r = [0u8; 1];
|
|
rng.fill(&mut r).ok();
|
|
if (r[0] & 1) == 0 {
|
|
session.p_dusk = session.p_dawn.clone();
|
|
session.p_dawn.user = Some(uid);
|
|
session.p_dawn.connections.clear();
|
|
response.mode = 0;
|
|
} else {
|
|
session.p_dusk.user = Some(uid);
|
|
response.mode = 1;
|
|
}
|
|
true
|
|
} else {
|
|
// Session is not empty
|
|
response.status = STATUS_ERROR;
|
|
false
|
|
} {
|
|
println!("Add user to session.");
|
|
|
|
app.filesystem.session_update(session.id, session).ok();
|
|
response.status = STATUS_OK;
|
|
|
|
send_gamestate = true;
|
|
true
|
|
} else { false }
|
|
}
|
|
|
|
// Resume session for player connection
|
|
else {
|
|
println!("User resumes session.");
|
|
response.status = STATUS_OK;
|
|
response.mode = (session.p_dusk.user == user_id) as u8;
|
|
true
|
|
}
|
|
} else { response.status = STATUS_NOAUTH; false }
|
|
}
|
|
|
|
// Join game as spectator.
|
|
else {
|
|
println!("User spectates session.");
|
|
|
|
response.status = STATUS_OK;
|
|
response.mode = 2;
|
|
true
|
|
} {
|
|
// Associate session and connection on join
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.session = Some(session.token);
|
|
}
|
|
session.add_connection(response.mode, qr.id);
|
|
}
|
|
}
|
|
|
|
if send_gamestate {
|
|
if let Some(session) = app.sessions.get(&request.token) {
|
|
// Send GameState update to all connections.
|
|
let mut packets = Vec::new();
|
|
let game_state = generate_gamestate(&app, session);
|
|
|
|
for (cid, player) in session.get_connections() {
|
|
if cid != qr.id {
|
|
let mut gs = game_state.clone();
|
|
gs.player = player;
|
|
packets.push(QRPacket::new(cid, QRPacketData::RGameState(gs)));
|
|
}
|
|
}
|
|
for packet in packets {
|
|
app.send_response(packet).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response)))
|
|
}
|
|
|
|
// SessionRetire
|
|
QRPacketData::QSessionRetire(request) => {
|
|
println!("Request: Session Retire");
|
|
|
|
if let Some(_session) = app.sessions.get_mut(&request.token) {
|
|
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
// SessionLeave
|
|
QRPacketData::QSessionLeave => {
|
|
println!("Request: Session Leave");
|
|
|
|
// Verify that session exists.
|
|
if let Some(session_token) = session_id {
|
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
|
if user_id.is_some() && user_id == session.p_dawn.user { session.remove_connection(0, qr.id); }
|
|
else if user_id.is_some() && user_id == session.p_dusk.user { session.remove_connection(1, qr.id); }
|
|
else { session.remove_connection(2, qr.id); }
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
// GameState
|
|
QRPacketData::QGameState(request) => {
|
|
let mut response = PacketGameStateResponse::new();
|
|
|
|
println!("Request: Game State");
|
|
|
|
if let Some(session) = app.sessions.get(&request.token) {
|
|
response = generate_gamestate(&app, &session);
|
|
|
|
if user_id.is_some() {
|
|
if user_id == session.p_dawn.user { response.player = 0; }
|
|
else if user_id == session.p_dusk.user { response.player = 1; }
|
|
}
|
|
} else {
|
|
response.status = STATUS_ERROR;
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RGameState(response)))
|
|
}
|
|
|
|
// GamePlay
|
|
QRPacketData::QGamePlay(mut request) => {
|
|
println!("Request: Game Play");
|
|
|
|
request.status = STATUS_ERROR;
|
|
let mut packets = Vec::<QRPacket>::new();
|
|
|
|
if let Some(sid) = session_id {
|
|
if let Some(session) = app.sessions.get_mut(&sid) {
|
|
if session.p_dawn.user.is_some() && session.p_dusk.user.is_some() {
|
|
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|
|
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
|
|
|
|
// Check validation of play
|
|
if request.turn == session.game.turn {
|
|
|
|
// Update internal representation
|
|
if session.game.process(&request.play).is_ok() {
|
|
request.status = STATUS_OK;
|
|
|
|
// Save play to game history.
|
|
app.filesystem.session_history_push(session.id, request.play).ok();
|
|
|
|
// Forward play to all clients
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::QGamePlay(request.clone())
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if request.status != STATUS_ERROR {
|
|
for packet in packets {
|
|
app.send_response(packet).await;
|
|
}
|
|
|
|
// Updates will have already been sent, so nothing is needed here.
|
|
Some(QRPacket::new(qr.id, QRPacketData::None))
|
|
} else {
|
|
|
|
// Return error status.
|
|
Some(QRPacket::new(qr.id, QRPacketData::QGamePlay(request)))
|
|
}
|
|
}
|
|
|
|
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
|
}
|
|
}
|
|
None => None,
|
|
} {
|
|
app.send_response(response).await;
|
|
}
|
|
}
|
|
|
|
fn generate_gamestate(app:&App, session:&Session) -> protocol::PacketGameStateResponse
|
|
{
|
|
use protocol::{PacketGameStateResponse, PacketGameStateResponsePiece};
|
|
|
|
let mut response = PacketGameStateResponse::new();
|
|
|
|
response.player = 2;
|
|
response.turn = session.game.turn;
|
|
if session.game.history.len() > 0 {
|
|
response.play = session.game.history[session.game.history.len() - 1];
|
|
}
|
|
|
|
// Get Dawn handle
|
|
if let Some(id) = session.p_dawn.user {
|
|
if let Some(user) = app.get_user_by_id(id) {
|
|
response.dawn_handle = user.handle.clone();
|
|
}
|
|
}
|
|
|
|
// Get Dusk handle
|
|
if let Some(id) = session.p_dusk.user {
|
|
if let Some(user) = app.get_user_by_id(id) {
|
|
response.dusk_handle = user.handle.clone();
|
|
}
|
|
}
|
|
|
|
// Get pool sizes
|
|
response.dawn_pool = session.game.pool[0];
|
|
response.dusk_pool = session.game.pool[1];
|
|
|
|
// Get list of pieces
|
|
for i in 0..session.game.board.pieces.len() {
|
|
response.pieces[i] = if let Some(piece) = &session.game.board.pieces[i] {
|
|
PacketGameStateResponsePiece {
|
|
valid:true,
|
|
piece:piece.class,
|
|
promoted:piece.promoted,
|
|
player:piece.player,
|
|
tile:piece.tile,
|
|
}
|
|
} else {
|
|
PacketGameStateResponsePiece::new()
|
|
};
|
|
}
|
|
|
|
response
|
|
}
|