From 2d9cb93ae6b0091c293a8480a203db0d0cc8f091 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 12 Sep 2024 18:17:48 -0700 Subject: [PATCH] add to test ability to invite using known players --- xwords4/common/comms.c | 18 ++-- xwords4/common/knownplyr.c | 2 +- xwords4/common/nli.c | 7 ++ xwords4/common/nli.h | 1 + xwords4/common/server.c | 5 +- xwords4/linux/extcmds.c | 61 +++++++++----- xwords4/linux/gtkboard.c | 3 + xwords4/linux/linuxmain.c | 5 +- xwords4/linux/scripts/netGamesTest.py | 113 ++++++++++++++++++-------- 9 files changed, 144 insertions(+), 71 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 45bc3fae9..5708bab26 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1666,8 +1666,8 @@ pickChannel( const CommsCtxt* comms, const NetLaunchInfo* nli, /* First, do we already have an invitation for this address */ for ( AddressRecord* rec = comms->recs; !!rec; rec = rec->next ) { if ( addrsAreSame( destAddr, &rec->addr ) ) { - result = rec->channelNo; - XP_LOGFF( "addrs match; reusing channel" ); + result = rec->channelNo & CHANNEL_MASK; + XP_LOGFF( "addrs match; reusing channel %d", result ); break; } } @@ -1685,7 +1685,7 @@ pickChannel( const CommsCtxt* comms, const NetLaunchInfo* nli, for ( XP_PlayerAddr chan = 1; chan <= CHANNEL_MASK; ++chan ) { if ( 0 == (gicd.hasInvitesMask & (1 << chan)) ) { result = chan; - XP_LOGFF( "using unused channel" ); + XP_LOGFF( "using unused channel %d", result ); break; } } @@ -1696,14 +1696,14 @@ pickChannel( const CommsCtxt* comms, const NetLaunchInfo* nli, for ( XP_PlayerAddr chan = 1; chan <= CHANNEL_MASK; ++chan ) { if ( 0 == (gicd.hasNonInvitesMask & (1 << chan)) ) { result = chan; - XP_LOGFF( "recycling channel" ); + XP_LOGFF( "recycling channel: %d", result ); break; } } } } - LOG_RETURNF( "%d", result ); + COMMS_LOGFF( "=> 0X%X", result ); return result; } @@ -1714,9 +1714,9 @@ comms_invite( CommsCtxt* comms, XWEnv xwe, const NetLaunchInfo* nli, COMMS_LOGFF("(sendNow=%s)", boolToStr(sendNow)); LOGNLI(nli); WITH_MUTEX(&comms->mutex); - XP_PlayerAddr forceChannel = pickChannel(comms, nli, destAddr); + XP_PlayerAddr forceChannel = pickChannel( comms, nli, destAddr ); XP_LOGFF( "forceChannel: %d", forceChannel ); - XP_ASSERT( 0 < forceChannel && (forceChannel & CHANNEL_MASK) == forceChannel ); + XP_ASSERT( 0 < forceChannel ); if ( 0 < forceChannel ) { XP_ASSERT( (forceChannel & CHANNEL_MASK) == forceChannel ); if ( !haveRealChannel( comms, forceChannel ) ) { @@ -2779,7 +2779,7 @@ getChannelFromInvite( const CommsCtxt* comms, const CommsAddrRec* retAddr, COMMS_LOGFF( "channelNo after: %x", *channelNoP ); } } - COMMS_LOGFF( "=>%s", boolToStr(found) ); + COMMS_LOGFF( "=> %s", boolToStr(found) ); return found; } @@ -2872,7 +2872,7 @@ checkChannelNo( CommsCtxt* comms, const CommsAddrRec* retAddr, XP_PlayerAddr* ch comms->nextChannelNo = channelNo; } *channelNoP = channelNo; - LOG_RETURNF( "%s", boolToStr(success) ); + COMMS_LOGFF( "=> %s", boolToStr(success) ); return success; } diff --git a/xwords4/common/knownplyr.c b/xwords4/common/knownplyr.c index 3b3e6dc65..6102680dc 100644 --- a/xwords4/common/knownplyr.c +++ b/xwords4/common/knownplyr.c @@ -367,7 +367,7 @@ kplr_getAddr( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* name, } releaseStateLocked( dutil, xwe, state ); END_WITH_MUTEX(); - LOG_RETURNF( "%s", boolToStr(found) ); + XP_LOGFF( "(%s) => %s", name, boolToStr(found) ); return found; } diff --git a/xwords4/common/nli.c b/xwords4/common/nli.c index 1f1d73e15..2d82b852c 100644 --- a/xwords4/common/nli.c +++ b/xwords4/common/nli.c @@ -149,6 +149,13 @@ nli_setMQTTDevID( NetLaunchInfo* nli, const MQTTDevID* mqttDevID ) formatMQTTDevID( mqttDevID, nli->mqttDevID, VSIZE(nli->mqttDevID) ); } +void +nli_setPhone( NetLaunchInfo* nli, const XP_UCHAR* phone ) +{ + types_addType( &nli->_conTypes, COMMS_CONN_SMS ); + XP_STRNCPY( nli->phone, phone, VSIZE(nli->phone) ); +} + void nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream ) { diff --git a/xwords4/common/nli.h b/xwords4/common/nli.h index 17553193d..30f084678 100644 --- a/xwords4/common/nli.h +++ b/xwords4/common/nli.h @@ -49,6 +49,7 @@ void nli_setDevID( NetLaunchInfo* nli, XP_U32 devID ); void nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID ); void nli_setGameName( NetLaunchInfo* nli, const XP_UCHAR* gameName ); void nli_setMQTTDevID( NetLaunchInfo* nli, const MQTTDevID* mqttDevID ); +void nli_setPhone( NetLaunchInfo* nli, const XP_UCHAR* phone ); #ifdef XWFEATURE_NLI_FROM_ARGV XP_Bool nli_fromArgv( MPFORMAL NetLaunchInfo* nlip, int argc, const char** argv ); diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 05d0cf418..25e6581be 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -1069,7 +1069,7 @@ server_initClientConnection( ServerCtxt* server, XWEnv xwe ) SRVR_LOGFF( "wierd state: %s (expected XWSTATE_NONE); dropping message", getStateStr(server->nv.gameState) ); } - SRVR_LOGFF( "=>%s", boolToStr(result) ); + SRVR_LOGFF( "=> %s", boolToStr(result) ); return result; } /* server_initClientConnection */ @@ -2145,6 +2145,7 @@ findOrderedSlot( ServerCtxt* server, XWStreamCtxt* stream, for ( int ii = 0; !success && ii < gi->nPlayers; ++ii ) { ServerPlayer* sp = &server->srvPlyrs[ii]; + SRVR_LOGFFV( "ii: %d; deviceIndex: %d", ii, sp->deviceIndex ); if ( UNKNOWN_DEVICE == sp->deviceIndex ) { int addrIndx = rip->addrIndices[ii]; if ( addrsAreSame( &guestAddr, &rip->addrs[addrIndx] ) ) { @@ -2156,7 +2157,7 @@ findOrderedSlot( ServerCtxt* server, XWStreamCtxt* stream, } } - LOG_RETURNF( "%s", boolToStr(success) ); + SRVR_LOGFFV( "()=>%s", boolToStr(success) ); return success; } diff --git a/xwords4/linux/extcmds.c b/xwords4/linux/extcmds.c index c7c0c2b84..c02507a52 100644 --- a/xwords4/linux/extcmds.c +++ b/xwords4/linux/extcmds.c @@ -28,6 +28,7 @@ #include "gamesdb.h" #include "dbgutil.h" #include "stats.h" +#include "knownplyr.h" static XP_U32 castGid( cJSON* obj ) @@ -82,45 +83,61 @@ gidFromObject( const cJSON* obj ) return castGid( tmp ); } + +/* Invite can be via a known player or via */ static XP_Bool inviteFromArgs( CmdWrapper* wr, cJSON* args ) { + XW_DUtilCtxt* dutil = wr->params->dutil; XP_U32 gameID = gidFromObject( args ); + XP_Bool viaKnowns = XP_FALSE; cJSON* remotes = cJSON_GetObjectItem( args, "remotes" ); + if ( !remotes ) { + remotes = cJSON_GetObjectItem( args, "kps" ); + viaKnowns = !!remotes; + } int nRemotes = cJSON_GetArraySize(remotes); CommsAddrRec destAddrs[nRemotes]; XP_MEMSET( destAddrs, 0, sizeof(destAddrs) ); - for ( int ii = 0; ii < nRemotes; ++ii ) { + XP_Bool success = XP_TRUE; + for ( int ii = 0; success && ii < nRemotes; ++ii ) { cJSON* item = cJSON_GetArrayItem( remotes, ii ); - cJSON* addr = cJSON_GetObjectItem( item, "addr" ); - XP_ASSERT( !!addr ); - cJSON* tmp = cJSON_GetObjectItem( addr, "mqtt" ); - if ( !!tmp ) { - XP_LOGFF( "parsing mqtt: %s", tmp->valuestring ); - addr_addType( &destAddrs[ii], COMMS_CONN_MQTT ); -#ifdef DEBUG - XP_Bool success = -#endif - 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; + if ( viaKnowns ) { + /* item is just a name */ + XP_LOGFF( "found kplyr name: %s", item->valuestring ); + success = kplr_getAddr( dutil, NULL_XWE, item->valuestring, + &destAddrs[ii], NULL ); + } else { + cJSON* addr = cJSON_GetObjectItem( item, "addr" ); + XP_ASSERT( !!addr ); + cJSON* tmp = cJSON_GetObjectItem( addr, "mqtt" ); + if ( !!tmp ) { + XP_LOGFF( "parsing mqtt: %s", tmp->valuestring ); + addr_addType( &destAddrs[ii], COMMS_CONN_MQTT ); + 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, destAddrs ); + if ( success ) { + (*wr->procs.addInvites)( wr->closure, gameID, nRemotes, destAddrs ); + } - LOG_RETURN_VOID(); - return XP_TRUE; + LOG_RETURNF( "%s", boolToStr(success) ); + return success; } static XP_Bool diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index ffb164fd8..b6df24c1a 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1406,6 +1406,9 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, const MQTTDevID* devid = mqttc_getDevID( cGlobals->params ); nli_setMQTTDevID( &nli, devid ); } + if ( addr_hasType( &myAddr, COMMS_CONN_SMS ) ) { + nli_setPhone( &nli, myAddr.u.sms.phone ); + } #ifdef DEBUG { diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 365714ea8..d8b861651 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -179,11 +179,10 @@ linux_addInvites( CommonGlobals* cGlobals, XP_U16 nRemotes, CommsAddrRec selfAddr; comms_getSelfAddr( comms, &selfAddr ); + NetLaunchInfo nli; + nli_init( &nli, cGlobals->gi, &selfAddr, 1, 0 ); for ( int ii = 0; ii < nRemotes; ++ii ) { - NetLaunchInfo nli; - nli_init( &nli, cGlobals->gi, &selfAddr, 1, 0 ); - comms_invite( comms, NULL_XWE, &nli, &destAddrs[ii], XP_TRUE ); } } diff --git a/xwords4/linux/scripts/netGamesTest.py b/xwords4/linux/scripts/netGamesTest.py index f5af48d2d..5cb419b55 100755 --- a/xwords4/linux/scripts/netGamesTest.py +++ b/xwords4/linux/scripts/netGamesTest.py @@ -4,6 +4,7 @@ import argparse, datetime, glob, json, os, random, shutil, signal, \ socket, struct, subprocess, sys, threading, time g_ROOT_NAMES = ['Brynn', 'Ariela', 'Kati', 'Eric'] +g_INVITE_HOWS = ['Out-of-App', 'Internal', 'KnownPlayer'] # These must correspond to what the linux app is looking for in roFromStr() g_ROS = ['same', 'low_score_first', 'high_score_first', 'juggle',] gDone = False @@ -472,15 +473,45 @@ class Device(): Device.getForPlayer(guest) \ .expectInvite(newGid, rematchLevel, gid, rematchOrder) + # Randomly choose from g_INVITE_HOWS based on the ratio expressed + # in INVITE_PCTS, without requiring e.g. that they sum to 100% + def _inviteHow(self): + count = len(g_INVITE_HOWS) + # multiply by len to ensure the ratios work without errors + asInts = [count * int(one) for one in self.args.INVITE_PCTS.split(':')] + assert count == len(asInts) + total = sum(asInts) + assert 0 == total % count + choice = random.randrange(0, total) + for indx in range(count): + target = asInts[indx] + if choice < target: + result = g_INVITE_HOWS[indx] + break + choice -= target + return result + # inviting means either causing host to send an in-game invitation # (the way rematch works) or causing guest to register (as happens # when email or SMS is used for invitations.) def invite(self, game): - doOOB = random.randint(0, 99) < self.args.OOB_PCT - if doOOB: self.inviteOutOfBand(game) - else: self.inviteInBand(game) + test = { key: 0 for key in g_INVITE_HOWS } - def inviteOutOfBand(self, game): + how = self._inviteHow() + if how == 'Out-of-App': + success = self.inviteOutOfApp(game) + elif how == 'Internal': + success = self.inviteInApp(game) + elif how == 'KnownPlayer': + success = self.inviteKP(game) + else: assert False + + return success + # doOOB = random.randint(0, 99) < self.args.OOB_PCT + # if doOOB: self.inviteOutOfBand(game) + # else: self.inviteInBand(game) + + def inviteOutOfApp(self, game): # For each invitee, we need to make sure it exists, launch it, # and then send it the equivalent of an emailed invitation. @@ -499,8 +530,7 @@ class Device(): game.needsInvite = False - - def inviteInBand(self, game): + def inviteInApp(self, game): remotes = [] guestDevs = [] useRandomDevID = random.randint(0, 100) < self.args.BAD_INVITE_PCT @@ -519,6 +549,14 @@ class Device(): guestDev.expectInvite(game.gid, game.rematchLevel) game.needsInvite = useRandomDevID + def inviteKP(self, game): + response = self._sendWaitReply('invite', gid=game.gid, + kps=game.guestNames) + if response['success']: + for name in game.guestNames: + Device.getForPlayer(name).expectInvite(game.gid, game.rematchLevel) + game.needsInvite = False + def _mkAddr(self, dev, useRandomDevID=False): addr = {} if self.args.WITH_MQTT: @@ -644,38 +682,38 @@ class Device(): def _checkScript(self): assert os.path.exists(self.args.APP_NEW) - if not os.path.exists(self.script): - scriptArgs = ['exec'] # without exec means terminate() won't work - if self.args.VALGRIND: - scriptArgs += ['valgrind'] - # args += ['--leak-check=full'] - # args += ['--track-origins=yes'] - scriptArgs.append('"${APP}"') + scriptArgs = ['exec'] # without exec means terminate() won't work + if self.args.VALGRIND: + scriptArgs += ['valgrind'] + # args += ['--leak-check=full'] + # args += ['--track-origins=yes'] - scriptArgs += '--db', self.dbName, '--skip-confirm' - scriptArgs += '--localName', self.hostName - scriptArgs += '--cmd-socket-name', self.cmdSocketName + scriptArgs.append('"${APP}"') - if self.args.WITH_MQTT: - scriptArgs += [ '--mqtt-port', self.args.MQTT_PORT, '--mqtt-host', self.args.MQTT_HOST ] + scriptArgs += '--db', self.dbName, '--skip-confirm' + scriptArgs += '--localName', self.hostName + scriptArgs += '--cmd-socket-name', self.cmdSocketName - if self.args.WITH_SMS: - scriptArgs += [ '--sms-number', self.smsNumber ] + if self.args.WITH_MQTT: + scriptArgs += [ '--mqtt-port', self.args.MQTT_PORT, '--mqtt-host', self.args.MQTT_HOST ] - scriptArgs += ['--board-size', '15', '--sort-tiles'] + if self.args.WITH_SMS: + scriptArgs += [ '--sms-number', self.smsNumber ] - # useDupeMode = random.randint(0, 100) < self.args.DUP_PCT - # if not useDupeMode: scriptArgs += ['--trade-pct', self.args.TRADE_PCT] + scriptArgs += ['--board-size', '15', '--sort-tiles'] + + # useDupeMode = random.randint(0, 100) < self.args.DUP_PCT + # if not useDupeMode: scriptArgs += ['--trade-pct', self.args.TRADE_PCT] - # if self.devID: args.extend( ' '.split(self.devID)) - scriptArgs += [ '$*' ] + # if self.devID: args.extend( ' '.split(self.devID)) + scriptArgs += [ '$*' ] - with open( self.script, 'w' ) as fil: - fil.write('#!/bin/bash\n' ) - fil.write('APP="${{APP:-{}}}"\n'.format(self.args.APP_NEW)) - fil.write(' '.join([str(arg) for arg in scriptArgs]) + '\n') - os.chmod(self.script, 0o755) + with open( self.script, 'w' ) as fil: + fil.write('#!/bin/bash\n' ) + fil.write('APP="${{APP:-{}}}"\n'.format(self.args.APP_NEW)) + fil.write(' '.join([str(arg) for arg in scriptArgs]) + '\n') + os.chmod(self.script, 0o755) @staticmethod def printStatus(statusSteps, noPrint): @@ -954,16 +992,23 @@ def mkParser(): parser.add_argument('--bad-invite-pct', dest = 'BAD_INVITE_PCT', default = 0, type=int, help='What pct (0..99) of MQTT invitations will be to non-existant devices') - parser.add_argument('--oob-invite-pct', dest = 'OOB_PCT', default = 50, type=int, - help='What pct (0..99) of guests will "emailed" rather than comms-invited') + + # Inviting to new games is done in one of three ways. New games + # with new players involve an out-of-app process like sending an + # email with a URL. After that, once a remote player is "known", + # known-player invitation uses info in the KnownPlayer + # data. Finally, rematches work with the information in the + # current game's addressing. + parser.add_argument('--invite-pcts', dest = 'INVITE_PCTS', default = '33:33:33', + help='Odds of inviting each of 3 ways: {}'.format(':'.join(g_INVITE_HOWS))) parser.add_argument('--timer-seconds', dest='TIMER_SECS', default=10, type=int, help='Enable game timer with game this many seconds long') parser.add_argument('--min-app-life', dest='MIN_APP_LIFE', default=15, type=int, help='Minimum number of seconds app will run after each launch') - parser.add_argument('--with-sms', dest = 'WITH_SMS', action = 'store_true') - parser.add_argument('--without-sms', dest = 'WITH_SMS', default = False, action = 'store_false') + parser.add_argument('--with-sms', dest = 'WITH_SMS', action = 'store_true', default=False) + parser.add_argument('--without-sms', dest = 'WITH_SMS', default=False, action='store_false') # parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int) parser.add_argument('--with-mqtt', dest = 'WITH_MQTT', default = True, action = 'store_true')