From 13cc6c79f10a574c122c1b1a3a3bdecda7baa7e4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 7 Jul 2018 22:56:32 -0700 Subject: [PATCH] add save/restore of partial messages Handles case where the app receives only a subset of the SMS messages into which a larger game-level message has been broken. Now when it restarts and the remaining parts come in the whole can be reassembled. --- xwords4/common/comtypes.h | 2 + xwords4/common/dutil.h | 4 +- xwords4/common/smsproto.c | 120 ++++++++++++++++++++++++++++++++++++-- xwords4/linux/gamesdb.c | 17 ++++-- xwords4/linux/gamesdb.h | 16 ++--- xwords4/linux/gtkmain.c | 16 +++-- xwords4/linux/lindutil.c | 32 +++++++--- 7 files changed, 170 insertions(+), 37 deletions(-) diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 31bf09ef1..2e916c7c4 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -208,6 +208,8 @@ typedef enum { BONUS_LAST } XWBonusType; +#define PERSIST_KEY(str) __FILE__ ":" str + /* I need a way to communiate prefs to common/ code. For now, though, I'll * leave storage of these values up to the platforms. First, because I don't * want to deal with versioning in the common code. Second, becuase they diff --git a/xwords4/common/dutil.h b/xwords4/common/dutil.h index ae7d4aa3d..a22d35384 100644 --- a/xwords4/common/dutil.h +++ b/xwords4/common/dutil.h @@ -72,9 +72,9 @@ struct XW_DUtilCtxt { #define dutil_getUserQuantityString( duc, c, q ) \ (duc)->vtable.m_dutil_getUserQuantityString((duc),(c),(q)) -#define dutil_stor(duc, k, v) \ +#define dutil_store(duc, k, v) \ (duc)->vtable.m_dutil_store((duc), (k), (v)); -#define dutil_load(uc, k, s) \ +#define dutil_load(duc, k, s) \ (duc)->vtable.m_dutil_load((duc), (k), (s)); #ifdef XWFEATURE_SMS diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c index 01b885d69..83e9a89b8 100644 --- a/xwords4/common/smsproto.c +++ b/xwords4/common/smsproto.c @@ -23,6 +23,7 @@ #include "util.h" #include "smsproto.h" #include "comtypes.h" +#include "strutils.h" # define MAX_WAIT 5 // # define MAX_MSG_LEN 50 /* for testing */ @@ -65,7 +66,7 @@ struct SMSProto { XW_DUtilCtxt* dutil; pthread_t creator; XP_U16 nNextID; - + int lastStoredSize; XP_U16 nToPhones; ToPhoneRec* toPhoneRecs; @@ -75,6 +76,8 @@ struct SMSProto { MPSLOT; }; +#define KEY_PARTIALS PERSIST_KEY("partials") + static int nextMsgID( SMSProto* state ); static SMSMsgArray* toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld ); static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone, @@ -85,6 +88,8 @@ static void addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx, int count, const XP_U8* data, XP_U16 len ); static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone, int msgID ); +static void savePartials( SMSProto* state ); +static void restorePartials( SMSProto* state ); static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex ); static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex ); @@ -104,6 +109,9 @@ smsproto_init( MPFORMAL XW_DUtilCtxt* dutil ) state->dutil = dutil; // checkThread( state ); <-- Android's calling this on background thread now MPASSIGN( state->mpool, mpool ); + + restorePartials( state ); + return state; } @@ -220,6 +228,7 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, /* __func__, len, fromPhone, proto, msgID, indx, count ); */ addMessage( state, fromPhone, msgID, indx, count, data + offset, len - offset ); result = completeMsgs( state, result, fromPhone, msgID ); + savePartials( state ); } break; case SMS_PROTO_VERSION_COMBO: @@ -397,16 +406,20 @@ addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx, int count, const XP_U8* data, XP_U16 len ) { XP_LOGF( "phone=%s, msgID=%d, %d/%d", fromPhone, msgID, indx, count ); - int fromPhoneIndex, msgIDIndex; /* ignored */ + XP_ASSERT( 0 < len ); + int ignore; MsgIDRec* msgIDRec = getMsgIDRec( state, fromPhone, msgID, XP_TRUE, - &fromPhoneIndex, &msgIDIndex ); + &ignore, &ignore ); /* if it's new, fill in missing fields */ if ( msgIDRec->count == 0 ) { msgIDRec->count = count; /* in case it's new */ msgIDRec->parts = XP_CALLOC( state->mpool, count * sizeof(*msgIDRec->parts)); + } else { + XP_ASSERT( count == msgIDRec->count ); } - XP_ASSERT( msgIDRec->parts[indx].len == 0 ); + XP_ASSERT( msgIDRec->parts[indx].len == 0 + || msgIDRec->parts[indx].len == len ); /* replace with same ok */ msgIDRec->parts[indx].len = len; msgIDRec->parts[indx].data = XP_MALLOC( state->mpool, len ); XP_MEMCPY( msgIDRec->parts[indx].data, data, len ); @@ -460,6 +473,84 @@ freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex } } +static void +savePartials( SMSProto* state ) +{ + checkThread( state ); + + XWStreamCtxt* stream + = mem_stream_make_raw( MPPARM(state->mpool) + dutil_getVTManager(state->dutil) ); + stream_putU8( stream, 0 ); /* version */ + + stream_putU8( stream, state->nFromPhones ); + for ( int ii = 0; ii < state->nFromPhones; ++ii ) { + const FromPhoneRec* rec = &state->fromPhoneRecs[ii]; + stringToStream( stream, rec->phone ); + stream_putU8( stream, rec->nMsgIDs ); + for ( int jj = 0; jj < rec->nMsgIDs; ++jj ) { + MsgIDRec* mir = &rec->msgIDRecs[jj]; + stream_putU16( stream, mir->msgID ); + stream_putU8( stream, mir->count ); + + /* There's an array here. It may be sparse. Save a len of 0 */ + for ( int kk = 0; kk < mir->count; ++kk ) { + int len = mir->parts[kk].len; + stream_putU8( stream, len ); + stream_putBytes( stream, mir->parts[kk].data, len ); + } + } + } + + XP_U16 newSize = stream_getSize( stream ); + if ( state->lastStoredSize == 2 && newSize == 2 ) { + XP_LOGF( "%s(): not storing empty again", __func__ ); + } else { + dutil_store( state->dutil, KEY_PARTIALS, stream ); + state->lastStoredSize = newSize; + } + + stream_destroy( stream ); + + LOG_RETURN_VOID(); +} /* savePartials */ + +static void +restorePartials( SMSProto* state ) +{ + // LOG_FUNC(); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool) + dutil_getVTManager(state->dutil) ); + dutil_load( state->dutil, KEY_PARTIALS, stream ); + if ( stream_getSize( stream ) >= 1 ) { + XP_ASSERT( 0 == stream_getU8( stream ) ); + int nFromPhones = stream_getU8( stream ); + for ( int ii = 0; ii < nFromPhones; ++ii ) { + XP_UCHAR phone[32]; + (void)stringFromStreamHere( stream, phone, VSIZE(phone) ); + int nMsgIDs = stream_getU8( stream ); + XP_LOGF( "%s(): got %d message records for phone %s", __func__, + nMsgIDs, phone ); + for ( int jj = 0; jj < nMsgIDs; ++jj ) { + XP_U16 msgID = stream_getU16( stream ); + int count = stream_getU8( stream ); + XP_LOGF( "%s(): got %d records for msgID %d", __func__, count, msgID ); + for ( int kk = 0; kk < count; ++kk ) { + int len = stream_getU8( stream ); + if ( 0 < len ) { + XP_U8 buf[len]; + stream_getBytes( stream, buf, len ); + addMessage( state, phone, msgID, kk, count, buf, len ); + } + } + } + } + } + stream_destroy( stream ); + + // LOG_RETURN_VOID(); +} + static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone, int msgID ) @@ -576,7 +667,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld ) } return result; -} +} /* toMsgs */ static int nextMsgID( SMSProto* state ) @@ -716,6 +807,25 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil ) XP_ASSERT( !out ); smsproto_freeMsgArray( state, arr ); + + /* now a message that's unpacked across multiple sessions to test store/load */ + XP_LOGF( "%s(): testing store/restore", __func__ ); + arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs ); + for ( int ii = 0; ii < arr->nMsgs; ++ii ) { + SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data, + arr->msgs[ii].len ); + if ( !!out ) { + XP_ASSERT( out->nMsgs == 1); + XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii ); + XP_ASSERT( out->msgs[0].len == 200 ); + XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) ); + smsproto_freeMsgArray( state, out ); + break; + } + smsproto_free( state ); /* give it a chance to store state */ + state = smsproto_init( mpool, dutil ); + } + smsproto_free( state ); LOG_RETURN_VOID(); } diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index c1eaeb002..a2b53e204 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -497,6 +497,7 @@ loadInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) { + XP_ASSERT( !!pDb ); char query[256]; snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); sqlite3_stmt* ppStmt; @@ -511,10 +512,10 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) void db_store( sqlite3* pDb, const gchar* key, const gchar* value ) { - char buf[256]; - snprintf( buf, sizeof(buf), - "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", - key, value ); + XP_ASSERT( !!pDb ); + gchar* buf = + g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", + key, value ); sqlite3_stmt *ppStmt; int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); assertPrintResult( pDb, result, SQLITE_OK ); @@ -522,11 +523,13 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value ) assertPrintResult( pDb, result, SQLITE_DONE ); XP_USE( result ); sqlite3_finalize( ppStmt ); + g_free( buf ); } FetchResult db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ) { + XP_ASSERT( !!pDb ); FetchResult result = NOT_THERE; char query[256]; snprintf( query, sizeof(query), @@ -552,10 +555,11 @@ db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ) } XP_Bool -db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ) +db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) { + XP_ASSERT( !!pDb ); int tmp = buflen; - FetchResult result = db_fetch( dbp, key, buf, &tmp ); + FetchResult result = db_fetch( pDb, key, buf, &tmp ); XP_ASSERT( result != BUFFER_TOO_SMALL ); return SUCCESS == result; } @@ -563,6 +567,7 @@ db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ) void db_remove( sqlite3* pDb, const gchar* key ) { + XP_ASSERT( !!pDb ); char query[256]; snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); sqlite3_stmt *ppStmt; diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index f6458707a..7ba93d5a3 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -47,7 +47,7 @@ typedef struct _GameInfo { } GameInfo; sqlite3* openGamesDB( const char* dbName ); -void closeGamesDB( sqlite3* dbp ); +void closeGamesDB( sqlite3* pDb ); void writeToDB( XWStreamCtxt* stream, void* closure ); sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb ); @@ -55,15 +55,15 @@ sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb ); void summarize( CommonGlobals* cGlobals ); /* Return GSList whose data is (ptrs to) rowids */ -GSList* listGames( sqlite3* dbp ); +GSList* listGames( sqlite3* pDb ); /* free list and data allocated by above */ void freeGamesList( GSList* games ); /* Mapping of relayID -> rowid */ GHashTable* getRelayIDsToRowsMap( sqlite3* pDb ); -XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib ); -void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids, +XP_Bool getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ); +void getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids, int* nRowIDs ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, @@ -78,12 +78,12 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); #define KEY_SMSPORT "SMSPORT" #define KEY_WIN_LOC "WIN_LOC" -void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); -void db_remove( sqlite3* dbp, const gchar* key ); +void db_store( sqlite3* pDb, const gchar* key, const gchar* value ); +void db_remove( sqlite3* pDb, const gchar* key ); typedef enum { NOT_THERE, BUFFER_TOO_SMALL, SUCCESS } FetchResult; -FetchResult db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint* buflen ); -XP_Bool db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); +FetchResult db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ); +XP_Bool db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ); #endif diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index ec89decca..453931ba2 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -861,14 +861,6 @@ gtkmain( LaunchParams* params ) g_globals_for_signal = &apg; - if ( params->runSMSTest ) { - CommonGlobals cGlobals = {.params = params }; - setupUtil( &cGlobals ); - smsproto_runTests( params->mpool, cGlobals.params->dutil ); - linux_util_vt_destroy( cGlobals.util ); - free( cGlobals.util ); - } - struct sigaction act = { .sa_handler = handle_sigintterm }; sigaction( SIGINT, &act, NULL ); sigaction( SIGTERM, &act, NULL ); @@ -928,7 +920,13 @@ gtkmain( LaunchParams* params ) XP_LOGF( "not activating SMS: I don't have a phone" ); } - + if ( params->runSMSTest ) { + CommonGlobals cGlobals = {.params = params }; + setupUtil( &cGlobals ); + smsproto_runTests( params->mpool, cGlobals.params->dutil ); + linux_util_vt_destroy( cGlobals.util ); + free( cGlobals.util ); + } #endif makeGamesWindow( &apg ); } else if ( !!params->dbFileName ) { diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c index 6b0cf5fc3..7fd321ecf 100644 --- a/xwords4/linux/lindutil.c +++ b/xwords4/linux/lindutil.c @@ -180,19 +180,37 @@ linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code, } static void -linux_dutil_store( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* data ) +linux_dutil_store( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream ) { - XP_LOGF( "%s(key=%s)", __func__, key ); - XP_USE( duc ); - XP_USE( data ); + LaunchParams* params = (LaunchParams*)duc->closure; + sqlite3* pDb = params->pDb; + + gchar* b64 = g_base64_encode( stream_getPtr( stream ), stream_getSize( stream ) ); + db_store( pDb, key, b64 ); + g_free( b64 ); } static void linux_dutil_load( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* inOut ) { - XP_LOGF( "%s(key=%s)", __func__, key ); - XP_USE( duc ); - XP_USE( inOut ); + LaunchParams* params = (LaunchParams*)duc->closure; + sqlite3* pDb = params->pDb; + + gint buflen = 0; + FetchResult res = db_fetch( pDb, key, NULL, &buflen ); + if ( res == BUFFER_TOO_SMALL ) { + gchar buf[buflen]; + res = db_fetch( pDb, key, buf, &buflen ); + XP_ASSERT( res == SUCCESS ); + + gsize out_len; + guchar* txt = g_base64_decode( (const gchar*)buf, &out_len ); + + stream_putBytes( inOut, txt, out_len ); + g_free( txt ); + } + + XP_LOGF( "%s(key=%s) => len: %d", __func__, key, stream_getSize(inOut) ); } #ifdef XWFEATURE_SMS