/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */ /* * Copyright 2023 - 2024 by Eric House (xwords@eehouse.org). All rights * reserved. * * 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 2 * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "extcmds.h" #include "device.h" #include "strutils.h" #include "linuxmain.h" #include "gamesdb.h" #include "dbgutil.h" static XP_U32 castGid( cJSON* obj ) { XP_U32 gameID; sscanf( obj->valuestring, "%X", &gameID ); return gameID; } static void makeObjIfNot( cJSON** objp ) { if ( NULL == *objp ) { *objp = cJSON_CreateObject(); } } static void addStringToObject( cJSON** objp, const char* key, const char* value ) { makeObjIfNot( objp ); cJSON_AddStringToObject( *objp, key, value ); } static void addGIDToObject( cJSON** objp, XP_U32 gid, const char* key ) { char buf[16]; sprintf( buf, "%08X", gid ); addStringToObject( objp, key, buf ); } static void addObjectToObject( cJSON** objp, const char* key, cJSON* value ) { makeObjIfNot( objp ); cJSON_AddItemToObject( *objp, key, value ); } static void addSuccessToObject( cJSON** objp, XP_Bool success ) { makeObjIfNot( objp ); cJSON_AddBoolToObject( *objp, "success", success ); } static XP_U32 gidFromObject( const cJSON* obj ) { cJSON* tmp = cJSON_GetObjectItem( obj, "gid" ); XP_ASSERT( !!tmp ); return castGid( tmp ); } static XP_Bool inviteFromArgs( CmdWrapper* wr, cJSON* args ) { XP_U32 gameID = gidFromObject( args ); cJSON* remotes = cJSON_GetObjectItem( args, "remotes" ); int nRemotes = cJSON_GetArraySize(remotes); CommsAddrRec destAddrs[nRemotes]; XP_MEMSET( destAddrs, 0, sizeof(destAddrs) ); XP_U16 channels[nRemotes]; XP_MEMSET( channels, 0, sizeof(channels) ); for ( int ii = 0; ii < nRemotes; ++ii ) { cJSON* item = cJSON_GetArrayItem( remotes, ii ); cJSON* tmp = cJSON_GetObjectItem( item, "channel" ); XP_ASSERT( !!tmp ); channels[ii] = tmp->valueint; XP_LOGFF( "read channel: %X", channels[ii] ); cJSON* addr = cJSON_GetObjectItem( item, "addr" ); XP_ASSERT( !!addr ); tmp = cJSON_GetObjectItem( addr, "mqtt" ); if ( !!tmp ) { XP_LOGFF( "parsing mqtt: %s", tmp->valuestring ); addr_addType( &destAddrs[ii], COMMS_CONN_MQTT ); XP_Bool success = strToMQTTCDevID( tmp->valuestring, &destAddrs[ii].u.mqtt.devID ); XP_ASSERT( success ); } tmp = cJSON_GetObjectItem( addr, "sms" ); if ( !!tmp ) { XP_LOGFF( "parsing sms: %s", tmp->valuestring ); addr_addType( &destAddrs[ii], COMMS_CONN_SMS ); XP_STRCAT( destAddrs[ii].u.sms.phone, tmp->valuestring ); destAddrs[ii].u.sms.port = 1; } } (*wr->procs.addInvites)( wr->closure, gameID, nRemotes, channels, destAddrs ); LOG_RETURN_VOID(); return XP_TRUE; } static XP_Bool moveifFromArgs( CmdWrapper* wr, cJSON* args ) { XP_U32 gameID = gidFromObject( args ); cJSON* tmp = cJSON_GetObjectItem( args, "tryTrade" ); XP_Bool tryTrade = !!tmp && cJSON_IsTrue( tmp ); return (*wr->procs.makeMoveIf)( wr->closure, gameID, tryTrade ); } static XP_Bool chatFromArgs( CmdWrapper* wr, cJSON* args ) { XP_U32 gameID = gidFromObject( args ); cJSON* tmp = cJSON_GetObjectItem( args, "msg" ); const char* msg = tmp->valuestring; return (*wr->procs.sendChat)( wr->closure, gameID, msg ); } /* Return 'gid' of new game */ static XP_U32 rematchFromArgs( CmdWrapper* wr, cJSON* args ) { XP_U32 result = 0; XP_U32 gameID = gidFromObject( args ); cJSON* tmp = cJSON_GetObjectItem( args, "rematchOrder" ); RematchOrder ro = roFromStr( tmp->valuestring ); XP_U32 newGameID = 0; if ( (*wr->procs.makeRematch)( wr->closure, gameID, ro, &newGameID ) ) { result = newGameID; } return result; } static XP_Bool getGamesStateForArgs( CmdWrapper* wr, cJSON* args, cJSON** states, cJSON** orders ) { LOG_FUNC(); LaunchParams* params = wr->params; *states = cJSON_CreateArray(); cJSON* gids = cJSON_GetObjectItem( args, "gids" ); XP_Bool success = !!gids; for ( int ii = 0 ; success && ii < cJSON_GetArraySize(gids) ; ++ii ) { XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) ); GameInfo gib; if ( gdb_getGameInfoForGID( params->pDb, gameID, &gib ) ) { cJSON* item = NULL; addGIDToObject( &item, gameID, "gid" ); cJSON_AddBoolToObject( item, "gameOver", gib.gameOver ); cJSON_AddNumberToObject( item, "nPending", gib.nPending ); cJSON_AddNumberToObject( item, "nMoves", gib.nMoves ); cJSON_AddNumberToObject( item, "nTiles", gib.nTiles ); cJSON_AddItemToArray( *states, item ); } } XP_LOGFF( "done with states" ); /* got here */ if ( success && !!orders ) { cJSON* gids = cJSON_GetObjectItem( args, "orders" ); if ( !gids ) { *orders = NULL; } else { *orders = cJSON_CreateArray(); for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) { XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) ); const CommonGlobals* cg = (*wr->procs.getForGameID)( wr->closure, gameID ); if ( !cg ) { continue; } const XWGame* game = &cg->game; if ( server_getGameIsConnected( game->server ) ) { const CurGameInfo* gi = cg->gi; LOGGI( gi, __func__ ); cJSON* order = NULL; addGIDToObject( &order, gameID, "gid" ); cJSON* players = cJSON_CreateArray(); for ( int jj = 0; jj < gi->nPlayers; ++jj ) { XP_LOGFF( "looking at player %d", jj ); const LocalPlayer* lp = &gi->players[jj]; XP_LOGFF( "adding player %d: %s", jj, lp->name ); cJSON* cName = cJSON_CreateString( lp->name ); cJSON_AddItemToArray( players, cName); } cJSON_AddItemToObject( order, "players", players ); cJSON_AddItemToArray( *orders, order ); } } } } LOG_RETURNF( "%s", boolToStr(success) ); return success; } /* Return for each gid and array of player names, in play order, and including for each whether it's the host and if a robot. For now let's try by opening each game (yeah! yuck!) to read the info directly. Later add to a the db accessed by gamesdb.c */ static cJSON* getPlayersForArgs( CmdWrapper* wr, cJSON* args ) { cJSON* result = cJSON_CreateArray(); cJSON* gids = cJSON_GetObjectItem( args, "gids" ); for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) { XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) ); const CommonGlobals* cg = (*wr->procs.getForGameID)( wr->closure, gameID ); const CurGameInfo* gi = cg->gi; LOGGI( gi, __func__ ); const XWGame* game = &cg->game; cJSON* players = cJSON_CreateArray(); for ( int jj = 0; jj < gi->nPlayers; ++jj ) { cJSON* playerObj = NULL; const LocalPlayer* lp = &gi->players[jj]; XP_LOGFF( "adding player %d: %s", jj, lp->name ); addStringToObject( &playerObj, "name", lp->name ); XP_Bool isLocal = lp->isLocal; cJSON_AddBoolToObject( playerObj, "isLocal", isLocal ); /* Roles: I don't think a guest in a 3- or 4-device game knows which of the other players is host. Host is who it sends its moves to, but is there an order there? */ XP_Bool isHost = game_getIsHost( game ); isHost = isHost && isLocal; cJSON_AddBoolToObject( playerObj, "isHost", isHost ); cJSON_AddItemToArray( players, playerObj ); } cJSON_AddItemToArray( result, players ); } return result; } static XP_U32 makeGameFromArgs( CmdWrapper* wr, cJSON* args ) { LaunchParams* params = wr->params; CurGameInfo gi = {0}; gi_copy( MPPARM(params->mpool) &gi, ¶ms->pgi ); gi.boardSize = 15; gi.traySize = 7; cJSON* tmp = cJSON_GetObjectItem( args, "nPlayers" ); XP_ASSERT( !!tmp ); gi.nPlayers = tmp->valueint; tmp = cJSON_GetObjectItem( args, "boardSize" ); if ( !!tmp ) { gi.boardSize = tmp->valueint; } tmp = cJSON_GetObjectItem( args, "traySize" ); if ( !!tmp ) { gi.traySize = tmp->valueint; } tmp = cJSON_GetObjectItem( args, "allowSub7" ); gi.tradeSub7 = !!tmp && cJSON_IsTrue( tmp ); tmp = cJSON_GetObjectItem( args, "isSolo" ); XP_ASSERT( !!tmp ); XP_Bool isSolo = cJSON_IsTrue( tmp ); tmp = cJSON_GetObjectItem( args, "timerSeconds" ); if ( !!tmp ) { gi.gameSeconds = tmp->valueint; if ( 0 != gi.gameSeconds ) { gi.timerEnabled = XP_TRUE; } } tmp = cJSON_GetObjectItem( args, "hostPosn" ); XP_ASSERT( !!tmp ); int hostPosn = tmp->valueint; replaceStringIfDifferent( params->mpool, &gi.players[hostPosn].name, params->localName ); for ( int ii = 0; ii < gi.nPlayers; ++ii ) { LocalPlayer* lp = &gi.players[ii]; lp->isLocal = isSolo || ii == hostPosn; if ( isSolo ) { lp->robotIQ = ii == hostPosn ? 0 : 1; } } gi.serverRole = isSolo ? SERVER_STANDALONE : SERVER_ISHOST; tmp = cJSON_GetObjectItem( args, "dict" ); XP_ASSERT( tmp ); replaceStringIfDifferent( params->mpool, &gi.dictName, tmp->valuestring ); /* cb_dims dims; */ /* figureDims( aGlobals, &dims ); */ XP_U32 newGameID; bool success = (*wr->procs.newGame)( wr->closure, &gi, &newGameID ); XP_ASSERT( success ); gi_disposePlayerInfo( MPPARM(params->mpool) &gi ); return newGameID; } static gboolean on_incoming_signal( GSocketService* XP_UNUSED(service), GSocketConnection* connection, GObject* XP_UNUSED(source_object), gpointer user_data ) { XP_LOGFF( "called" ); CmdWrapper* wr = (CmdWrapper*)user_data; // CursesAppGlobals* aGlobals = (CursesAppGlobals*)user_data; LaunchParams* params = wr->params; XP_U32 startTime = dutil_getCurSeconds( params->dutil, NULL_XWE ); GInputStream* istream = g_io_stream_get_input_stream( G_IO_STREAM(connection) ); short len; gssize nread = g_input_stream_read( istream, &len, sizeof(len), NULL, NULL ); XP_ASSERT( nread == sizeof(len) ); len = ntohs(len); gchar buf[len+1]; nread = g_input_stream_read( istream, buf, len, NULL, NULL ); if ( 0 <= nread ) { XP_ASSERT( nread == len ); buf[nread] = '\0'; XP_LOGFF( "Message: \"%s\"\n", buf ); cJSON* reply = cJSON_CreateArray(); cJSON* cmds = cJSON_Parse( buf ); XP_LOGFF( "got msg with array of len %d", cJSON_GetArraySize(cmds) ); for ( int ii = 0 ; ii < cJSON_GetArraySize(cmds) ; ++ii ) { cJSON* item = cJSON_GetArrayItem( cmds, ii ); cJSON* cmd = cJSON_GetObjectItem( item, "cmd" ); cJSON* key = cJSON_GetObjectItem( item, "key" ); cJSON* args = cJSON_GetObjectItem( item, "args" ); const char* cmdStr = cmd->valuestring; cJSON* response = NULL; XP_Bool success = XP_TRUE; if ( 0 == strcmp( cmdStr, "quit" ) ) { cJSON* gids; if ( getGamesStateForArgs( wr, args, &gids, NULL ) ) { addObjectToObject( &response, "states", gids ); } (*wr->procs.quit)( wr->closure ); } else if ( 0 == strcmp( cmdStr, "getMQTTDevID" ) ) { MQTTDevID devID; dvc_getMQTTDevID( params->dutil, NULL_XWE, &devID ); char buf[64]; formatMQTTDevID( &devID, buf, sizeof(buf) ); cJSON* devid = cJSON_CreateString( buf ); addObjectToObject( &response, "mqtt", devid ); } else if ( 0 == strcmp( cmdStr, "makeGame" ) ) { XP_U32 newGameID = makeGameFromArgs( wr, args ); success = 0 != newGameID; if ( success ) { addGIDToObject( &response, newGameID, "newGid" ); } } else if ( 0 == strcmp( cmdStr, "invite" ) ) { success = inviteFromArgs( wr, args ); } else if ( 0 == strcmp( cmdStr, "moveIf" ) ) { success = moveifFromArgs( wr, args ); } else if ( 0 == strcmp( cmdStr, "rematch" ) ) { XP_U32 newGameID = rematchFromArgs( wr, args ); success = 0 != newGameID; if ( success ) { addGIDToObject( &response, newGameID, "newGid" ); } } else if ( 0 == strcmp( cmdStr, "getStates" ) ) { cJSON* gids; cJSON* orders; success = getGamesStateForArgs( wr, args, &gids, &orders ); if ( success ) { addObjectToObject( &response, "states", gids ); addObjectToObject( &response, "orders", orders ); } } else if ( 0 == strcmp( cmdStr, "getPlayers" ) ) { cJSON* players = getPlayersForArgs( wr, args ); addObjectToObject( &response, "players", players ); } else if ( 0 == strcmp( cmdStr, "sendChat" ) ) { success = chatFromArgs( wr, args ); } else { success = XP_FALSE; XP_ASSERT(0); } addSuccessToObject( &response, success ); cJSON* tmp = cJSON_CreateObject(); cJSON_AddStringToObject( tmp, "cmd", cmdStr ); cJSON_AddNumberToObject( tmp, "key", key->valueint ); cJSON_AddItemToObject( tmp, "response", response ); /*(void)*/cJSON_AddItemToArray( reply, tmp ); } cJSON_Delete( cmds ); /* this apparently takes care of all children */ char* replyStr = cJSON_PrintUnformatted( reply ); short replyStrLen = strlen(replyStr); XP_LOGFF( "len(%s): %d", replyStr, replyStrLen ); short replyStrNBOLen = htons(replyStrLen); GOutputStream* ostream = g_io_stream_get_output_stream( G_IO_STREAM(connection) ); gsize nwritten; gboolean wroteall = g_output_stream_write_all( ostream, &replyStrNBOLen, sizeof(replyStrNBOLen), &nwritten, NULL, NULL ); XP_ASSERT( wroteall && nwritten == sizeof(replyStrNBOLen) ); wroteall = g_output_stream_write_all( ostream, replyStr, replyStrLen, &nwritten, NULL, NULL ); XP_ASSERT( wroteall && nwritten == replyStrLen ); GError* error = NULL; g_output_stream_close( ostream, NULL, &error ); if ( !!error ) { XP_LOGFF( "g_output_stream_close()=>%s", error->message ); g_error_free( error ); } cJSON_Delete( reply ); free( replyStr ); } XP_U32 consumed = dutil_getCurSeconds( params->dutil, NULL_XWE ) - startTime; if ( 0 < consumed ) { XP_LOGFF( "took %d seconds", consumed ); } LOG_RETURN_VOID(); return FALSE; } GSocketService* cmds_addCmdListener( const CmdWrapper* wr ) { LOG_FUNC(); XP_ASSERT( !!wr && !!wr->params ); const XP_UCHAR* cmdsSocket = wr->params->cmdsSocket; GSocketService* service = NULL; if ( !!cmdsSocket ) { service = g_socket_service_new(); struct sockaddr_un addr = {0}; addr.sun_family = AF_UNIX; strncpy( addr.sun_path, cmdsSocket, sizeof(addr.sun_path) - 1); GSocketAddress* gsaddr = g_socket_address_new_from_native (&addr, sizeof(addr) ); GError* error = NULL; if ( g_socket_listener_add_address( (GSocketListener*)service, gsaddr, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, NULL, NULL, &error ) ) { } else { XP_LOGFF( "g_socket_listener_add_address() failed: %s", error->message ); } g_object_unref( gsaddr ); g_signal_connect( service, "incoming", G_CALLBACK(on_incoming_signal), (void*)wr ); } else { XP_LOGFF( "cmdsSocket not set" ); } LOG_RETURNF( "%p", service ); return service; }