2022-04-06 11:49:54 +00:00
//Four Line Dropper Backend - A server enabling networked multiplayer for Four Line Dropper
//Copyright (C) 2022 Stephen Seo
//
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
2022-04-27 05:11:02 +00:00
use crate ::{
constants ::BACKEND_PHRASE_MAX_LENGTH ,
2022-04-29 09:30:41 +00:00
db_handler ::{ CheckPairingType , DBHandlerRequest , GetIDSenderType } ,
state ::EmoteEnum ,
2022-04-27 05:11:02 +00:00
} ;
2022-03-28 07:31:53 +00:00
2022-03-18 10:29:38 +00:00
use std ::{
2022-03-18 14:43:15 +00:00
sync ::mpsc ::{ sync_channel , SyncSender } ,
2022-03-18 10:29:38 +00:00
time ::Duration ,
} ;
2022-03-16 03:26:18 +00:00
use serde_json ::Value ;
2022-03-29 08:02:57 +00:00
const DB_REQUEST_TIMEOUT : Duration = Duration ::from_secs ( 5 ) ;
2022-03-18 14:43:15 +00:00
pub fn handle_json (
root : Value ,
2022-03-28 07:31:53 +00:00
tx : SyncSender < DBHandlerRequest > ,
2022-03-18 14:43:15 +00:00
_shutdown_tx : SyncSender < ( ) > , // maybe used here, not sure if it will be
) -> Result < String , String > {
2022-03-16 03:26:18 +00:00
if let Some ( Value ::String ( type_str ) ) = root . get ( " type " ) {
match type_str . as_str ( ) {
2022-04-27 02:42:28 +00:00
" pairing_request " = > handle_pairing_request ( root , tx ) ,
2022-03-29 08:02:57 +00:00
" check_pairing " = > handle_check_pairing ( root , tx ) ,
2022-03-31 11:38:22 +00:00
" place_token " = > handle_place_token ( root , tx ) ,
2022-03-31 08:38:03 +00:00
" disconnect " = > handle_disconnect ( root , tx ) ,
2022-03-30 11:43:49 +00:00
" game_state " = > handle_game_state ( root , tx ) ,
2022-04-29 08:16:32 +00:00
" send_emote " = > handle_send_emote ( root , tx ) ,
2022-03-16 03:31:18 +00:00
_ = > Err ( " { \" type \" : \" invalid_type \" } " . into ( ) ) ,
2022-03-16 03:26:18 +00:00
}
} else {
Err ( " { \" type \" : \" invalid_json \" } " . into ( ) )
}
}
2022-04-27 02:42:28 +00:00
fn handle_pairing_request ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
2022-03-29 06:02:20 +00:00
let ( player_tx , player_rx ) = sync_channel ::< GetIDSenderType > ( 1 ) ;
2022-04-27 02:42:28 +00:00
let mut phrase : Option < String > = None ;
if let Some ( phrase_text ) = root . get ( " phrase " ) {
2022-04-27 05:11:02 +00:00
if let Some ( mut phrase_str ) = phrase_text . as_str ( ) {
2022-04-27 03:47:45 +00:00
if ! phrase_str . is_empty ( ) {
2022-04-27 05:11:02 +00:00
if phrase_str . len ( ) > BACKEND_PHRASE_MAX_LENGTH {
let mut idx = BACKEND_PHRASE_MAX_LENGTH ;
while idx > 0 & & ! phrase_str . is_char_boundary ( idx ) {
idx - = 1 ;
}
if idx = = 0 {
phrase_str = " " ;
} else {
phrase_str = phrase_str . split_at ( idx ) . 0 ;
}
}
2022-04-27 05:15:54 +00:00
if ! phrase_str . is_empty ( ) {
phrase = Some ( phrase_str . to_owned ( ) ) ;
}
2022-04-27 03:47:45 +00:00
}
2022-04-27 02:42:28 +00:00
}
}
if tx
. send ( DBHandlerRequest ::GetID {
response_sender : player_tx ,
phrase ,
} )
. is_err ( )
{
2022-03-18 10:29:38 +00:00
return Err ( " { \" type \" : \" pairing_response \" , \" status \" : \" internal_error \" } " . into ( ) ) ;
}
2022-04-08 02:01:33 +00:00
if let Ok ( ( pid_opt , is_cyan_opt ) ) = player_rx . recv_timeout ( DB_REQUEST_TIMEOUT ) {
if pid_opt . is_none ( ) {
return Ok ( " { \" type \" : \" pairing_response \" , \" status \" : \" too_many_players \" } " . into ( ) ) ;
}
let pid = pid_opt . unwrap ( ) ;
2022-03-29 08:02:57 +00:00
if let Some ( is_cyan ) = is_cyan_opt {
Ok ( format! (
2022-04-01 08:17:42 +00:00
" {{ \" type \" : \" pairing_response \" , \" id \" : {}, \" status \" : \" paired \" , \" color \" : \" {} \" }} " ,
2022-03-29 08:02:57 +00:00
pid ,
if is_cyan { " cyan " } else { " magenta " }
) )
} else {
Ok ( format! (
2022-04-01 08:17:42 +00:00
" {{ \" type \" : \" pairing_response \" , \" id \" : {}, \" status \" : \" waiting \" }} " ,
2022-03-29 08:02:57 +00:00
pid
) )
}
2022-03-18 10:29:38 +00:00
} else {
Err ( " { \" type \" : \" pairing_response \" , \" status \" : \" internal_error_timeout \" } " . into ( ) )
}
2022-03-16 03:26:18 +00:00
}
2022-03-29 08:02:57 +00:00
fn handle_check_pairing ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
2022-03-30 11:43:49 +00:00
let id_option = root . get ( " id " ) ;
if id_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let player_id = id_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let player_id : u32 = player_id
. try_into ( )
. map_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let ( request_tx , request_rx ) = sync_channel ::< CheckPairingType > ( 1 ) ;
if tx
. send ( DBHandlerRequest ::CheckPairing {
id : player_id ,
response_sender : request_tx ,
} )
. is_err ( )
{
2022-04-06 09:43:17 +00:00
return Err ( " { \" type \" : \" pairing_status \" , \" status \" : \" internal_error \" } " . into ( ) ) ;
2022-03-30 11:43:49 +00:00
}
if let Ok ( ( exists , is_paired , is_cyan ) ) = request_rx . recv_timeout ( DB_REQUEST_TIMEOUT ) {
if ! exists {
2022-04-06 09:43:17 +00:00
Err ( " { \" type \" : \" pairing_status \" , \" status \" : \" unknown_id \" } " . into ( ) )
2022-03-30 11:43:49 +00:00
} else if is_paired {
Ok ( format! (
2022-04-06 09:43:17 +00:00
" {{ \" type \" : \" pairing_status \" , \" status \" : \" paired \" , \" color \" : \" {} \" }} " ,
2022-03-30 11:43:49 +00:00
if is_cyan { " cyan " } else { " magenta " }
) )
2022-03-29 08:02:57 +00:00
} else {
2022-04-06 09:43:17 +00:00
Ok ( " { \" type \" : \" pairing_status \" , \" status \" : \" waiting \" } " . into ( ) )
2022-03-29 08:02:57 +00:00
}
} else {
2022-04-06 09:43:17 +00:00
Err ( " { \" type \" : \" pairing_status \" , \" status \" : \" internal_error_timeout \" } " . into ( ) )
2022-03-29 08:02:57 +00:00
}
2022-03-16 03:26:18 +00:00
}
2022-03-31 11:38:22 +00:00
fn handle_place_token ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
let id_option = root . get ( " id " ) ;
if id_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let player_id = id_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let player_id : u32 = player_id
. try_into ( )
. map_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let position_option = root . get ( " position " ) ;
if position_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let position = position_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let position : usize = position
. try_into ( )
. m ap_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let ( resp_tx , resp_rx ) = sync_channel ( 1 ) ;
if tx
. send ( DBHandlerRequest ::PlaceToken {
id : player_id ,
pos : position ,
response_sender : resp_tx ,
} )
. is_err ( )
{
return Err ( String ::from (
" { \" type \" : \" place_token \" , \" status \" : \" internal_error \" } " ,
) ) ;
}
let place_result = resp_rx . recv_timeout ( DB_REQUEST_TIMEOUT ) ;
if let Ok ( Ok ( ( place_status , board_opt ) ) ) = place_result {
if let Some ( board_string ) = board_opt {
Ok ( format! (
" {{ \" type \" : \" place_token \" , \" status \" : \" {} \" , \" board \" : \" {} \" }} " ,
place_status , board_string
) )
} else {
Ok ( format! (
" {{ \" type \" : \" place_token \" , \" status \" : \" {} \" }} " ,
place_status
) )
}
} else if let Ok ( Err ( place_error ) ) = place_result {
Err ( format! (
" {{ \" type \" : \" place_token \" , \" status \" : \" {} \" }} " ,
place_error
) )
} else {
Err ( String ::from (
" { \" type \" : \" place_token \" , \" status \" : \" internal_error \" } " ,
) )
}
2022-03-16 03:26:18 +00:00
}
2022-03-31 08:38:03 +00:00
fn handle_disconnect ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
let id_option = root . get ( " id " ) ;
if id_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let player_id = id_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let player_id : u32 = player_id
. try_into ( )
. map_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let ( resp_tx , resp_rx ) = sync_channel ( 1 ) ;
if tx
. send ( DBHandlerRequest ::DisconnectID {
id : player_id ,
response_sender : resp_tx ,
} )
. is_err ( )
{
return Err ( String ::from (
" { \" type \" : \" disconnect \" , \" status \" : \" internal_error \" } " ,
) ) ;
}
if let Ok ( was_removed ) = resp_rx . recv_timeout ( DB_REQUEST_TIMEOUT ) {
if was_removed {
Ok ( String ::from ( " { \" type \" : \" disconnect \" , \" status \" : \" ok \" } " ) )
} else {
Ok ( String ::from (
" { \" type \" : \" disconnect \" , \" status \" : \" unknown_id \" } " ,
) )
}
} else {
Err ( String ::from (
" { \" type \" : \" disconnect \" , \" status \" : \" internal_error \" } " ,
) )
}
2022-03-16 03:26:18 +00:00
}
2022-03-30 11:43:49 +00:00
fn handle_game_state ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
let id_option = root . get ( " id " ) ;
if id_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let player_id = id_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let player_id : u32 = player_id
. try_into ( )
. map_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let ( resp_tx , resp_rx ) = sync_channel ( 1 ) ;
if tx
. send ( DBHandlerRequest ::GetGameState {
id : player_id ,
response_sender : resp_tx ,
} )
. is_err ( )
{
return Err ( " { \" type \" : \" game_state \" , \" status \" : \" internal_error \" } " . into ( ) ) ;
}
2022-04-30 07:27:43 +00:00
if let Ok ( ( db_game_state , board_string_opt , updated_time_opt , received_emote_opt ) ) =
2022-04-29 08:16:32 +00:00
resp_rx . recv_timeout ( DB_REQUEST_TIMEOUT )
{
2022-03-30 11:43:49 +00:00
if let Some ( board_string ) = board_string_opt {
2022-04-30 07:27:43 +00:00
let updated_time = if let Some ( time_string ) = updated_time_opt {
time_string
} else {
return Err ( " { \" type \" : \" game_state \" , \" status \" : \" internal_error \" } " . into ( ) ) ;
} ;
2022-04-29 08:16:32 +00:00
if let Some ( emote ) = received_emote_opt {
Ok ( format! (
2022-04-30 07:27:43 +00:00
" {{ \" type \" : \" game_state \" , \" status \" : \" {} \" , \" board \" : \" {} \" , \" peer_emote \" : \" {} \" , \" updated_time \" : \" {} \" }} " ,
db_game_state , board_string , emote , updated_time
2022-04-29 08:16:32 +00:00
) )
} else {
Ok ( format! (
2022-04-30 07:27:43 +00:00
" {{ \" type \" : \" game_state \" , \" status \" : \" {} \" , \" board \" : \" {} \" , \" updated_time \" : \" {} \" }} " ,
db_game_state , board_string , updated_time
2022-04-29 08:16:32 +00:00
) )
}
2022-03-30 11:43:49 +00:00
} else {
Ok ( format! (
" {{ \" type \" : \" game_state \" , \" status \" : \" {} \" }} " ,
db_game_state
) )
}
} else {
Err ( " { \" type \" : \" game_state \" , \" status \" : \" internal_error_timeout \" } " . into ( ) )
}
2022-03-16 03:26:18 +00:00
}
2022-04-29 08:16:32 +00:00
fn handle_send_emote ( root : Value , tx : SyncSender < DBHandlerRequest > ) -> Result < String , String > {
let id_option = root . get ( " id " ) ;
if id_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let player_id = id_option
. unwrap ( )
. as_u64 ( )
. ok_or_else ( | | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let player_id : u32 = player_id
. try_into ( )
. map_err ( | _ | String ::from ( " { \" type \" : \" invalid_syntax \" } " ) ) ? ;
let emote_type_option = root . get ( " emote " ) ;
if emote_type_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let emote_type_option = emote_type_option . unwrap ( ) . as_str ( ) ;
if emote_type_option . is_none ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let emote_type = emote_type_option . unwrap ( ) ;
let emote_enum : Result < EmoteEnum , ( ) > = emote_type . try_into ( ) ;
if emote_enum . is_err ( ) {
return Err ( " { \" type \" : \" invalid_syntax \" } " . into ( ) ) ;
}
let emote_enum = emote_enum . unwrap ( ) ;
let ( resp_tx , resp_rx ) = sync_channel ( 1 ) ;
if tx
. send ( DBHandlerRequest ::SendEmote {
id : player_id ,
emote_type : emote_enum ,
response_sender : resp_tx ,
} )
. is_err ( )
{
return Err ( " { \" type \" : \" send_emote \" , \" status \" : \" internal_error \" } " . into ( ) ) ;
}
if let Ok ( db_response ) = resp_rx . recv_timeout ( DB_REQUEST_TIMEOUT ) {
if db_response . is_ok ( ) {
Ok ( " { \" type \" : \" send_emote \" , \" status \" : \" ok \" } " . into ( ) )
} else {
Err ( " { \" type \" : \" send_emote \" , \" status \" : \" internal_error \" } " . into ( ) )
}
} else {
Err ( " { \" type \" : \" send_emote \" , \" status \" : \" internal_error \" } " . into ( ) )
}
}