xwords/xwords4/common/smsproto.c
Eric House 4a57b76817 cleanup: pass xwe with closeProc rather than to destroy
It's only needed when there's a close proc, and that's rare.
2023-02-03 22:00:47 -08:00

1089 lines
38 KiB
C

/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2018 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 <unistd.h>
#include <pthread.h>
#include "util.h"
#include "smsproto.h"
#include "comtypes.h"
#include "strutils.h"
#define MAX_WAIT 3
// # define MAX_MSG_LEN 50 /* for testing */
#ifndef MAX_LEN_BINARY
# define MAX_LEN_BINARY 115
#endif
/* PENDING: Might want to make SEND_NOW_SIZE smaller; might as well send now
if even the smallest new message is likely to put us over. */
#define SEND_NOW_SIZE MAX_LEN_BINARY
/* To match the SMSService format */
#define SMS_PROTO_VERSION_JAVA 1
#define SMS_PROTO_VERSION_COMBO 2
#define PARTIALS_FORMAT 0
typedef struct _MsgRec {
XP_U32 createSeconds;
SMSMsgNet msgNet;
} MsgRec;
typedef struct _ToPhoneRec {
XP_UCHAR phone[32];
XP_U32 createSeconds;
XP_U16 nMsgs;
XP_U16 totalSize;
MsgRec** msgs;
} ToPhoneRec;
typedef struct _MsgIDRec {
int msgID;
int count;
struct {
XP_U16 len;
XP_U8* data;
}* parts;
} MsgIDRec;
typedef struct _FromPhoneRec {
XP_UCHAR phone[32];
int nMsgIDs;
MsgIDRec* msgIDRecs;
} FromPhoneRec;
struct SMSProto {
XW_DUtilCtxt* dutil;
pthread_t creator;
pthread_mutex_t mutex;
XP_U16 nNextID;
int lastStoredSize;
XP_U16 nToPhones;
ToPhoneRec* toPhoneRecs;
int nFromPhones;
FromPhoneRec* fromPhoneRecs;
#ifdef DEBUG
pthread_t starter;
int nestCount;
#endif
MPSLOT;
};
static int nextMsgID( SMSProto* state, XWEnv xwe );
static XWStreamCtxt* mkStream( SMSProto* state );
static void destroyStream( XWStreamCtxt* stream );
static SMSMsgArray* toNetMsgs( SMSProto* state, XWEnv xwe, ToPhoneRec* rec,
XP_Bool forceOld );
static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone,
XP_Bool create );
static void addToOutRec( SMSProto* state, ToPhoneRec* rec, SMS_CMD cmd,
XP_U16 port, XP_U32 gameID, const XP_U8* buf,
XP_U16 buflen, XP_U32 nowSeconds );
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, XP_U16 wantPort,
int msgID );
static void savePartials( SMSProto* state, XWEnv xwe );
static void restorePartials( SMSProto* state, XWEnv xwe );
static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex );
static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
int msgIDIndex );
static void freeForPhone( SMSProto* state, const XP_UCHAR* phone );
static void freeMsg( SMSProto* state, MsgRec** msg );
static void freeRec( SMSProto* state, ToPhoneRec* rec );
#if defined DEBUG && defined COMMS_CHECKSUM
static void logResult( const SMSProto* state, XWEnv xwe,
const SMSMsgArray* result, const char* caller );
#else
# define logResult( state, xwe, result, caller )
#endif
SMSProto*
smsproto_init( MPFORMAL XWEnv xwe, XW_DUtilCtxt* dutil )
{
SMSProto* state = (SMSProto*)XP_CALLOC( mpool, sizeof(*state) );
pthread_mutex_init( &state->mutex, NULL );
state->dutil = dutil;
MPASSIGN( state->mpool, mpool );
XP_U32 siz = sizeof(state->nNextID);
const XP_UCHAR* keys[] = { KEY_NEXTID, NULL, };
dutil_loadPtr( state->dutil, xwe, keys, &state->nNextID, &siz );
XP_LOGF( "%s(): loaded nextMsgID: %d", __func__, state->nNextID );
restorePartials( state, xwe );
return state;
}
void
smsproto_free( SMSProto* state )
{
if ( NULL != state ) {
XP_ASSERT( state->creator == 0 || state->creator == pthread_self() );
for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) {
freeRec( state, &state->toPhoneRecs[ii] );
}
XP_FREEP( state->mpool, &state->toPhoneRecs );
if ( 0 < state->nFromPhones ) {
XP_LOGF( "%s(): freeing undelivered partial messages", __func__ );
}
while (0 < state->nFromPhones) {
FromPhoneRec* ffr = &state->fromPhoneRecs[0];
while ( 0 < ffr->nMsgIDs ) {
freeMsgIDRec( state, &ffr->msgIDRecs[0], 0, 0 );
}
}
XP_ASSERT( !state->fromPhoneRecs ); /* above nulls this once empty */
pthread_mutex_destroy( &state->mutex );
XP_FREEP( state->mpool, &state );
}
}
static void
headerToStream( XWStreamCtxt* stream, SMS_CMD cmd, XP_U16 port, XP_U32 gameID )
{
// XP_LOGF( "%s(cmd: %d; gameID: %d)", __func__, cmd, gameID );
stream_putU8( stream, SMS_PROTO_VERSION_JAVA );
stream_putU16( stream, port );
stream_putU8( stream, cmd );
switch ( cmd ) {
case NONE:
XP_ASSERT(0);
break;
case INVITE:
break;
default:
stream_putU32( stream, gameID );
}
}
static XP_Bool
headerFromStream( XWStreamCtxt* stream, SMS_CMD* cmd, XP_U16* port, XP_U32* gameID )
{
XP_Bool success = XP_FALSE;
XP_U8 tmp;
if ( stream_gotU8( stream, &tmp )
&& tmp == SMS_PROTO_VERSION_JAVA
&& stream_gotU16( stream, port )
&& stream_gotU8( stream, &tmp ) ) {
*cmd = tmp;
switch( *cmd ) {
case INVITE:
success = XP_TRUE;
break;
default:
success = stream_gotU32( stream, gameID );
break;
}
}
// XP_LOGF( "%s() => cmd: %d; gameID: %d", __func__, *cmd, *gameID );
return success;
}
/* Maintain a list of pending messages per phone number. When called and it's
* been at least some amount of time since we last added something, or at
* least some longer time since the oldest message was added, return an array
* of messages ready to send via the device's raw SMS (i.e. respecting its
* size limits.)
* Pass in the current time, as that's easier than keeping an instance of
* UtilCtxt around.
*/
SMSMsgArray*
smsproto_prepOutbound( SMSProto* state, XWEnv xwe, SMS_CMD cmd, XP_U32 gameID,
const void* buf, XP_U16 buflen, const XP_UCHAR* toPhone,
int toPort, XP_Bool forceOld, XP_U16* waitSecsP )
{
XP_USE( toPort );
SMSMsgArray* result = NULL;
pthread_mutex_lock( &state->mutex );
#if defined DEBUG && defined COMMS_CHECKSUM
XP_UCHAR* checksum = dutil_md5sum( state->dutil, xwe, buf, buflen );
XP_LOGFF( "(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", cmd,
gameID, buflen, checksum, toPhone );
XP_FREEP( state->mpool, &checksum );
#endif
ToPhoneRec* rec = getForPhone( state, toPhone, cmd != NONE );
/* First, add the new message (if present) to the array */
XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil, xwe );
if ( cmd != NONE ) {
addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds );
}
/* rec will be non-null if there's something in it */
XP_Bool doSend = XP_FALSE;
if ( rec != NULL ) {
doSend = forceOld
|| rec->totalSize > SEND_NOW_SIZE
|| MAX_WAIT <= nowSeconds - rec->createSeconds;
/* other criteria? */
}
if ( doSend ) {
result = toNetMsgs( state, xwe, rec, forceOld );
freeForPhone( state, toPhone );
}
XP_U16 waitSecs = 0;
if ( !result && !!rec && (rec->nMsgs > 0) ) {
waitSecs = MAX_WAIT - (nowSeconds - rec->createSeconds);
}
*waitSecsP = waitSecs;
XP_LOGF( "%s() => %p (count=%d, *waitSecs=%d)", __func__, result,
!!result ? result->nMsgs : 0, *waitSecsP );
pthread_mutex_unlock( &state->mutex );
logResult( state, xwe, result, __func__ );
return result;
} /* smsproto_prepOutbound */
static SMSMsgArray*
appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg )
{
if ( NULL == arr ) {
arr = XP_CALLOC( state->mpool, sizeof(*arr) );
arr->format = FORMAT_LOC;
} else {
XP_ASSERT( arr->format == FORMAT_LOC );
}
arr->u.msgsLoc = XP_REALLOC( state->mpool, arr->u.msgsLoc,
(arr->nMsgs + 1) * sizeof(*arr->u.msgsLoc) );
arr->u.msgsLoc[arr->nMsgs++] = *msg;
return arr;
}
static SMSMsgArray*
appendNetMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgNet* msg )
{
if ( NULL == arr ) {
arr = XP_CALLOC( state->mpool, sizeof(*arr) );
arr->format = FORMAT_NET;
} else {
XP_ASSERT( arr->format == FORMAT_NET );
}
arr->u.msgsNet = XP_REALLOC( state->mpool, arr->u.msgsNet,
(arr->nMsgs + 1) * sizeof(*arr->u.msgsNet) );
arr->u.msgsNet[arr->nMsgs++] = *msg;
return arr;
}
SMSMsgArray*
smsproto_prepInbound( SMSProto* state, XWEnv xwe, const XP_UCHAR* fromPhone,
XP_U16 wantPort, const XP_U8* data, XP_U16 len )
{
XP_LOGFF( "len=%d, fromPhone=%s", len, fromPhone );
#if defined DEBUG && defined COMMS_CHECKSUM
XP_UCHAR* checksum = dutil_md5sum( state->dutil, xwe, data, len );
XP_LOGFF( "(fromPhone=%s, len=%d); sum=%s", fromPhone, len, checksum );
XP_FREEP( state->mpool, &checksum );
#endif
SMSMsgArray* result = NULL;
pthread_mutex_lock( &state->mutex );
XWStreamCtxt* stream = mkStream( state );
stream_putBytes( stream, data, len );
XP_U8 proto;
if ( stream_gotU8( stream, &proto ) ) {
switch ( proto ) {
case SMS_PROTO_VERSION_JAVA: {
XP_U8 msgID, indx, count;
if ( stream_gotU8( stream, &msgID )
&& stream_gotU8( stream, &indx )
&& stream_gotU8( stream, &count )
&& indx < count ) {
XP_U16 len = stream_getSize( stream );
XP_U8 buf[len];
stream_getBytes( stream, buf, len );
addMessage( state, fromPhone, msgID, indx, count, buf, len );
result = completeMsgs( state, result, fromPhone, wantPort, msgID );
savePartials( state, xwe );
}
}
break;
case SMS_PROTO_VERSION_COMBO: {
XP_U8 oneLen, msgID;
while ( stream_gotU8( stream, &oneLen )
&& stream_gotU8( stream, &msgID ) ) {
XP_U8 tmp[oneLen];
stream_getBytes( stream, tmp, oneLen );
XWStreamCtxt* msgStream = mkStream( state );
stream_putBytes( msgStream, tmp, oneLen );
XP_U32 gameID;
XP_U16 port;
SMS_CMD cmd;
if ( headerFromStream( msgStream, &cmd, &port, &gameID ) ) {
XP_U16 msgLen = stream_getSize( msgStream );
XP_U8 buf[msgLen];
if ( stream_gotBytes( msgStream, buf, msgLen ) ) {
if ( port == wantPort ) {
SMSMsgLoc msg = { .len = msgLen,
.cmd = cmd,
.gameID = gameID,
.data = XP_MALLOC( state->mpool, msgLen ),
};
XP_MEMCPY( msg.data, buf, msgLen );
result = appendLocMsg( state, result, &msg );
} else {
XP_LOGF( "%s(): expected port %d, got %d", __func__,
wantPort, port );
}
}
}
destroyStream( msgStream );
}
}
break;
default:
/* Don't assert! happens all the time */
XP_LOGF( "%s(): unexpected proto %d", __func__, proto );
break;
}
}
destroyStream( stream );
XP_LOGFF( "=> %p (len=%d)", result, (!!result) ? result->nMsgs : 0 );
logResult( state, xwe, result, __func__ );
pthread_mutex_unlock( &state->mutex );
return result;
}
void
smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
{
pthread_mutex_lock( &state->mutex );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
XP_U8** ptr = arr->format == FORMAT_LOC
? &arr->u.msgsLoc[ii].data : &arr->u.msgsNet[ii].data;
XP_FREEP( state->mpool, ptr );
}
void** ptr;
switch( arr->format ) {
case FORMAT_LOC:
ptr = (void**)&arr->u.msgsLoc;
break;
case FORMAT_NET:
ptr = (void**)&arr->u.msgsNet;
break;
default:
XP_ASSERT(0);
ptr = NULL;
}
XP_FREEP( state->mpool, ptr );
XP_FREEP( state->mpool, &arr );
pthread_mutex_unlock( &state->mutex );
}
#if defined DEBUG && defined COMMS_CHECKSUM
static void
logResult( const SMSProto* state, XWEnv xwe, const SMSMsgArray* result,
const char* caller )
{
if ( !!result ) {
for ( int ii = 0; ii < result->nMsgs; ++ii ) {
XP_U8* data;
XP_U16 len = 0;
switch ( result->format ) {
case FORMAT_LOC: {
SMSMsgLoc* msgsLoc = &result->u.msgsLoc[ii];
data = msgsLoc->data;
len = msgsLoc->len;
}
break;
case FORMAT_NET: {
SMSMsgNet* msgsNet = &result->u.msgsNet[ii];
data = msgsNet->data;
len = msgsNet->len;
}
break;
default:
XP_ASSERT(0);
}
if ( 0 == len ) {
XP_LOGFF( "%s() => datum[%d] len: 0", caller, ii );
} else {
XP_UCHAR* checksum = dutil_md5sum( state->dutil, xwe, data, len );
XP_LOGFF( "%s() => datum[%d] sum: %s, len: %d", caller, ii, checksum, len );
XP_FREEP( state->mpool, &checksum );
}
}
}
}
#endif
static void
freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp )
{
XP_FREEP( state->mpool, &(*msgp)->msgNet.data );
XP_FREEP( state->mpool, msgp );
}
static void
freeRec( SMSProto* state, ToPhoneRec* rec )
{
for ( XP_U16 jj = 0; jj < rec->nMsgs; ++jj ) {
freeMsg( state, &rec->msgs[jj] );
}
XP_FREEP( state->mpool, &rec->msgs );
}
static ToPhoneRec*
getForPhone( SMSProto* state, const XP_UCHAR* phone, XP_Bool create )
{
ToPhoneRec* rec = NULL;
for ( XP_U16 ii = 0; !rec && ii < state->nToPhones; ++ii ) {
if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) {
rec = &state->toPhoneRecs[ii];
}
}
if ( !rec && create ) {
state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs,
(1 + state->nToPhones) * sizeof(*state->toPhoneRecs) );
rec = &state->toPhoneRecs[state->nToPhones++];
XP_MEMSET( rec, 0, sizeof(*rec) );
XP_STRCAT( rec->phone, phone );
}
return rec;
}
static void
freeForPhone( SMSProto* state, const XP_UCHAR* phone )
{
for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) {
if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) {
freeRec( state, &state->toPhoneRecs[ii] );
XP_U16 nAbove = state->nToPhones - ii - 1;
XP_ASSERT( nAbove >= 0 );
if ( nAbove > 0 ) {
XP_MEMMOVE( &state->toPhoneRecs[ii], &state->toPhoneRecs[ii+1],
nAbove * sizeof(*state->toPhoneRecs) );
}
--state->nToPhones;
if ( 0 == state->nToPhones ) {
XP_FREEP( state->mpool, &state->toPhoneRecs );
} else {
state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs,
state->nToPhones * sizeof(*state->toPhoneRecs) );
}
break;
}
}
}
static void
addToOutRec( SMSProto* state, ToPhoneRec* rec, SMS_CMD cmd,
XP_U16 port, XP_U32 gameID, const XP_U8* buf, XP_U16 buflen,
XP_U32 nowSeconds )
{
XWStreamCtxt* stream = mkStream( state );
headerToStream( stream, cmd, port, gameID );
stream_putBytes( stream, buf, buflen );
MsgRec* mRec = XP_CALLOC( state->mpool, sizeof(*rec) );
XP_U16 len = stream_getSize( stream );
mRec->msgNet.len = len;
mRec->msgNet.data = XP_MALLOC( state->mpool, len );
XP_MEMCPY( mRec->msgNet.data, stream_getPtr(stream), len );
destroyStream( stream );
mRec->createSeconds = nowSeconds;
rec->msgs = XP_REALLOC( state->mpool, rec->msgs, (1 + rec->nMsgs) * sizeof(*rec->msgs) );
rec->msgs[rec->nMsgs++] = mRec;
rec->totalSize += len;
XP_LOGFF( "added msg to %s of len %d; total now %d", rec->phone, len,
rec->totalSize );
if ( rec->nMsgs == 1 ) {
rec->createSeconds = nowSeconds;
}
}
static MsgIDRec*
getMsgIDRec( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
XP_Bool addMissing, int* fromPhoneIndex, int* msgIDIndex )
{
MsgIDRec* result = NULL;
FromPhoneRec* fromPhoneRec = NULL;
for ( int ii = 0; ii < state->nFromPhones; ++ii ) {
if ( 0 == XP_STRCMP( state->fromPhoneRecs[ii].phone, fromPhone ) ) {
fromPhoneRec = &state->fromPhoneRecs[ii];
*fromPhoneIndex = ii;
break;
}
}
// create and add if not found
if ( NULL == fromPhoneRec && addMissing ) {
state->fromPhoneRecs =
XP_REALLOC( state->mpool, state->fromPhoneRecs,
(state->nFromPhones + 1) * sizeof(*state->fromPhoneRecs) );
*fromPhoneIndex = state->nFromPhones;
fromPhoneRec = &state->fromPhoneRecs[state->nFromPhones++];
XP_MEMSET( fromPhoneRec, 0, sizeof(*fromPhoneRec) );
XP_STRCAT( fromPhoneRec->phone, fromPhone );
}
// Now find msgID record
if ( NULL != fromPhoneRec ) {
for ( int ii = 0; ii < fromPhoneRec->nMsgIDs; ++ii ) {
if ( fromPhoneRec->msgIDRecs[ii].msgID == msgID ) {
result = &fromPhoneRec->msgIDRecs[ii];
*msgIDIndex = ii;
break;
}
}
// create and add if not found
if ( NULL == result && addMissing ) {
fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs,
(fromPhoneRec->nMsgIDs + 1)
* sizeof(*fromPhoneRec->msgIDRecs) );
MsgIDRec newRec = { .msgID = msgID };
*msgIDIndex = fromPhoneRec->nMsgIDs;
result = &fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs];
fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs++] = newRec;
}
}
return result;
}
/* Messages that are split gather here until complete
*/
static void
addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx,
int count, const XP_U8* data, XP_U16 len )
{
// XP_LOGFF( "phone=%s, msgID=%d, %d/%d", fromPhone, msgID, indx, count );
XP_ASSERT( 0 < len );
MsgIDRec* msgIDRec;
for ( ; ; ) {
int fromPhoneIndex;
int msgIDIndex;
msgIDRec = getMsgIDRec( state, fromPhone, msgID, XP_TRUE,
&fromPhoneIndex, &msgIDIndex );
/* sanity check... */
if ( msgIDRec->count == 0 || msgIDRec->count == count ) {
break;
}
freeMsgIDRec( state, msgIDRec, fromPhoneIndex, msgIDIndex );
}
/* 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));
}
XP_ASSERT( msgIDRec->parts[indx].len == 0
|| msgIDRec->parts[indx].len == len ); /* replace with same ok */
msgIDRec->parts[indx].len = len;
XP_FREEP( state->mpool, &msgIDRec->parts[indx].data ); /* in case non-null (replacement) */
msgIDRec->parts[indx].data = XP_MALLOC( state->mpool, len );
XP_MEMCPY( msgIDRec->parts[indx].data, data, len );
}
static void
rmFromPhoneRec( SMSProto* state, int fromPhoneIndex )
{
FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex];
XP_ASSERT( fromPhoneRec->nMsgIDs == 0 );
XP_FREEP( state->mpool, &fromPhoneRec->msgIDRecs );
if ( --state->nFromPhones == 0 ) {
XP_FREEP( state->mpool, &state->fromPhoneRecs );
} else {
XP_U16 nAbove = state->nFromPhones - fromPhoneIndex;
XP_ASSERT( nAbove >= 0 );
if ( nAbove > 0 ) {
XP_MEMMOVE( &state->fromPhoneRecs[fromPhoneIndex], &state->fromPhoneRecs[fromPhoneIndex+1],
nAbove * sizeof(*state->fromPhoneRecs) );
}
state->fromPhoneRecs = XP_REALLOC( state->mpool, state->fromPhoneRecs,
state->nFromPhones * sizeof(*state->fromPhoneRecs));
}
}
static void
freeMsgIDRec( SMSProto* state, MsgIDRec* XP_UNUSED_DBG(rec), int fromPhoneIndex, int msgIDIndex )
{
FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex];
MsgIDRec* msgIDRec = &fromPhoneRec->msgIDRecs[msgIDIndex];
XP_ASSERT( msgIDRec == rec );
for ( int ii = 0; ii < msgIDRec->count; ++ii ) {
XP_FREEP( state->mpool, &msgIDRec->parts[ii].data );
}
XP_FREEP( state->mpool, &msgIDRec->parts );
if ( --fromPhoneRec->nMsgIDs > 0 ) {
XP_U16 nAbove = fromPhoneRec->nMsgIDs - msgIDIndex;
XP_ASSERT( nAbove >= 0 );
if ( nAbove > 0 ) {
XP_MEMMOVE( &fromPhoneRec->msgIDRecs[msgIDIndex], &fromPhoneRec->msgIDRecs[msgIDIndex+1],
nAbove * sizeof(*fromPhoneRec->msgIDRecs) );
}
fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs,
fromPhoneRec->nMsgIDs
* sizeof(*fromPhoneRec->msgIDRecs));
} else {
rmFromPhoneRec( state, fromPhoneIndex );
}
}
static void
savePartials( SMSProto* state, XWEnv xwe )
{
XWStreamCtxt* stream = mkStream( state );
stream_putU8( stream, PARTIALS_FORMAT );
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_LOGFF( "not storing empty again" );
} else {
const XP_UCHAR* keys[] = { KEY_PARTIALS, NULL, };
dutil_storeStream( state->dutil, xwe, keys, stream );
state->lastStoredSize = newSize;
}
destroyStream( stream );
LOG_RETURN_VOID();
} /* savePartials */
static void
restorePartials( SMSProto* state, XWEnv xwe )
{
XWStreamCtxt* stream = mkStream( state );
const XP_UCHAR* keys[] = { KEY_PARTIALS, NULL, };
dutil_loadStream( state->dutil, xwe, keys, stream );
if ( stream_getSize( stream ) >= 1
&& PARTIALS_FORMAT == 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 );
}
}
}
}
}
destroyStream( stream );
}
static SMSMsgArray*
completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
XP_U16 wantPort, int msgID )
{
int fromPhoneIndex, msgIDIndex;
MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE,
&fromPhoneIndex, &msgIDIndex);
if ( !rec ) {
XP_LOGFF( "no rec for phone %s, msgID %d", fromPhone, msgID );
XP_ASSERT( 0 );
}
int len = 0;
XP_Bool haveAll = XP_TRUE;
for ( int ii = 0; ii < rec->count; ++ii ) {
if ( rec->parts[ii].len == 0 ) {
haveAll = XP_FALSE;
break;
} else {
len += rec->parts[ii].len;
}
}
if ( haveAll ) {
XWStreamCtxt* stream = mkStream( state );
for ( int ii = 0; ii < rec->count; ++ii ) {
stream_putBytes( stream, rec->parts[ii].data, rec->parts[ii].len );
}
XP_U32 gameID;
XP_U16 port;
SMS_CMD cmd;
if ( headerFromStream( stream, &cmd, &port, &gameID ) ) {
XP_U16 len = stream_getSize( stream );
SMSMsgLoc msg = { .len = len,
.cmd = cmd,
.gameID = gameID,
.data = XP_MALLOC( state->mpool, len ),
};
if ( stream_gotBytes( stream, msg.data, len ) && port == wantPort ) {
arr = appendLocMsg( state, arr, &msg );
} else {
XP_LOGFF( "expected port %d, got %d", wantPort, port );
XP_FREEP( state->mpool, &msg.data );
}
}
destroyStream( stream );
freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex );
}
return arr;
}
static SMSMsgArray*
toNetMsgs( SMSProto* state, XWEnv xwe, ToPhoneRec* rec, XP_Bool forceOld )
{
SMSMsgArray* result = NULL;
for ( XP_U16 ii = 0; ii < rec->nMsgs; ) {
// XP_LOGF( "%s(): looking at msg %d of %d", __func__, ii, rec->nMsgs );
XP_U16 count = (rec->msgs[ii]->msgNet.len + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY;
/* First, see if this message and some number of its neighbors can be
combined */
int last = ii;
int sum = 0;
if ( count == 1 && !forceOld ) {
for ( ; last < rec->nMsgs; ++last ) {
int nextLen = rec->msgs[last]->msgNet.len;
if ( sum + nextLen > MAX_LEN_BINARY ) {
break;
}
sum += nextLen;
}
}
if ( last > ii ) {
int nMsgs = last - ii;
if ( nMsgs > 1 ) {
XP_LOGFF( "combining %d through %d (%d msgs)", ii, last - 1, nMsgs );
}
int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */
SMSMsgNet newMsg = { .len = len,
.data = XP_MALLOC( state->mpool, len )
};
int indx = 0;
newMsg.data[indx++] = SMS_PROTO_VERSION_COMBO;
for ( int jj = ii; jj < last; ++jj ) {
const SMSMsgNet* msg = &rec->msgs[jj]->msgNet;
newMsg.data[indx++] = msg->len;
newMsg.data[indx++] = nextMsgID( state, xwe );
XP_MEMCPY( &newMsg.data[indx], msg->data, msg->len ); /* bad! */
indx += msg->len;
}
result = appendNetMsg( state, result, &newMsg );
ii = last;
} else {
int msgID = nextMsgID( state, xwe );
const SMSMsgNet* msg = &rec->msgs[ii]->msgNet;
XP_U8* nextStart = msg->data;
XP_U16 lenLeft = msg->len;
for ( XP_U16 indx = 0; indx < count; ++indx ) {
XP_ASSERT( lenLeft > 0 );
XP_U16 useLen = lenLeft;
if ( useLen >= MAX_LEN_BINARY ) {
useLen = MAX_LEN_BINARY;
}
lenLeft -= useLen;
SMSMsgNet newMsg = { .len = useLen + 4,
.data = XP_MALLOC( state->mpool, useLen + 4 )
};
newMsg.data[0] = SMS_PROTO_VERSION_JAVA;
newMsg.data[1] = msgID;
newMsg.data[2] = indx;
newMsg.data[3] = count;
XP_MEMCPY( newMsg.data + 4, nextStart, useLen );
nextStart += useLen;
result = appendNetMsg( state, result, &newMsg );
}
++ii;
}
}
return result;
} /* toMsgs */
static int
nextMsgID( SMSProto* state, XWEnv xwe )
{
int result = ++state->nNextID % 0x000000FF;
const XP_UCHAR* keys[] = { KEY_NEXTID, NULL, };
dutil_storePtr( state->dutil, xwe, keys, &state->nNextID,
sizeof(state->nNextID) );
LOG_RETURNF( "%d", result );
return result;
}
static XWStreamCtxt*
mkStream( SMSProto* state )
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
return stream;
}
static void
destroyStream( XWStreamCtxt* stream )
{
stream_destroy( stream );
}
#ifdef DEBUG
void
smsproto_runTests( MPFORMAL XWEnv xwe, XW_DUtilCtxt* dutil )
{
LOG_FUNC();
SMSProto* state = smsproto_init( mpool, xwe, dutil );
const int smallSiz = 20;
const char* phones[] = {"1234", "3456", "5467", "9877"};
const char* buf = "asoidfaisdfoausdf aiousdfoiu asodfu oiuasdofi oiuaosiduf oaisudf oiasd f"
";oiaisdjfljiojaklj asdlkjalskdjf laksjd flkjasdlfkj aldsjkf lsakdjf lkjsad flkjsd fl;kj"
"asdifaoaosidfoiauosidufoaus doifuoaiusdoifu aoisudfoaisd foia sdoifuasodfu aosiud foiuas odfiu asd"
"aosdoiaosdoiisidfoiosi isoidufoisu doifuoisud oiuoi98a90iu-asjdfoiasdfij"
;
const XP_Bool forceOld = XP_TRUE;
SMSMsgArray* arrs[VSIZE(phones)];
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
arrs[ii] = NULL;
}
/* Loop until all the messages are ready. */
const XP_U32 gameID = 12344321;
const int port = 1;
for ( XP_Bool firstTime = XP_TRUE; ; firstTime = XP_FALSE) {
XP_Bool allDone = XP_TRUE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
XP_U16 waitSecs;
if ( firstTime ) {
XP_U16 len = (ii + 1) * 30;
arrs[ii] = smsproto_prepOutbound( state, xwe, DATA, gameID, buf, len, phones[ii],
port, forceOld, &waitSecs );
} else if ( NULL == arrs[ii]) {
arrs[ii] = smsproto_prepOutbound( state, xwe, DATA, gameID, NULL, 0, phones[ii],
port, forceOld, &waitSecs );
} else {
continue;
}
allDone = allDone & (waitSecs == 0 && !!arrs[ii]);
}
if ( allDone ) {
break;
} else {
(void)sleep( 2 );
}
}
for ( int indx = 0; ; ++indx ) {
XP_Bool haveOne = XP_FALSE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
if (!!arrs[ii] && indx < arrs[ii]->nMsgs) {
XP_ASSERT( arrs[ii]->format == FORMAT_NET );
haveOne = XP_TRUE;
SMSMsgArray* outArr = smsproto_prepInbound( state, xwe, phones[ii], port,
arrs[ii]->u.msgsNet[indx].data,
arrs[ii]->u.msgsNet[indx].len );
if ( !!outArr ) {
XP_ASSERT( outArr->format == FORMAT_LOC );
SMSMsgLoc* msg = &outArr->u.msgsLoc[0];
XP_ASSERT( msg->gameID == gameID );
XP_ASSERT( msg->cmd == DATA );
// XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
XP_ASSERT( outArr->nMsgs == 1 );
XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) );
smsproto_freeMsgArray( state, outArr );
smsproto_freeMsgArray( state, arrs[ii] );
arrs[ii] = NULL;
}
}
}
if (!haveOne) {
break;
}
}
/* Now let's send a bunch of small messages that should get combined */
for ( int nUsed = 0; ; ++nUsed ) {
XP_U16 waitSecs;
SMSMsgArray* sendArr = smsproto_prepOutbound( state, xwe, DATA, gameID, &buf[nUsed],
smallSiz, phones[0], port,
XP_FALSE, &waitSecs );
if ( sendArr == NULL ) {
XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz );
continue;
}
XP_ASSERT( waitSecs == 0 );
XP_ASSERT( sendArr->format == FORMAT_NET );
int totalBack = 0;
for ( int jj = 0; jj < sendArr->nMsgs; ++jj ) {
SMSMsgArray* recvArr = smsproto_prepInbound( state, xwe, phones[0], port,
sendArr->u.msgsNet[jj].data,
sendArr->u.msgsNet[jj].len );
if ( !!recvArr ) {
XP_ASSERT( recvArr->format == FORMAT_LOC );
XP_LOGF( "%s(): got %d msgs (from %d)", __func__, recvArr->nMsgs, nUsed + 1 );
for ( int kk = 0; kk < recvArr->nMsgs; ++kk ) {
SMSMsgLoc* msg = &recvArr->u.msgsLoc[kk];
// XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
XP_ASSERT( msg->gameID == gameID );
XP_ASSERT( msg->cmd == DATA );
XP_ASSERT( msg->len == smallSiz );
XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) );
++totalBack;
}
smsproto_freeMsgArray( state, recvArr );
}
}
XP_ASSERT( forceOld || totalBack == nUsed + 1 );
XP_LOGF( "%s(): %d messages checked out", __func__, totalBack );
smsproto_freeMsgArray( state, sendArr );
break;
}
/* Now let's add a too-long message and unpack only the first part. Make
sure it's cleaned up correctly */
XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( state, xwe, DATA, gameID, buf, 200, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( !!arr && arr->nMsgs > 1 );
/* add only part 1 */
SMSMsgArray* out = smsproto_prepInbound( state, xwe, "33333", port, arr->u.msgsNet[0].data,
arr->u.msgsNet[0].len );
XP_ASSERT( !out );
smsproto_freeMsgArray( state, arr );
/* Try the no-buffer messages */
XP_LOGF( "%s(): trying DEATH", __func__ );
arr = smsproto_prepOutbound( state, xwe, DEATH, gameID, NULL, 0, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( arr->format == FORMAT_NET );
out = smsproto_prepInbound( state, xwe, "33333", port,
arr->u.msgsNet[0].data,
arr->u.msgsNet[0].len );
XP_ASSERT( out->format == FORMAT_LOC );
XP_ASSERT( out->u.msgsLoc[0].cmd == DEATH );
XP_ASSERT( out->u.msgsLoc[0].gameID == gameID );
smsproto_freeMsgArray( state, arr );
smsproto_freeMsgArray( state, out );
XP_LOGF( "%s(): DEATH checked out", __func__ );
/* Test port mismatch */
arr = smsproto_prepOutbound( state, xwe, DEATH, gameID, NULL, 0, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( arr->format == FORMAT_NET );
out = smsproto_prepInbound( state, xwe, "33333", port + 1,
arr->u.msgsNet[0].data,
arr->u.msgsNet[0].len );
XP_ASSERT( out == NULL );
smsproto_freeMsgArray( state, arr );
XP_LOGF( "%s(): mismatched port test done", __func__ );
/* now a message that's unpacked across multiple sessions to test store/load */
XP_LOGF( "%s(): testing store/restore", __func__ );
arr = smsproto_prepOutbound( state, xwe, DATA, gameID, (XP_U8*)buf, 200, "33333",
port, XP_TRUE, &waitSecs );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsgArray* out = smsproto_prepInbound( state, xwe, "33333", port,
arr->u.msgsNet[ii].data,
arr->u.msgsNet[ii].len );
if ( !!out ) {
XP_ASSERT( out->nMsgs == 1);
XP_ASSERT( out->format == FORMAT_LOC );
XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii );
XP_ASSERT( out->u.msgsLoc[0].len == 200 );
XP_ASSERT( 0 == memcmp( out->u.msgsLoc[0].data, buf, 200 ) );
smsproto_freeMsgArray( state, out );
break;
}
smsproto_free( state ); /* give it a chance to store state */
state = smsproto_init( mpool, xwe, dutil );
}
/* Really bad to pass a different state than was created with, but now
since only mpool is used and it's the same for all states, let it
go. */
smsproto_freeMsgArray( state, arr ); /* give it a chance to store state */
smsproto_free( state );
LOG_RETURN_VOID();
}
#endif