1149 lines
52 KiB
Rust
1149 lines
52 KiB
Rust
use bus::Bus;
|
|
use game::history::Play;
|
|
use crate::{
|
|
config,
|
|
app::{
|
|
authentication::Authentication,
|
|
connection::Connection,
|
|
user::{User, UserStatus},
|
|
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();
|
|
|
|
let mut send_user_status = Vec::<u32>::new();
|
|
|
|
while let Some(packet) = bus.receive_wait() {
|
|
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 match qr.data {
|
|
QRPacketData::QConn(request) => {
|
|
let id = app.connections.add(Connection {
|
|
bus: request.bus_id,
|
|
stream: request.stream,
|
|
auth: None,
|
|
session: None,
|
|
|
|
prev:0,
|
|
next:0,
|
|
});
|
|
if let Some(conn) = app.connections.get_mut(id) {
|
|
conn.prev = id as u32;
|
|
conn.next = id as u32;
|
|
}
|
|
|
|
app.log.log(&format!("Connect: {}", id));
|
|
|
|
bus.send(
|
|
packet.from,
|
|
QRPacket::new(id as u32, QRPacketData::RConn)
|
|
).ok();
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QDisconn => {
|
|
app.log.log(&format!("Disconnect: {}", qr.id));
|
|
|
|
// Uninitialize connection
|
|
if if let Some(conn) = app.connections.get(qr.id as usize).cloned() {
|
|
|
|
// Disassociate session if present
|
|
if let Some(session_token) = conn.session {
|
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
|
if user_id == Some(session.p_dawn.user) {
|
|
session.remove_connection(0, qr.id);
|
|
}
|
|
else if user_id == Some(session.p_dusk.user) {
|
|
session.remove_connection(1, qr.id);
|
|
}
|
|
else {
|
|
session.remove_connection(2, qr.id);
|
|
}
|
|
}
|
|
|
|
app.send_session_spectators(session_token).await;
|
|
}
|
|
|
|
// Remove connection from chain.
|
|
if let Some(auth_id) = conn.auth {
|
|
if let Some(auth) = app.auths.get(&auth_id).cloned() {
|
|
|
|
// Update user connection reference.
|
|
if let Some(user) = app.get_user_by_id_mut(auth.user) {
|
|
if Some(qr.id) == user.connection {
|
|
|
|
// Set connection to next if exists.
|
|
if conn.next != qr.id {
|
|
user.connection = Some(conn.next);
|
|
} else {
|
|
user.connection = None;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Link prev and next connections.
|
|
if conn.next != qr.id {
|
|
if let Some(prev_conn) = app.connections.get_mut(conn.prev as usize) {
|
|
prev_conn.next = conn.next;
|
|
}
|
|
|
|
if let Some(next_conn) = app.connections.get_mut(conn.next as usize) {
|
|
next_conn.prev = conn.prev;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close socket
|
|
let mut socket = conn.stream.write().await;
|
|
socket.close().await.ok();
|
|
true
|
|
} else { false } {
|
|
app.connections.remove(qr.id as usize).ok();
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QHello => {
|
|
let response = PacketHelloResponse {
|
|
version:config::VERSION.to_string(),
|
|
};
|
|
Some(QRPacket::new(qr.id, QRPacketData::RHello(response)))
|
|
}
|
|
|
|
QRPacketData::QRegister(request) => {
|
|
let mut response = PacketRegisterResponse::new();
|
|
response.status = STATUS_SERVER_ERROR;
|
|
|
|
app.log.log("Request: Register");
|
|
|
|
let mut is_valid = true;
|
|
let regex = regex::Regex::new(r"^[\p{L}\p{N}\p{Pd}\p{Pc}\p{Sm}\p{Sc}]{1,24}$").unwrap();
|
|
|
|
let invite = app.invite_tokens.get(&request.code).cloned();
|
|
if is_valid && invite.is_none() { response.status = STATUS_BAD_CODE; is_valid = false; }
|
|
if is_valid && !regex.is_match(&request.handle) { 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 {
|
|
let handle = request.handle.to_lowercase();
|
|
let display_name = request.handle.clone();
|
|
|
|
match app.user_handle.get(handle.as_bytes()) {
|
|
None => {
|
|
let mut salt = [0u8; 16];
|
|
match rng.fill(&mut salt) {
|
|
Ok(_) => {
|
|
let salt_id = app.filesystem.salt_create(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
|
|
app.filesystem.handle_create(&handle, user_id).ok();
|
|
app.user_handle.set(handle.as_bytes(), user_id);
|
|
|
|
let user_data = User {
|
|
id:user_id,
|
|
flags:0,
|
|
handle:display_name,
|
|
secret,
|
|
na_key:salt_id,
|
|
|
|
connection:Some(qr.id),
|
|
|
|
status:UserStatus::new(),
|
|
challenges:Vec::new(),
|
|
};
|
|
|
|
app.filesystem.user_create(&user_data).ok();
|
|
|
|
// Create user entry
|
|
let user_pos = app.users.add(user_data);
|
|
|
|
app.user_id.set(user_id as isize, user_pos);
|
|
|
|
app.log.log(&format!("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);
|
|
}
|
|
|
|
// Remove invitation from store.
|
|
if let Some(invite) = invite {
|
|
if let Some(invite) = app.invites.get(invite as usize).cloned() {
|
|
app.invite_tokens.unset(&invite.token);
|
|
}
|
|
app.invites.remove(invite as usize).ok();
|
|
}
|
|
}
|
|
}
|
|
Err(_) => { app.log.log("error: failed to generate salt.") }
|
|
}
|
|
}
|
|
Some(_) => {
|
|
response.status = STATUS_BAD_HANDLE;
|
|
app.log.log(&format!("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;
|
|
|
|
app.log.log("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.to_lowercase().as_bytes()).cloned() {
|
|
Some(uid) => {
|
|
if let Some(tuid) = app.user_id.get(uid as isize).cloned() {
|
|
if let Some(mut user) = app.users.get(tuid).cloned() {
|
|
// Get user salt
|
|
if let Some(salt) = app.salts.get(user.na_key as isize).cloned() {
|
|
|
|
// [TEMPORARY] WORKAROUND FOR PASSWORD RESET
|
|
if user.secret.is_empty() {
|
|
app.log.log(&format!("Password reset: {}", user.handle));
|
|
if let Ok(secret) = argon2::hash_raw(&request.secret.as_bytes(), &salt, &argon_config) {
|
|
user.secret = secret;
|
|
if if let Some(app_user) = app.users.get_mut(tuid) {
|
|
app_user.secret = user.secret.clone();
|
|
true
|
|
} else { false } {
|
|
app.filesystem.user_update(uid, &user).ok();
|
|
}
|
|
}
|
|
} else {
|
|
// Verify salted secret against user data
|
|
if argon2::verify_raw(&request.secret.as_bytes(), &salt, &user.secret, &argon_config).unwrap_or(false) {
|
|
app.log.log(&format!("Authenticated user '{}' id {}", user.handle, uid));
|
|
|
|
// Generate authentication token and secret
|
|
response.status = STATUS_OK;
|
|
response.handle = user.handle.clone();
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Mark send status.
|
|
send_user_status.push(uid);
|
|
|
|
// Attach authentication to connection.
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.auth = Some(response.token);
|
|
if let Some(cid) = user.connection {
|
|
conn.prev = cid;
|
|
}
|
|
}
|
|
|
|
// Add connection to chain.
|
|
if let Some(user_cid) = user.connection {
|
|
if let Some(existing) = app.connections.get(user_cid as usize).cloned() {
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.next = existing.next;
|
|
}
|
|
}
|
|
} else {
|
|
if let Some(user) = app.users.get_mut(tuid) {
|
|
user.connection = Some(qr.id);
|
|
}
|
|
}
|
|
} else {
|
|
app.log.log("notice: password verification failed.");
|
|
}
|
|
}
|
|
} else {
|
|
app.log.log(&format!("error: user salt id '{}' not found.", user.na_key));
|
|
}
|
|
} else {
|
|
app.log.log(&format!("error: user with id '{}' not found.", uid));
|
|
}
|
|
} else {
|
|
app.log.log(&format!("error: user with id '{}' not found.", uid));
|
|
}
|
|
}
|
|
None => { }
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RAuth(response)))
|
|
}
|
|
|
|
QRPacketData::QAuthResume(request) => {
|
|
app.log.log("Request: Auth Resume");
|
|
|
|
let mut response = PacketAuthResumeResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
|
|
if let Some(auth) = app.auths.get(&request.token).cloned() {
|
|
|
|
// 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 if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.auth = Some(request.token);
|
|
response.status = STATUS_OK;
|
|
true
|
|
} else {
|
|
response.status = STATUS_SERVER_ERROR;
|
|
false
|
|
} {
|
|
// Add connection to chain.
|
|
if let Some(user) = app.get_user_by_id(auth.user).cloned() {
|
|
response.handle = user.handle.clone();
|
|
|
|
if let Some(user_cid) = user.connection {
|
|
if let Some(existing) = app.connections.get(user_cid as usize).cloned() {
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.next = existing.next;
|
|
}
|
|
}
|
|
} else {
|
|
if let Some(user) = app.get_user_by_id_mut(auth.user) {
|
|
user.connection = Some(qr.id);
|
|
}
|
|
}
|
|
|
|
// Mark send user status.
|
|
send_user_status.push(auth.user);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response)))
|
|
}
|
|
|
|
QRPacketData::QAuthRevoke => {
|
|
app.log.log("Request: Auth Revoke");
|
|
|
|
// Remove connection from chain.
|
|
if let Some(conn) = app.connections.get(qr.id as usize).cloned() {
|
|
if let Some(auth_id) = conn.auth {
|
|
if let Some(auth) = app.auths.get(&auth_id).cloned() {
|
|
|
|
// Update user connection reference.
|
|
if let Some(user) = app.get_user_by_id_mut(auth.user) {
|
|
if Some(qr.id) == user.connection {
|
|
|
|
// Set connection to next if exists.
|
|
if conn.next != qr.id {
|
|
user.connection = Some(conn.next);
|
|
} else {
|
|
user.connection = None;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Link prev and next connections.
|
|
if conn.next != qr.id {
|
|
if let Some(prev_conn) = app.connections.get_mut(conn.prev as usize) {
|
|
prev_conn.next = conn.next;
|
|
}
|
|
|
|
if let Some(next_conn) = app.connections.get_mut(conn.next as usize) {
|
|
next_conn.prev = conn.prev;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove authentication from connection.
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
match conn.auth {
|
|
Some(auth) => {
|
|
app.log.log(&format!("Deauthenticated connection: {}", qr.id));
|
|
app.auths.unset(&auth);
|
|
}
|
|
None => { }
|
|
}
|
|
conn.auth = None;
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
QRPacketData::QSessionList(request) => {
|
|
app.log.log("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) {
|
|
|
|
let mut valid = match request.game_state {
|
|
1 => !session.game.is_complete(),
|
|
2 => !session.game.is_complete(),
|
|
3 => session.game.is_complete(),
|
|
_ => true,
|
|
};
|
|
valid &= !request.is_player || Some(session.p_dawn.user) == user_id || Some(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 player :u8 = if user_id.is_some() {
|
|
if Some(session.p_dawn.user) == user_id { 1 }
|
|
else if Some(session.p_dusk.user) == user_id { 2 }
|
|
else { 0 }
|
|
} else { 0 };
|
|
|
|
let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1;
|
|
let is_complete = (session.game.is_complete() as u8) * (((session.game.turn & 1) == 0) as u8 + 1);
|
|
|
|
let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) {
|
|
user.handle.clone()
|
|
} else { String::new() };
|
|
|
|
let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
|
|
user.handle.clone()
|
|
} else { String::new() };
|
|
|
|
let mut last_move = 0;
|
|
|
|
let mut index = session.game.history.len();
|
|
let mut move_count = 0;
|
|
while move_count < 4 && index > 0 {
|
|
let play = session.game.history[index - 1];
|
|
if play.source <= 2 {
|
|
last_move <<= 8;
|
|
last_move |= 0x80 | play.meta as u32;
|
|
move_count += 1;
|
|
}
|
|
index -= 1;
|
|
}
|
|
|
|
response.records.push(PacketSessionListResponseRecord {
|
|
token:session.token,
|
|
handles:[
|
|
dawn_handle,
|
|
dusk_handle,
|
|
],
|
|
turn:session.game.turn,
|
|
last_move,
|
|
viewers:session.connections.len() as u32,
|
|
player,
|
|
is_turn,
|
|
is_complete,
|
|
});
|
|
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
if count >= 60 { break; }
|
|
next_id = app.session_time.next(id);
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response)))
|
|
}
|
|
|
|
QRPacketData::QSessionView(request) => {
|
|
app.log.log("Request: Session Join");
|
|
|
|
let mut response = PacketSessionViewResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
response.token = request.token;
|
|
|
|
// Verify that session exists
|
|
if let Some(session) = app.sessions.get_mut(&request.token) {
|
|
|
|
response.is_complete = session.game.is_complete();
|
|
|
|
if user_id == Some(session.p_dawn.user) {
|
|
response.player = 1;
|
|
} else if user_id == Some(session.p_dusk.user) {
|
|
response.player = 2;
|
|
}
|
|
|
|
// Join game as player
|
|
if if request.join {
|
|
|
|
// Verify client is authenticated
|
|
if user_id.is_some() {
|
|
// Resume session if user is player
|
|
if Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id {
|
|
app.log.log("User resumes session.");
|
|
response.status = STATUS_OK;
|
|
true
|
|
} else { false }
|
|
} else { response.status = STATUS_NOAUTH; false }
|
|
}
|
|
|
|
// Join game as spectator.
|
|
else {
|
|
app.log.log("User spectates session.");
|
|
response.status = STATUS_OK;
|
|
true
|
|
} {
|
|
// Associate session and connection on join
|
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
|
conn.session = Some(session.token);
|
|
}
|
|
let mode = if user_id == Some(session.p_dawn.user) {
|
|
0
|
|
} else if user_id == Some(session.p_dusk.user) {
|
|
1
|
|
} else {
|
|
2
|
|
};
|
|
session.add_connection(mode, qr.id);
|
|
app.send_session_spectators(request.token).await;
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response)))
|
|
}
|
|
|
|
// SessionResign
|
|
QRPacketData::QSessionResign(request) => {
|
|
use game::history::Play;
|
|
app.log.log("Request: Session Resign");
|
|
|
|
let mut packets = Vec::<QRPacket>::new();
|
|
|
|
let play = Play {
|
|
source: 0xF,
|
|
from: 0,
|
|
to: 0,
|
|
meta: 0,
|
|
};
|
|
|
|
if let Some(session) = app.sessions.get_mut(&request.token) {
|
|
if !session.game.is_complete() {
|
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
|
|
|
session.game.process(&play).ok();
|
|
app.filesystem.session_history_push(session.id, play).ok();
|
|
|
|
send_user_status.push(session.p_dawn.user);
|
|
send_user_status.push(session.p_dusk.user);
|
|
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::GameMessage(PacketGameMessage {
|
|
data: GameMessageData::Resign,
|
|
test: false,
|
|
expected: false,
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for packet in packets {
|
|
app.send_response(packet).await;
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
// SessionLeave
|
|
QRPacketData::QSessionLeave => {
|
|
app.log.log("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 == Some(session.p_dawn.user) {
|
|
session.remove_connection(0, qr.id);
|
|
}
|
|
else if user_id == Some(session.p_dusk.user) {
|
|
session.remove_connection(1, qr.id);
|
|
}
|
|
else {
|
|
session.remove_connection(2, qr.id);
|
|
}
|
|
}
|
|
|
|
app.send_session_spectators(session_token).await;
|
|
}
|
|
|
|
Some(QRPacket::new(0, QRPacketData::None))
|
|
}
|
|
|
|
// GameState
|
|
QRPacketData::QGameState(request) => {
|
|
let mut response = PacketGameStateResponse::new();
|
|
|
|
app.log.log("Request: Game State");
|
|
|
|
if let Some(session) = app.sessions.get(&request.token) {
|
|
response.status = STATUS_OK;
|
|
response = generate_game_state(&app, &session);
|
|
|
|
if user_id == Some(session.p_dawn.user) { response.player = 0; }
|
|
else if user_id == Some(session.p_dusk.user) { response.player = 1; }
|
|
} else {
|
|
response.status = STATUS_ERROR;
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RGameState(response)))
|
|
}
|
|
|
|
// GameMessage
|
|
QRPacketData::GameMessage(request) => {
|
|
app.log.log("Request: Game Message");
|
|
|
|
let mut packets = Vec::<QRPacket>::new();
|
|
let mut response = QRPacketData::None;
|
|
|
|
if let Some(sid) = session_id {
|
|
if let Some(session) = app.sessions.get_mut(&sid) {
|
|
|
|
match request.data {
|
|
GameMessageData::PlayMove(turn, from, to)
|
|
| GameMessageData::PlayDrop(turn, from, to)
|
|
| GameMessageData::PlayAlt(turn, from, to)
|
|
=> {
|
|
if request.test {
|
|
let play = Play {
|
|
source: match request.data {
|
|
GameMessageData::PlayMove(..) => 0,
|
|
GameMessageData::PlayDrop(..) => 1,
|
|
GameMessageData::PlayAlt(..) => 2,
|
|
_ => 0,
|
|
},
|
|
from, to,
|
|
meta: 0,
|
|
};
|
|
|
|
let text = format!("PLAY {} {} {}", play.source, play.from, play.to);
|
|
let result = if session.game.play_is_valid(&play) {
|
|
response = QRPacketData::TestResult(PacketTestResult::new(
|
|
request.expected,
|
|
&text,
|
|
));
|
|
request.expected
|
|
} else {
|
|
response = QRPacketData::TestResult(PacketTestResult::new(
|
|
!request.expected,
|
|
&text,
|
|
));
|
|
!request.expected
|
|
};
|
|
if result {
|
|
app.log.log(&format!("OK {} {}", request.expected, text));
|
|
} else {
|
|
app.log.log(&format!("NO {} {}", request.expected, text));
|
|
}
|
|
} else if !session.game.is_complete() {
|
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
|
if turn == session.game.turn {
|
|
let play = Play {
|
|
source: match request.data {
|
|
GameMessageData::PlayMove(..) => 0,
|
|
GameMessageData::PlayDrop(..) => 1,
|
|
GameMessageData::PlayAlt(..) => 2,
|
|
_ => 0,
|
|
},
|
|
from, to,
|
|
meta: 0,
|
|
};
|
|
|
|
if session.game.process(&play).is_ok() {
|
|
// Commit play to history
|
|
app.filesystem.session_history_push(session.id, play).ok();
|
|
|
|
// Forward messsage to all clients
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::GameMessage(request.clone())
|
|
));
|
|
}
|
|
|
|
// Send status to players.
|
|
send_user_status.push(session.p_dawn.user);
|
|
send_user_status.push(session.p_dusk.user);
|
|
|
|
session.undo = 0;
|
|
} else {
|
|
println!("notice: bad play {} {} {}", play.source, play.from, play.to);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GameMessageData::Undo(turn, _) => {
|
|
use packet::PacketGameMessage;
|
|
|
|
if session.game.turn > 0 && !session.game.is_complete() {
|
|
let player = if user_id == Some(session.p_dawn.user) { 1 }
|
|
else if user_id == Some(session.p_dusk.user) { 2 }
|
|
else { 0 };
|
|
|
|
if player != 0 {
|
|
if turn == session.game.turn && (session.undo & player) == 0 {
|
|
if session.undo == 0 {
|
|
|
|
// Send undo request to opposing player
|
|
let packet = GameMessageData::Undo(turn, 0);
|
|
|
|
if player == 1 {
|
|
for cid in &session.p_dusk.connections {
|
|
packets.push(QRPacket::new(
|
|
*cid,
|
|
QRPacketData::GameMessage(PacketGameMessage::with(packet))
|
|
));
|
|
}
|
|
} else {
|
|
for cid in &session.p_dawn.connections {
|
|
packets.push(QRPacket::new(
|
|
*cid,
|
|
QRPacketData::GameMessage(PacketGameMessage::with(packet))
|
|
));
|
|
}
|
|
}
|
|
|
|
session.undo |= player;
|
|
} else {
|
|
// Send undo command to clients
|
|
let revert_count = if session.game.turn > 1 && session.undo == (1 << (session.game.turn & 1)) { 2 } else { 1 };
|
|
|
|
for _ in 0..revert_count {
|
|
session.game.history.pop();
|
|
app.filesystem.session_history_pop(session.id).ok();
|
|
}
|
|
session.game.apply_history(&session.game.history.clone()).ok();
|
|
|
|
let packet = GameMessageData::Undo(session.game.turn, 1);
|
|
|
|
// Send undo request to opposing player
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::GameMessage(PacketGameMessage::with(packet))
|
|
));
|
|
}
|
|
|
|
send_user_status.push(session.p_dawn.user);
|
|
send_user_status.push(session.p_dusk.user);
|
|
|
|
session.undo = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GameMessageData::Resign => {
|
|
if !session.game.is_complete() {
|
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
|
|
|
session.undo = 0;
|
|
|
|
// Forward messsage to all clients
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::GameMessage(request.clone())
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GameMessageData::Reaction(_) => {
|
|
|
|
// Forward messsage to all clients
|
|
for (cid, _) in session.get_connections() {
|
|
packets.push(QRPacket::new(
|
|
cid,
|
|
QRPacketData::GameMessage(request.clone())
|
|
));
|
|
}
|
|
}
|
|
|
|
_ => { }
|
|
}
|
|
}
|
|
}
|
|
|
|
match request.data {
|
|
GameMessageData::Error => {
|
|
Some(QRPacket::new(qr.id, QRPacketData::GameMessage(request)))
|
|
}
|
|
_ => {
|
|
for packet in packets {
|
|
app.send_response(packet).await;
|
|
}
|
|
|
|
// Updates already sent; nothing to do here.
|
|
Some(QRPacket::new(qr.id, response))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Challenge
|
|
QRPacketData::QChallenge(request) => {
|
|
app.log.log("Request: Challenge");
|
|
|
|
if let Some(user_id) = user_id {
|
|
if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()).cloned() {
|
|
if let Some(chal_user) = app.get_user_by_id_mut(chal_id) {
|
|
let mut find = false;
|
|
for ch in &chal_user.challenges {
|
|
if *ch == user_id { find = true; }
|
|
}
|
|
if !find {
|
|
chal_user.challenges.push(user_id);
|
|
send_user_status.push(chal_id);
|
|
} else {
|
|
app.log.log("notice: duplicate challenge.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
// ChallengeAnswer
|
|
QRPacketData::QChallengeAnswer(request) => {
|
|
app.log.log("Request: Challenge Answer");
|
|
|
|
use crate::app::session::{SessionToken, SessionSecret, Player};
|
|
|
|
let mut response = PacketChallengeAnswerResponse::new();
|
|
response.status = STATUS_ERROR;
|
|
|
|
if let Some(user_id) = user_id {
|
|
if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()).copied() {
|
|
|
|
// Check if challenge exists.
|
|
if let Some(user) = app.get_user_by_id_mut(user_id) {
|
|
match user.challenges.binary_search(&chal_id) {
|
|
Ok(pos) => {
|
|
response.status = if request.answer { STATUS_OK } else { STATUS_REJECT };
|
|
user.challenges.remove(pos);
|
|
}
|
|
Err(_) => { }
|
|
}
|
|
}
|
|
|
|
// Create session if response was positive.
|
|
if response.status == STATUS_OK {
|
|
|
|
// 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();
|
|
|
|
// Add session to latest list.
|
|
let chain_id = app.session_time.add(token);
|
|
|
|
// Choose player seats.
|
|
let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis() as u64;
|
|
|
|
// Build session.
|
|
let mut session = Session {
|
|
id:0,
|
|
token,
|
|
secret,
|
|
game:game::Game::new(),
|
|
p_dawn:Player {
|
|
user:if (time & 1) == 0 { user_id } else { chal_id },
|
|
connections:Vec::new(),
|
|
},
|
|
p_dusk:Player {
|
|
user:if (time & 1) == 0 { chal_id } else { user_id },
|
|
connections:Vec::new(),
|
|
},
|
|
connections:Vec::new(),
|
|
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
|
|
chain_id,
|
|
|
|
undo:0,
|
|
};
|
|
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_create(&session).unwrap();
|
|
|
|
app.sessions.set(&token, session);
|
|
app.session_time.set(chain_id, token);
|
|
|
|
response.token = token;
|
|
|
|
send_user_status.push(chal_id);
|
|
}
|
|
|
|
send_user_status.push(user_id);
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RChallengeAnswer(response)))
|
|
}
|
|
|
|
// ChallengeList
|
|
QRPacketData::QChallengeList => {
|
|
app.log.log("Request: Challenge List");
|
|
|
|
let mut response = PacketChallengeListResponse::new();
|
|
response.status = STATUS_NOAUTH;
|
|
|
|
if let Some(user_id) = user_id {
|
|
if let Some(user) = app.get_user_by_id(user_id) {
|
|
response.status = STATUS_OK;
|
|
|
|
for ch_id in &user.challenges {
|
|
if let Some(target) = app.get_user_by_id(*ch_id) {
|
|
response.challenges.push(PacketChallengeListData {
|
|
handle:target.handle.clone(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RChallengeList(response)))
|
|
}
|
|
|
|
// UserList
|
|
QRPacketData::QUserList => {
|
|
app.log.log("Request: User List");
|
|
|
|
let mut response = PacketUserListResponse::new();
|
|
response.status = STATUS_NOAUTH;
|
|
|
|
if let Some(user_id) = user_id {
|
|
for (_, uid) in app.user_id.pairs() {
|
|
let uid = *uid as u32;
|
|
|
|
if uid != user_id {
|
|
if let Some(user) = app.get_user_by_id(uid) {
|
|
response.records.push(PacketUserListData {
|
|
handle:user.handle.clone(),
|
|
is_online:user.connection.is_some(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
response.records.sort_by(|a, b| {
|
|
b.is_online.cmp(&a.is_online)
|
|
.then(a.handle.to_lowercase().cmp(&b.handle.to_lowercase()))
|
|
});
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RUserList(response)))
|
|
}
|
|
|
|
// InviteAcquire
|
|
QRPacketData::QInviteAcquire => {
|
|
use crate::app::{Invitation, InviteToken};
|
|
app.log.log("Request: Invite Acquire");
|
|
|
|
let mut response = PacketInviteAcquireResponse::new();
|
|
|
|
if let Some(user_id) = user_id {
|
|
|
|
// Count number of invites acquired by user.
|
|
let mut count_invites = 0;
|
|
let invites = app.invites.list();
|
|
for id in invites {
|
|
if let Some(invite) = app.invites.get(id) {
|
|
if let Some(parent) = invite.parent {
|
|
if parent == user_id {
|
|
count_invites += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create new invite if below threshold.
|
|
const MAX_INVITES :usize = 10;
|
|
if count_invites < MAX_INVITES {
|
|
let mut token = InviteToken::default();
|
|
loop {
|
|
rng.fill(&mut token).ok();
|
|
for byte in &mut token {
|
|
*byte = *byte % 62;
|
|
*byte += if *byte < 10 {
|
|
'0' as u8
|
|
} else if *byte < 36 {
|
|
'a' as u8 - 10
|
|
} else {
|
|
'A' as u8 - 36
|
|
};
|
|
}
|
|
|
|
if app.invite_tokens.get(&token).is_none() { break }
|
|
}
|
|
|
|
let invite = Invitation {
|
|
token,
|
|
parent:Some(user_id),
|
|
};
|
|
|
|
let id = app.invites.add(invite);
|
|
app.invite_tokens.set(&token, id as u32);
|
|
|
|
response.status = STATUS_OK;
|
|
} else {
|
|
response.status = STATUS_ERROR;
|
|
}
|
|
} else {
|
|
response.status = STATUS_NOAUTH;
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RInviteAcquire(response)))
|
|
}
|
|
|
|
// InviteList
|
|
QRPacketData::QInviteList => {
|
|
app.log.log("Request: Invite List");
|
|
|
|
let mut response = PacketInviteListResponse::new();
|
|
|
|
if let Some(user_id) = user_id {
|
|
|
|
// Get user's invitations.
|
|
let invites = app.invites.list();
|
|
for id in invites {
|
|
if let Some(invite) = app.invites.get(id) {
|
|
if let Some(parent) = invite.parent {
|
|
if parent == user_id {
|
|
response.records.push(invite.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
response.status = STATUS_OK;
|
|
} else {
|
|
response.status = STATUS_NOAUTH;
|
|
}
|
|
|
|
Some(QRPacket::new(qr.id, QRPacketData::RInviteList(response)))
|
|
}
|
|
|
|
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
|
} {
|
|
Some(response) => {
|
|
app.send_response(response).await;
|
|
}
|
|
None => { }
|
|
}
|
|
|
|
for user_id in &send_user_status {
|
|
app.send_status(*user_id).await;
|
|
}
|
|
send_user_status.clear();
|
|
}
|
|
}
|
|
|
|
fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateResponse
|
|
{
|
|
use protocol::PacketGameStateResponse;
|
|
|
|
let mut response = PacketGameStateResponse::new();
|
|
|
|
response.token = session.token;
|
|
response.player = 2;
|
|
|
|
// Get Dawn handle
|
|
if let Some(user) = app.get_user_by_id(session.p_dawn.user) {
|
|
response.dawn_handle = user.handle.clone();
|
|
}
|
|
|
|
// Get Dusk handle
|
|
if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
|
|
response.dusk_handle = user.handle.clone();
|
|
}
|
|
response.dawn_online = session.p_dawn.connections.len() > 0;
|
|
response.dusk_online = session.p_dusk.connections.len() > 0;
|
|
response.spectators = session.connections.len() as u32;
|
|
|
|
response.undo = session.undo;
|
|
|
|
// Get history
|
|
response.history = session.game.history.clone();
|
|
|
|
response
|
|
}
|