Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2018-08-04 11:14:54 -07:00
commit 9fb8671ef0
31 changed files with 813 additions and 570 deletions

View file

@ -10,7 +10,7 @@ android:
- tools - tools
- platform-tools - platform-tools
- build-tools-27.0.3 - build-tools-27.0.3
- android-26 - android-23
licenses: licenses:
- android-sdk-preview-license-.+ - android-sdk-preview-license-.+
- android-sdk-license-.+ - android-sdk-license-.+

View file

@ -1,6 +1,6 @@
def INITIAL_CLIENT_VERS = 8 def INITIAL_CLIENT_VERS = 8
def VERSION_CODE_BASE = 133 def VERSION_CODE_BASE = 135
def VERSION_NAME = '4.4.137' def VERSION_NAME = '4.4.139'
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
def BUILD_INFO_NAME = "build-info.txt" def BUILD_INFO_NAME = "build-info.txt"
@ -33,7 +33,7 @@ android {
buildToolsVersion '27.0.3' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
minSdkVersion 8 minSdkVersion 8
targetSdkVersion 26 targetSdkVersion 23
versionCode VERSION_CODE_BASE versionCode VERSION_CODE_BASE
versionName VERSION_NAME versionName VERSION_NAME
} }
@ -92,7 +92,6 @@ android {
} }
xw4d { xw4d {
dimension "variant" dimension "variant"
minSdkVersion 8
applicationId "org.eehouse.android.xw4dbg" applicationId "org.eehouse.android.xw4dbg"
manifestPlaceholders = [ FABRIC_API_KEY: "$FABRIC_API_KEY", APP_ID: applicationId, ] manifestPlaceholders = [ FABRIC_API_KEY: "$FABRIC_API_KEY", APP_ID: applicationId, ]
resValue "string", "app_name", "CrossDbg" resValue "string", "app_name", "CrossDbg"
@ -223,7 +222,7 @@ task copyStrings(type: Exec) {
task ndkSetup(type: Exec) { task ndkSetup(type: Exec) {
workingDir '../' workingDir '../'
commandLine "./scripts/ndksetup.sh", "--with-clang" commandLine "./scripts/ndksetup.sh", "--with-clang", "--arm-only"
} }
task myPreBuild(dependsOn: ['ndkSetup', 'mkImages', 'copyStrings', 'mkXml']) { task myPreBuild(dependsOn: ['ndkSetup', 'mkImages', 'copyStrings', 'mkXml']) {

View file

@ -13,9 +13,9 @@
</style> </style>
</head> </head>
<body> <body>
<h2>CrossWords 4.4.137 release</h2> <h2>CrossWords 4.4.139 release</h2>
<p>This release paves the way for play-via-SMS improvements.</p> <p>This release fixes a bug with picking tiles face-up</p>
<div id="survey"> <div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take <p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -25,12 +25,10 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Add new SMS-handling code. One feature isn't compatible with <li>Don't put up multiple tile pickers. (This feature is rarely
the previous release so it won't be activated until there's been used, but had apparently been broken for a while.)</li>
time for everybody to upgrade.</li> <li>Fix two more bugs reported by users (Thanks!)</li>
<li>Include new Catalan translations</li> <li>Include new translations for Catalan and Norwegian</li>
<li>Fix bug causing (rarely) unending notifications of wordlist
upgrade</li>
</ul> </ul>
<p>(The full changelog <p>(The full changelog

View file

@ -365,7 +365,7 @@ public class BTService extends XWService {
break; break;
case INVITE: case INVITE:
String jsonData = intent.getStringExtra( GAMEDATA_KEY ); String jsonData = intent.getStringExtra( GAMEDATA_KEY );
NetLaunchInfo nli = new NetLaunchInfo( this, jsonData ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, jsonData );
Log.i( TAG, "onStartCommand: nli: %s", nli.toString() ); Log.i( TAG, "onStartCommand: nli: %s", nli.toString() );
String btAddr = intent.getStringExtra( ADDR_KEY ); String btAddr = intent.getStringExtra( ADDR_KEY );
m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) ); m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
@ -545,7 +545,7 @@ public class BTService extends XWService {
NetLaunchInfo nli; NetLaunchInfo nli;
if ( BT_PROTO_JSONS == proto ) { if ( BT_PROTO_JSONS == proto ) {
String asJson = is.readUTF(); String asJson = is.readUTF();
nli = new NetLaunchInfo( BTService.this, asJson ); nli = NetLaunchInfo.makeFrom( BTService.this, asJson );
} else { } else {
short len = is.readShort(); short len = is.readShort();
byte[] nliData = new byte[len]; byte[] nliData = new byte[len];
@ -1106,11 +1106,17 @@ public class BTService extends XWService {
postEvent( MultiEvent.BT_GAME_CREATED, rowid ); postEvent( MultiEvent.BT_GAME_CREATED, rowid );
} }
@Override
MultiMsgSink getSink( long rowid )
{
return m_btMsgSink;
}
private BTCmd makeOrNotify( NetLaunchInfo nli, String btName, private BTCmd makeOrNotify( NetLaunchInfo nli, String btName,
String btAddr ) String btAddr )
{ {
BTCmd result; BTCmd result;
if ( handleInvitation( nli, btName, DictFetchOwner.OWNER_BT ) ) { if ( handleInvitation( nli, btName, DictFetchOwner.OWNER_BT ) ) { // here
result = BTCmd.INVITE_ACCPT; result = BTCmd.INVITE_ACCPT;
} else { } else {
result = BTCmd.INVITE_DUP_INVITE; // dupe of rematch result = BTCmd.INVITE_DUP_INVITE; // dupe of rematch

View file

@ -115,7 +115,7 @@ public class BoardDelegate extends DelegateBase
private BoardUtilCtxt m_utils; private BoardUtilCtxt m_utils;
private boolean m_gameOver = false; private boolean m_gameOver = false;
private JNIThread m_jniThread; private volatile JNIThread m_jniThread;
private JNIThread m_jniThreadRef; private JNIThread m_jniThreadRef;
private JNIThread.GameStateInfo m_gsi; private JNIThread.GameStateInfo m_gsi;
@ -136,6 +136,7 @@ public class BoardDelegate extends DelegateBase
m_when = when; m_when = when;
m_handle = handle; m_handle = handle;
} }
@Override
public void run() { public void run() {
m_timers[m_why] = null; m_timers[m_why] = null;
if ( null != m_jniThread ) { if ( null != m_jniThread ) {
@ -1117,6 +1118,7 @@ public class BoardDelegate extends DelegateBase
case ENABLE_SMS_DO: case ENABLE_SMS_DO:
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
retrySMSInvites( params ); retrySMSInvites( params );
} }
@ -1164,6 +1166,7 @@ public class BoardDelegate extends DelegateBase
case ENABLE_RELAY_DO_OR: case ENABLE_RELAY_DO_OR:
if ( m_dropOnDismiss ) { if ( m_dropOnDismiss ) {
postDelayed( new Runnable() { postDelayed( new Runnable() {
@Override
public void run() { public void run() {
askDropRelay(); askDropRelay();
} }
@ -1174,6 +1177,16 @@ public class BoardDelegate extends DelegateBase
finish(); finish();
break; break;
case BLANK_PICKED:
case TRAY_PICKED:
// If the user cancels the tile picker the common code doesn't
// know, and won't put it up again as long as this game remains
// loaded. There might be a way to fix that, but the safest thing
// to do for now is to close. User will have to begin the process
// of committing turn again on re-launching the game.
finish();
break;
default: default:
handled = false; handled = false;
} }
@ -1257,17 +1270,20 @@ public class BoardDelegate extends DelegateBase
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) public void eventOccurred( MultiService.MultiEvent event, final Object ... args )
{ {
boolean doStopProgress = false;
switch( event ) { switch( event ) {
case MESSAGE_ACCEPTED: case MESSAGE_ACCEPTED:
case MESSAGE_REFUSED: case MESSAGE_REFUSED:
ConnStatusHandler. ConnStatusHandler.
updateStatusIn( m_activity, this, CommsConnType.COMMS_CONN_BT, updateStatusIn( m_activity, this, CommsConnType.COMMS_CONN_BT,
MultiService.MultiEvent.MESSAGE_ACCEPTED == event); MultiService.MultiEvent.MESSAGE_ACCEPTED == event);
doStopProgress = true;
break; break;
case MESSAGE_NOGAME: case MESSAGE_NOGAME:
int gameID = (Integer)args[0]; int gameID = (Integer)args[0];
if ( gameID == m_gi.gameID ) { if ( gameID == m_gi.gameID ) {
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
showDialogFragment( DlgID.DLG_DELETED ); showDialogFragment( DlgID.DLG_DELETED );
} }
@ -1285,13 +1301,11 @@ public class BoardDelegate extends DelegateBase
Log.w( TAG, "failed to create game" ); Log.w( TAG, "failed to create game" );
break; break;
case NEWGAME_DUP_REJECTED: case NEWGAME_DUP_REJECTED:
if ( m_progressShown ) { doStopProgress = true;
m_progressShown = false;
stopProgress(); // in case it's a BT invite
}
final String msg = final String msg =
getString( R.string.err_dup_invite_fmt, (String)args[0] ); getString( R.string.err_dup_invite_fmt, (String)args[0] );
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
makeOkOnlyBuilder( msg ).show(); makeOkOnlyBuilder( msg ).show();
} }
@ -1311,13 +1325,15 @@ public class BoardDelegate extends DelegateBase
break; break;
default: default:
if ( m_progressShown ) { doStopProgress = true; // in case it's a BT invite
m_progressShown = false;
stopProgress(); // in case it's a BT invite
}
super.eventOccurred( event, args ); super.eventOccurred( event, args );
break; break;
} }
if ( doStopProgress && m_progressShown ) {
m_progressShown = false;
stopProgress();
}
} }
////////////////////////////////////////////////// //////////////////////////////////////////////////
@ -1328,6 +1344,7 @@ public class BoardDelegate extends DelegateBase
final boolean allHere, final int nMissing ) final boolean allHere, final int nMissing )
{ {
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override
public void run() { public void run() {
handleConndMessage( room, devOrder, allHere, nMissing ); // from here too handleConndMessage( room, devOrder, allHere, nMissing ); // from here too
} }
@ -1381,6 +1398,7 @@ public class BoardDelegate extends DelegateBase
final int strIDf = strID; final int strIDf = strID;
final DlgID dlgIDf = dlgID; final DlgID dlgIDf = dlgID;
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
showDialogFragment( dlgIDf, R.string.relay_alert, showDialogFragment( dlgIDf, R.string.relay_alert,
getString( strIDf ) ); getString( strIDf ) );
@ -1397,6 +1415,7 @@ public class BoardDelegate extends DelegateBase
{ {
if ( success ) { if ( success ) {
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
setGotGameDict( name ); setGotGameDict( name );
} }
@ -1452,6 +1471,7 @@ public class BoardDelegate extends DelegateBase
public void invalidateParent() public void invalidateParent()
{ {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override
public void run() { public void run() {
m_view.invalidate(); m_view.invalidate();
} }
@ -1462,6 +1482,7 @@ public class BoardDelegate extends DelegateBase
{ {
final String msg = ConnStatusHandler.getStatusText( m_activity, m_connTypes ); final String msg = ConnStatusHandler.getStatusText( m_activity, m_connTypes );
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
if ( null == msg ) { if ( null == msg ) {
askNoAddrsDelete(); askNoAddrsDelete();
@ -1627,6 +1648,7 @@ public class BoardDelegate extends DelegateBase
public void requestTime() public void requestTime()
{ {
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override
public void run() { public void run() {
if ( null != m_jniThread ) { if ( null != m_jniThread ) {
m_jniThread.handleBkgrnd( JNICmd.CMD_DO ); m_jniThread.handleBkgrnd( JNICmd.CMD_DO );
@ -1678,6 +1700,7 @@ public class BoardDelegate extends DelegateBase
if ( 0 != id ) { if ( 0 != id ) {
final String bonusStr = getString( id ); final String bonusStr = getString( id );
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
showToast( bonusStr ); showToast( bonusStr );
} }
@ -1696,6 +1719,7 @@ public class BoardDelegate extends DelegateBase
} }
final String text = expl; final String text = expl;
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
showToast( text ); showToast( text );
} }
@ -1706,6 +1730,7 @@ public class BoardDelegate extends DelegateBase
public void cellSquareHeld( final String words ) public void cellSquareHeld( final String words )
{ {
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
launchLookup( wordsToArray( words ), m_gi.dictLang ); launchLookup( wordsToArray( words ), m_gi.dictLang );
} }
@ -1744,19 +1769,25 @@ public class BoardDelegate extends DelegateBase
} }
} }
private void startTP( final Action action,
final TilePickAlert.TilePickState tps )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
show( TilePickAlert.newInstance( action, tps ) );
}
} );
}
// This is supposed to be called from the jni thread // This is supposed to be called from the jni thread
@Override @Override
public void notifyPickTileBlank( int playerNum, int col, int row, public void notifyPickTileBlank( int playerNum, int col, int row,
String[] texts ) String[] texts )
{ {
final TilePickAlert.TilePickState tps = TilePickAlert.TilePickState tps =
new TilePickAlert.TilePickState( playerNum, texts, col, row ); new TilePickAlert.TilePickState( playerNum, texts, col, row );
runOnUiThread( new Runnable() { startTP( Action.BLANK_PICKED, tps );
@Override
public void run() {
show( TilePickAlert.newInstance( Action.BLANK_PICKED, tps ) );
}
} );
} }
@Override @Override
@ -1764,15 +1795,10 @@ public class BoardDelegate extends DelegateBase
int playerNum, int nToPick, int playerNum, int nToPick,
String[] texts, int[] counts ) String[] texts, int[] counts )
{ {
final TilePickAlert.TilePickState tps TilePickAlert.TilePickState tps
= new TilePickAlert.TilePickState( isInitial, playerNum, nToPick, = new TilePickAlert.TilePickState( isInitial, playerNum, nToPick,
texts, counts ); texts, counts );
runOnUiThread( new Runnable() { startTP( Action.TRAY_PICKED, tps );
@Override
public void run() {
show( TilePickAlert.newInstance( Action.TRAY_PICKED, tps ) );
}
} );
} }
@Override @Override
@ -1787,6 +1813,7 @@ public class BoardDelegate extends DelegateBase
if ( 0 <= newTurn ) { if ( 0 <= newTurn ) {
m_mySIS.nMissing = 0; m_mySIS.nMissing = 0;
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
makeNotAgainBuilder( R.string.not_again_turnchanged, makeNotAgainBuilder( R.string.not_again_turnchanged,
R.string.key_notagain_turnchanged ) R.string.key_notagain_turnchanged )
@ -1801,7 +1828,9 @@ public class BoardDelegate extends DelegateBase
@Override @Override
public boolean engineProgressCallback() public boolean engineProgressCallback()
{ {
return ! m_jniThread.busy(); // return true if engine should keep going
JNIThread jnit = m_jniThread;
return jnit != null && !jnit.busy();
} }
@Override @Override
@ -1879,6 +1908,7 @@ public class BoardDelegate extends DelegateBase
if ( asToast ) { if ( asToast ) {
final int residf = resid; final int residf = resid;
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override
public void run() { public void run() {
showToast( residf ); showToast( residf );
} }
@ -2021,6 +2051,7 @@ public class BoardDelegate extends DelegateBase
String fromPlayer, final int tsSeconds ) String fromPlayer, final int tsSeconds )
{ {
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override
public void run() { public void run() {
DBUtils.appendChatHistory( m_activity, m_rowid, msg, DBUtils.appendChatHistory( m_activity, m_rowid, msg,
fromIndx, tsSeconds ); fromIndx, tsSeconds );
@ -2543,6 +2574,7 @@ public class BoardDelegate extends DelegateBase
if ( keepOn ) { if ( keepOn ) {
if ( null == m_screenTimer ) { if ( null == m_screenTimer ) {
m_screenTimer = new Runnable() { m_screenTimer = new Runnable() {
@Override
public void run() { public void run() {
if ( null != m_view ) { if ( null != m_view ) {
m_view.setKeepScreenOn( false ); m_view.setKeepScreenOn( false );

View file

@ -126,8 +126,6 @@ public class ConnStatusHandler {
private ConnStatusHandler() {} private ConnStatusHandler() {}
private static HashMap<CommsConnType,SuccessRecord[]> s_records =
new HashMap<CommsConnType,SuccessRecord[]>();
private static boolean s_needsSave = false; private static boolean s_needsSave = false;
public static void setRect( int left, int top, int right, int bottom ) public static void setRect( int left, int top, int right, int bottom )
@ -184,7 +182,7 @@ public class ConnStatusHandler {
typ.longName( context ), did ) ); typ.longName( context ), did ) );
// For sends we list failures too. // For sends we list failures too.
SuccessRecord record = recordFor( typ, false ); SuccessRecord record = recordFor( context, typ, false );
tmp = LocUtils.getString( context, record.successNewer? tmp = LocUtils.getString( context, record.successNewer?
R.string.connstat_succ : R.string.connstat_succ :
R.string.connstat_unsucc ); R.string.connstat_unsucc );
@ -214,7 +212,7 @@ public class ConnStatusHandler {
} }
sb.append( "\n" ); sb.append( "\n" );
record = recordFor( typ, true ); record = recordFor( context, typ, true );
if ( record.haveSuccess() ) { if ( record.haveSuccess() ) {
sb.append( LocUtils.getString( context, sb.append( LocUtils.getString( context,
R.string.connstat_lastreceipt_fmt, R.string.connstat_lastreceipt_fmt,
@ -264,7 +262,7 @@ public class ConnStatusHandler {
} }
synchronized( ConnStatusHandler.class ) { synchronized( ConnStatusHandler.class ) {
SuccessRecord record = recordFor( connType, isIn ); SuccessRecord record = recordFor( context, connType, isIn );
record.update( success ); record.update( success );
} }
invalidateParent(); invalidateParent();
@ -306,14 +304,14 @@ public class ConnStatusHandler {
// Do the background coloring and arrow. Top half first // Do the background coloring and arrow. Top half first
scratchR.bottom -= (2 * quarterHeight); scratchR.bottom -= (2 * quarterHeight);
fillHalf( canvas, scratchR, connTypes, enabled, false ); fillHalf( context, canvas, scratchR, connTypes, enabled, false );
scratchR.bottom -= quarterHeight; scratchR.bottom -= quarterHeight;
drawArrow( canvas, res, scratchR, false ); drawArrow( canvas, res, scratchR, false );
// bottom half and arrow // bottom half and arrow
scratchR.top = s_rect.top + (2 * quarterHeight); scratchR.top = s_rect.top + (2 * quarterHeight);
scratchR.bottom = s_rect.bottom; scratchR.bottom = s_rect.bottom;
fillHalf( canvas, scratchR, connTypes, enabled, true ); fillHalf( context, canvas, scratchR, connTypes, enabled, true );
scratchR.top += quarterHeight; scratchR.top += quarterHeight;
drawArrow( canvas, res, scratchR, true ); drawArrow( canvas, res, scratchR, true );
@ -332,11 +330,11 @@ public class ConnStatusHandler {
} }
} }
private static void fillHalf( Canvas canvas, Rect rect, private static void fillHalf( Context context, Canvas canvas, Rect rect,
CommsConnTypeSet connTypes, boolean enabled, CommsConnTypeSet connTypes, boolean enabled,
boolean isIn ) boolean isIn )
{ {
enabled = enabled && null != newestSuccess( connTypes, isIn ); enabled = enabled && null != newestSuccess( context, connTypes, isIn );
s_fillPaint.setColor( enabled ? XWApp.GREEN : XWApp.RED ); s_fillPaint.setColor( enabled ? XWApp.GREEN : XWApp.RED );
canvas.drawRect( rect, s_fillPaint ); canvas.drawRect( rect, s_fillPaint );
} }
@ -359,19 +357,23 @@ public class ConnStatusHandler {
// This gets rid of lint warning, but I don't like it as it // This gets rid of lint warning, but I don't like it as it
// effects the whole method. // effects the whole method.
// @SuppressWarnings("unchecked") // @SuppressWarnings("unchecked")
public static void loadState( Context context ) private static HashMap<CommsConnType,SuccessRecord[]> s_records;
private static HashMap<CommsConnType,SuccessRecord[]> getRecords( Context context )
{ {
synchronized( ConnStatusHandler.class ) { synchronized( ConnStatusHandler.class ) {
s_records = null; if ( s_records == null ) {
String as64 = XWPrefs.getPrefsString( context, String as64 = XWPrefs.getPrefsString( context,
R.string.key_connstat_data ); R.string.key_connstat_data );
if ( null != as64 && 0 < as64.length() ) { if ( null != as64 && 0 < as64.length() ) {
s_records = (HashMap<CommsConnType,SuccessRecord[]>)Utils.string64ToSerializable(as64); s_records = (HashMap<CommsConnType,SuccessRecord[]>)Utils.
} string64ToSerializable(as64);
if ( null == s_records ) { }
s_records = new HashMap<CommsConnType,SuccessRecord[]>(); if ( null == s_records ) {
s_records = new HashMap<CommsConnType,SuccessRecord[]>();
}
} }
} }
return s_records;
} }
private static void saveState( final Context context, private static void saveState( final Context context,
@ -440,7 +442,8 @@ public class ConnStatusHandler {
icon.draw( canvas ); icon.draw( canvas );
} }
private static SuccessRecord newestSuccess( CommsConnTypeSet connTypes, private static SuccessRecord newestSuccess( Context context,
CommsConnTypeSet connTypes,
boolean isIn ) boolean isIn )
{ {
SuccessRecord result = null; SuccessRecord result = null;
@ -448,7 +451,7 @@ public class ConnStatusHandler {
Iterator<CommsConnType> iter = connTypes.iterator(); Iterator<CommsConnType> iter = connTypes.iterator();
while ( iter.hasNext() ) { while ( iter.hasNext() ) {
CommsConnType connType = iter.next(); CommsConnType connType = iter.next();
SuccessRecord record = recordFor( connType, isIn ); SuccessRecord record = recordFor( context, connType, isIn );
if ( record.successNewer ) { if ( record.successNewer ) {
if ( null == result || result.lastSuccess < record.lastSuccess ) { if ( null == result || result.lastSuccess < record.lastSuccess ) {
result = record; result = record;
@ -459,14 +462,16 @@ public class ConnStatusHandler {
return result; return result;
} }
private static SuccessRecord recordFor( CommsConnType connType, boolean isIn ) private static SuccessRecord recordFor( Context context,
CommsConnType connType,
boolean isIn )
{ {
SuccessRecord[] records = s_records.get( connType ); SuccessRecord[] records = getRecords( context ).get( connType );
if ( null == records ) { if ( null == records ) {
records = new SuccessRecord[] { new SuccessRecord(), records = new SuccessRecord[] { new SuccessRecord(),
new SuccessRecord(), new SuccessRecord(),
}; };
s_records.put( connType, records ); getRecords( context ).put( connType, records );
} }
return records[isIn?0:1]; return records[isIn?0:1];
} }
@ -474,7 +479,7 @@ public class ConnStatusHandler {
private static void doSave( Context context ) private static void doSave( Context context )
{ {
synchronized( ConnStatusHandler.class ) { synchronized( ConnStatusHandler.class ) {
String as64 = Utils.serializableToString64( s_records ); String as64 = Utils.serializableToString64( getRecords( context ) );
XWPrefs.setPrefsString( context, R.string.key_connstat_data, XWPrefs.setPrefsString( context, R.string.key_connstat_data,
as64 ); as64 );
s_needsSave = false; s_needsSave = false;

View file

@ -1183,7 +1183,9 @@ public class GameUtils {
boolean informNow ) boolean informNow )
{ {
GameSummary summary = DBUtils.getSummary( context, lock ); GameSummary summary = DBUtils.getSummary( context, lock );
if ( DeviceRole.SERVER_STANDALONE != summary.serverRole ) { if ( null == summary ) {
Log.e( TAG, "tellDied(): can't get summary" );
} else if ( DeviceRole.SERVER_STANDALONE != summary.serverRole ) {
int gameID = summary.gameID; int gameID = summary.gameID;
GamePtr gamePtr = loadMakeGame( context, lock ); GamePtr gamePtr = loadMakeGame( context, lock );

View file

@ -2260,7 +2260,7 @@ public class GamesListDelegate extends ListDelegateBase
{ {
String data = NFCUtils.getFromIntent( intent ); String data = NFCUtils.getFromIntent( intent );
if ( null != data ) { if ( null != data ) {
NetLaunchInfo nli = new NetLaunchInfo( m_activity, data ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( m_activity, data );
if ( nli.isValid() ) { if ( nli.isValid() ) {
startNewNetGame( nli ); startNewNetGame( nli );
} }

View file

@ -149,7 +149,8 @@ public class MultiService {
{ {
Assert.assertTrue( isMissingDictIntent( intent ) ); Assert.assertTrue( isMissingDictIntent( intent ) );
String nliData = intent.getStringExtra( NLI_DATA ); String nliData = intent.getStringExtra( NLI_DATA );
NetLaunchInfo nli = new NetLaunchInfo( context, nliData ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( context, nliData );
Assert.assertTrue( nli != null || !BuildConfig.DEBUG );
return nli; return nli;
} }

View file

@ -1,7 +1,7 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */ /* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */
/* /*
* Copyright 2009-2011 by Eric House (xwords@eehouse.org). All * Copyright 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
* rights reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@ -27,8 +27,13 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Iterator; import java.util.Iterator;
import org.json.JSONException; import org.json.JSONException;
@ -90,7 +95,7 @@ public class NetLaunchInfo implements Serializable {
inviteID = GameUtils.formatGameID( Utils.nextRandomInt() ); inviteID = GameUtils.formatGameID( Utils.nextRandomInt() );
} }
public NetLaunchInfo( Context context, String data ) private NetLaunchInfo( Context context, String data ) throws JSONException
{ {
init( context, data ); init( context, data );
} }
@ -128,6 +133,31 @@ public class NetLaunchInfo implements Serializable {
return nli; return nli;
} }
public static NetLaunchInfo makeFrom( Context context, String data )
{
NetLaunchInfo nli = null;
try {
nli = new NetLaunchInfo( context, data );
} catch ( JSONException jse ) {
Log.ex( TAG, jse );
}
return nli;
}
public static NetLaunchInfo makeFrom( Context context, byte[] data )
{
NetLaunchInfo nli = null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream( data );
DataInputStream dis = new DataInputStream( bais );
String nliData = dis.readUTF();
nli = NetLaunchInfo.makeFrom( context, nliData );
} catch ( java.io.IOException ex ) {
Assert.assertFalse( BuildConfig.DEBUG );
}
return nli;
}
public NetLaunchInfo( Context context, Uri data ) public NetLaunchInfo( Context context, Uri data )
{ {
this(); this();
@ -422,63 +452,58 @@ public class NetLaunchInfo implements Serializable {
return result; return result;
} }
private void init( Context context, String data ) private void init( Context context, String data ) throws JSONException
{ {
CommsConnTypeSet supported = CommsConnTypeSet.getSupported( context ); CommsConnTypeSet supported = CommsConnTypeSet.getSupported( context );
try { JSONObject json = new JSONObject( data );
JSONObject json = new JSONObject( data );
int flags = json.optInt(ADDRS_KEY, -1); int flags = json.optInt(ADDRS_KEY, -1);
boolean hasAddrs = -1 != flags; boolean hasAddrs = -1 != flags;
m_addrs = hasAddrs ? m_addrs = hasAddrs ?
new CommsConnTypeSet( flags ) : new CommsConnTypeSet(); new CommsConnTypeSet( flags ) : new CommsConnTypeSet();
lang = json.optInt( MultiService.LANG, -1 ); lang = json.optInt( MultiService.LANG, -1 );
forceChannel = json.optInt( MultiService.FORCECHANNEL, 0 ); forceChannel = json.optInt( MultiService.FORCECHANNEL, 0 );
dict = json.optString( MultiService.DICT ); dict = json.optString( MultiService.DICT );
gameName = json.optString( MultiService.GAMENAME ); gameName = json.optString( MultiService.GAMENAME );
nPlayersT = json.optInt( MultiService.NPLAYERST, -1 ); nPlayersT = json.optInt( MultiService.NPLAYERST, -1 );
nPlayersH = json.optInt( MultiService.NPLAYERSH, 1 ); // absent ok nPlayersH = json.optInt( MultiService.NPLAYERSH, 1 ); // absent ok
gameID = json.optInt( MultiService.GAMEID, 0 ); gameID = json.optInt( MultiService.GAMEID, 0 );
// Try each type // Try each type
for ( CommsConnType typ : supported.getTypes() ) { for ( CommsConnType typ : supported.getTypes() ) {
if ( hasAddrs && !m_addrs.contains( typ ) ) { if ( hasAddrs && !m_addrs.contains( typ ) ) {
continue; continue;
} }
boolean doAdd; boolean doAdd;
switch ( typ ) { switch ( typ ) {
case COMMS_CONN_BT: case COMMS_CONN_BT:
btAddress = json.optString( MultiService.BT_ADDRESS ); btAddress = json.optString( MultiService.BT_ADDRESS );
btName = json.optString( MultiService.BT_NAME ); btName = json.optString( MultiService.BT_NAME );
doAdd = !hasAddrs && !btAddress.isEmpty(); doAdd = !hasAddrs && !btAddress.isEmpty();
break; break;
case COMMS_CONN_RELAY: case COMMS_CONN_RELAY:
room = json.getString( MultiService.ROOM ); room = json.getString( MultiService.ROOM );
inviteID = json.optString( MultiService.INVITEID ); inviteID = json.optString( MultiService.INVITEID );
doAdd = !hasAddrs && !room.isEmpty(); doAdd = !hasAddrs && !room.isEmpty();
break; break;
case COMMS_CONN_SMS: case COMMS_CONN_SMS:
phone = json.optString( PHONE_KEY ); phone = json.optString( PHONE_KEY );
isGSM = json.optBoolean( GSM_KEY, false ); isGSM = json.optBoolean( GSM_KEY, false );
osVers = json.optInt( OSVERS_KEY, 0 ); osVers = json.optInt( OSVERS_KEY, 0 );
doAdd = !hasAddrs && !phone.isEmpty(); doAdd = !hasAddrs && !phone.isEmpty();
break; break;
case COMMS_CONN_P2P: case COMMS_CONN_P2P:
p2pMacAddress = json.optString( P2P_MAC_KEY ); p2pMacAddress = json.optString( P2P_MAC_KEY );
doAdd = !hasAddrs && null != p2pMacAddress; doAdd = !hasAddrs && null != p2pMacAddress;
break; break;
default: default:
doAdd = false; doAdd = false;
Assert.fail(); Assert.fail();
} }
if ( doAdd ) { if ( doAdd ) {
m_addrs.add( typ ); m_addrs.add( typ );
}
} }
} catch ( JSONException jse ) {
Log.ex( TAG, jse );
} }
removeUnsupported( supported ); removeUnsupported( supported );
@ -589,6 +614,20 @@ public class NetLaunchInfo implements Serializable {
return makeLaunchJSON(); return makeLaunchJSON();
} }
public byte[] asByteArray()
{
byte[] result = null;
try {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
DataOutputStream das = new DataOutputStream( bas );
das.writeUTF( makeLaunchJSON() );
result = bas.toByteArray();
} catch ( java.io.IOException ex ) {
Assert.assertFalse( BuildConfig.DEBUG );
}
return result;
}
public static void putExtras( Intent intent, int gameID, String btAddr ) public static void putExtras( Intent intent, int gameID, String btAddr )
{ {
Assert.fail(); Assert.fail();

View file

@ -397,7 +397,7 @@ public class RelayService extends XWService
case GOT_INVITE: case GOT_INVITE:
int srcDevID = intent.getIntExtra( INVITE_FROM, 0 ); int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
NetLaunchInfo nli NetLaunchInfo nli
= new NetLaunchInfo( this, intent.getStringExtra(NLI_DATA) ); = NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
receiveInvitation( srcDevID, nli ); receiveInvitation( srcDevID, nli );
break; break;
case SEND: case SEND:
@ -820,7 +820,7 @@ public class RelayService extends XWService
Log.d( TAG, "sendInvitation(%d->%d/%s [%s])", srcDevID, destDevID, Log.d( TAG, "sendInvitation(%d->%d/%s [%s])", srcDevID, destDevID,
relayID, nliStr ); relayID, nliStr );
NetLaunchInfo nli = new NetLaunchInfo( this, nliStr ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, nliStr );
byte[] nliData = XwJNI.nliToStream( nli ); byte[] nliData = XwJNI.nliToStream( nli );
if ( BuildConfig.DEBUG ) { if ( BuildConfig.DEBUG ) {
NetLaunchInfo tmp = XwJNI.nliFromStream( nliData ); NetLaunchInfo tmp = XwJNI.nliFromStream( nliData );

View file

@ -41,6 +41,8 @@ import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.SMSProtoMsg;
import org.eehouse.android.xw4.jni.XwJNI.SMS_CMD;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -75,17 +77,13 @@ public class SMSService extends XWService {
private static final String BUFFER = "BUFFER"; private static final String BUFFER = "BUFFER";
private static final String BINBUFFER = "BINBUFFER"; private static final String BINBUFFER = "BINBUFFER";
private static final String PHONE = "PHONE"; private static final String PHONE = "PHONE";
private static final String GAMEDATA_STR = "GD"; private static final String GAMEDATA_BA = "GD";
private static final String PHONE_RECS_KEY = private static final String PHONE_RECS_KEY =
SMSService.class.getName() + "_PHONES"; SMSService.class.getName() + "_PHONES";
private static Boolean s_showToasts = null; private static Boolean s_showToasts = null;
// All messages are base64-encoded byte arrays. The first byte is
// always one of these. What follows depends.
private enum SMS_CMD { NONE, INVITE, DATA, DEATH, ACK, };
private BroadcastReceiver m_sentReceiver; private BroadcastReceiver m_sentReceiver;
private BroadcastReceiver m_receiveReceiver; private BroadcastReceiver m_receiveReceiver;
private OnSharedPreferenceChangeListener m_prefsListener; private OnSharedPreferenceChangeListener m_prefsListener;
@ -195,9 +193,9 @@ public class SMSService extends XWService {
{ {
Intent intent = getIntentTo( context, SMSAction.INVITE ); Intent intent = getIntentTo( context, SMSAction.INVITE );
intent.putExtra( PHONE, phone ); intent.putExtra( PHONE, phone );
String asString = nli.toString(); Log.w( TAG, "inviteRemote(%s, '%s')", phone, nli );
Log.w( TAG, "inviteRemote(%s, '%s')", phone, asString ); byte[] data = nli.asByteArray();
intent.putExtra( GAMEDATA_STR, asString ); intent.putExtra( GAMEDATA_BA, data );
context.startService( intent ); context.startService( intent );
} }
@ -339,7 +337,8 @@ public class SMSService extends XWService {
break; break;
case INVITE: case INVITE:
phone = intent.getStringExtra( PHONE ); phone = intent.getStringExtra( PHONE );
inviteRemote( phone, intent.getStringExtra( GAMEDATA_STR ) ); buffer = intent.getByteArrayExtra( GAMEDATA_BA );
inviteRemote( phone, buffer );
break; break;
case ADDED_MISSING: case ADDED_MISSING:
NetLaunchInfo nli NetLaunchInfo nli
@ -360,7 +359,7 @@ public class SMSService extends XWService {
break; break;
case RESEND: case RESEND:
phone = intent.getStringExtra( PHONE ); phone = intent.getStringExtra( PHONE );
resendFor( phone, null, false ); resendFor( phone );
} }
} }
@ -375,100 +374,59 @@ public class SMSService extends XWService {
return result; return result;
} // onStartCommand } // onStartCommand
private void inviteRemote( String phone, String nliData ) private void inviteRemote( String phone, byte[] asBytes )
{ {
Log.i( TAG, "inviteRemote()" ); resendFor( phone, SMS_CMD.INVITE, 0, asBytes, true );
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
DataOutputStream dos = new DataOutputStream( bas );
try {
dos.writeUTF( nliData );
dos.flush();
send( SMS_CMD.INVITE, bas.toByteArray(), phone );
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
} }
private void ackInvite( String phone, int gameID ) private void ackInvite( String phone, int gameID )
{ {
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 ); resendFor( phone, SMS_CMD.ACK_INVITE, gameID, null );
DataOutputStream dos = new DataOutputStream( bas );
try {
dos.writeInt( gameID );
dos.flush();
send( SMS_CMD.ACK, bas.toByteArray(), phone );
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
} }
private void sendDiedPacket( String phone, int gameID ) private void sendDiedPacket( String phone, int gameID )
{ {
if ( !s_sentDied.contains(gameID) ) { if ( !s_sentDied.contains(gameID) ) {
ByteArrayOutputStream bas = new ByteArrayOutputStream( 32 ); resendFor( phone, SMS_CMD.DEATH, gameID, null );
DataOutputStream dos = new DataOutputStream( bas );
try {
dos.writeInt( gameID );
dos.flush();
send( SMS_CMD.DEATH, bas.toByteArray(), phone );
s_sentDied.add( gameID );
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
} }
} }
public int sendPacket( String phone, int gameID, byte[] bytes ) public int sendPacket( String phone, int gameID, byte[] bytes )
{ {
int nSent = -1; resendFor( phone, SMS_CMD.DATA, gameID, bytes );
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 ); return bytes.length;
DataOutputStream dos = new DataOutputStream( bas );
try {
dos.writeInt( gameID );
dos.write( bytes, 0, bytes.length );
dos.flush();
send( SMS_CMD.DATA, bas.toByteArray(), phone );
nSent = bytes.length;
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
return nSent;
} }
private void send( SMS_CMD cmd, byte[] bytes, String phone ) private void sendOrRetry( byte[][] msgs, String toPhone, int waitSecs )
throws java.io.IOException
{ {
Log.d( TAG, "send(%s, len=%d)", cmd, bytes.length ); if ( null != msgs ) {
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 ); sendBuffers( msgs, toPhone );
DataOutputStream dos = new DataOutputStream( bas );
dos.writeByte( SMS_PROTO_VERSION );
if ( SMS_PROTO_VERSION_WITHPORT <= SMS_PROTO_VERSION ) {
dos.writeShort( getNBSPort() );
} }
dos.writeByte( cmd.ordinal() ); if ( waitSecs > 0 ) {
dos.write( bytes, 0, bytes.length ); postResend( toPhone, waitSecs );
dos.flush(); }
}
byte[] data = bas.toByteArray();
private void resendFor( String toPhone, SMS_CMD cmd, int gameID, byte[] data )
{
boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this ); boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this );
boolean forceNow = !newSMSEnabled; // || cmd == SMS_CMD.INVITE; boolean forceNow = !newSMSEnabled; // || cmd == SMS_CMD.INVITE;
resendFor( phone, data, forceNow ); resendFor( toPhone, cmd, gameID, data, forceNow );
} }
private void resendFor( String phone, byte[] data, boolean forceNow ) private void resendFor( String toPhone )
{
resendFor( toPhone, SMS_CMD.NONE, 0, null, false );
}
private void resendFor( String toPhone, SMS_CMD cmd, int gameID, byte[] data,
boolean forceNow )
{ {
int[] waitSecs = { 0 }; int[] waitSecs = { 0 };
byte[][] msgs = XwJNI.smsproto_prepOutbound( data, phone, forceNow, waitSecs ); byte[][] msgs = XwJNI.smsproto_prepOutbound( cmd, gameID, data, toPhone,
if ( null != msgs ) { getNBSPort(), forceNow,
sendBuffers( msgs, phone ); waitSecs );
} sendOrRetry( msgs, toPhone, waitSecs[0] );
if ( waitSecs[0] > 0 ) {
Assert.assertFalse( forceNow );
postResend( phone, waitSecs[0] );
}
} }
private void postResend( final String phone, final int waitSecs ) private void postResend( final String phone, final int waitSecs )
@ -492,97 +450,46 @@ public class SMSService extends XWService {
} ).start(); } ).start();
} }
private void receive( SMS_CMD cmd, byte[] data, String phone ) private void receive( SMSProtoMsg msg, String phone )
{ {
Log.i( TAG, "receive(cmd=%s)", cmd.toString() ); Log.i( TAG, "receive(cmd=%s)", msg.cmd );
DataInputStream dis = switch( msg.cmd ) {
new DataInputStream( new ByteArrayInputStream(data) ); case INVITE:
try { makeForInvite( phone, NetLaunchInfo.makeFrom( this, msg.data ) );
switch( cmd ) { break;
case INVITE: case DATA:
String nliData = dis.readUTF(); if ( feedMessage( msg.gameID, msg.data, new CommsAddrRec( phone ) ) ) {
makeForInvite( phone, new NetLaunchInfo( this, nliData ) ); SMSResendReceiver.resetTimer( this );
break;
case DATA:
int gameID = dis.readInt();
byte[] rest = new byte[dis.available()];
dis.readFully( rest );
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
SMSResendReceiver.resetTimer( this );
}
break;
case DEATH:
gameID = dis.readInt();
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
break;
case ACK:
gameID = dis.readInt();
postEvent( MultiEvent.NEWGAME_SUCCESS,
gameID );
break;
default:
Log.w( TAG, "unexpected cmd %s", cmd.toString() );
break;
} }
} catch ( java.io.IOException ioe ) { break;
Log.ex( TAG, ioe ); case DEATH:
postEvent( MultiEvent.MESSAGE_NOGAME, msg.gameID );
break;
case ACK_INVITE:
postEvent( MultiEvent.NEWGAME_SUCCESS, msg.gameID );
break;
default:
Log.w( TAG, "unexpected cmd %s", msg.cmd );
Assert.assertFalse( BuildConfig.DEBUG );
break;
} }
} }
private void receiveBuffer( byte[] buffer, String senderPhone ) private void receiveBuffer( byte[] buffer, String senderPhone )
{ {
byte[][] msgs = XwJNI.smsproto_prepInbound( buffer, senderPhone ); SMSProtoMsg[] msgs = XwJNI.smsproto_prepInbound( buffer, senderPhone,
getNBSPort() );
if ( null != msgs ) { if ( null != msgs ) {
for ( byte[] msg : msgs ) { for ( SMSProtoMsg msg : msgs ) {
if ( !disAssemble( senderPhone, msg ) ) { receive( msg, senderPhone );
Log.e( TAG, "failed on message from %s", senderPhone );
}
} }
postEvent( MultiEvent.SMS_RECEIVE_OK ); postEvent( MultiEvent.SMS_RECEIVE_OK );
} else { } else {
Log.w( TAG, "receiveBuffer(): bogus or incomplete message from phone %s", Log.d( TAG, "receiveBuffer(): bogus or incomplete message from %s",
senderPhone ); senderPhone );
} }
} }
private boolean disAssemble( String senderPhone, byte[] fullMsg )
{
boolean success = false;
DataInputStream dis =
new DataInputStream( new ByteArrayInputStream(fullMsg) );
try {
byte proto = dis.readByte();
short myPort = getNBSPort();
short gotPort;
if ( SMS_PROTO_VERSION_WITHPORT > proto ) {
gotPort = myPort;
} else {
gotPort = dis.readShort();
}
if ( SMS_PROTO_VERSION < proto ) {
Log.w( TAG, "SMSService.disAssemble: bad proto %d from %s;"
+ " dropping", proto, senderPhone );
postEvent( MultiEvent.BAD_PROTO_SMS, senderPhone );
} else if ( gotPort != myPort ) {
Log.d( TAG, "disAssemble(): received on port %d"
+ " but expected %d", gotPort, myPort );
} else {
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
byte[] rest = new byte[dis.available()];
dis.readFully( rest );
receive( cmd, rest, senderPhone );
success = true;
}
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
} catch ( ArrayIndexOutOfBoundsException oob ) {
// enum this older code doesn't know about, or just another app's
// message; drop it
Log.w( TAG, "disAssemble: dropping message with too-new enum" );
}
return success;
}
@Override @Override
protected void postNotification( String phone, int gameID, long rowid ) protected void postNotification( String phone, int gameID, long rowid )
{ {
@ -594,7 +501,8 @@ public class SMSService extends XWService {
private void makeForInvite( String phone, NetLaunchInfo nli ) private void makeForInvite( String phone, NetLaunchInfo nli )
{ {
if ( handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS ) ) { if ( nli != null ) {
handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS );
ackInvite( phone, nli.gameID() ); ackInvite( phone, nli.gameID() );
} }
} }

View file

@ -144,6 +144,18 @@ public class TilePickAlert extends XWDialogFragment
} }
} }
@Override
public void onCancel( DialogInterface dialog )
{
super.onCancel( dialog );
Activity activity = getActivity();
if ( activity instanceof DlgClickNotify ) {
DlgClickNotify notify = (DlgClickNotify)activity;
notify.onDismissed( m_action );
}
}
private void onDone() private void onDone()
{ {
Activity activity = getActivity(); Activity activity = getActivity();

View file

@ -760,7 +760,7 @@ public class WiDirService extends XWService {
{ {
Log.d( TAG, "handleGotInvite()" ); Log.d( TAG, "handleGotInvite()" );
String nliData = intent.getStringExtra( KEY_NLI ); String nliData = intent.getStringExtra( KEY_NLI );
NetLaunchInfo nli = new NetLaunchInfo( this, nliData ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, nliData );
String returnMac = intent.getStringExtra( KEY_SRC ); String returnMac = intent.getStringExtra( KEY_SRC );
if ( !handleInvitation( nli, returnMac, DictFetchOwner.OWNER_P2P ) ) { if ( !handleInvitation( nli, returnMac, DictFetchOwner.OWNER_P2P ) ) {
@ -774,6 +774,12 @@ public class WiDirService extends XWService {
Log.e( TAG, "postNotification() doing nothing" ); Log.e( TAG, "postNotification() doing nothing" );
} }
@Override
MultiMsgSink getSink( long rowid )
{
return m_sink;
}
private void handleGameGone( Intent intent ) private void handleGameGone( Intent intent )
{ {
int gameID = intent.getIntExtra( KEY_GAMEID, 0 ); int gameID = intent.getIntExtra( KEY_GAMEID, 0 );

View file

@ -71,8 +71,6 @@ public class XWApp extends Application {
Log.i( TAG, "onCreate(); git_rev=%s", getString( R.string.git_rev ) ); Log.i( TAG, "onCreate(); git_rev=%s", getString( R.string.git_rev ) );
DbgUtils.logEnable( this ); DbgUtils.logEnable( this );
ConnStatusHandler.loadState( this );
OnBootReceiver.startTimers( this ); OnBootReceiver.startTimers( this );
boolean mustCheck = Utils.firstBootThisVersion( this ); boolean mustCheck = Utils.firstBootThisVersion( this );

View file

@ -89,6 +89,7 @@ abstract class XWService extends Service {
abstract void postNotification( String device, int gameID, long rowid ); abstract void postNotification( String device, int gameID, long rowid );
// Return true if able to start game only
protected boolean handleInvitation( NetLaunchInfo nli, String device, protected boolean handleInvitation( NetLaunchInfo nli, String device,
DictFetchOwner dfo ) DictFetchOwner dfo )
{ {
@ -110,6 +111,7 @@ abstract class XWService extends Service {
} }
postNotification( device, nli.gameID(), rowid ); postNotification( device, nli.gameID(), rowid );
success = true;
} else { } else {
Intent intent = MultiService Intent intent = MultiService
.makeMissingDictIntent( this, nli, dfo ); .makeMissingDictIntent( this, nli, dfo );
@ -118,6 +120,7 @@ abstract class XWService extends Service {
} }
} }
} }
Log.d( TAG, "handleInvitation() => %b", success );
return success; return success;
} }
@ -130,7 +133,7 @@ abstract class XWService extends Service {
} }
// Meant to be overridden // Meant to be overridden
protected MultiMsgSink getSink( long rowid ) { Assert.fail(); return null; } abstract MultiMsgSink getSink( long rowid );
protected ReceiveResult receiveMessage( Context context, int gameID, protected ReceiveResult receiveMessage( Context context, int gameID,
MultiMsgSink sink, byte[] msg, MultiMsgSink sink, byte[] msg,

View file

@ -403,17 +403,25 @@ public class XwJNI {
public static native boolean comms_getAddrDisabled( GamePtr gamePtr, CommsConnType typ, public static native boolean comms_getAddrDisabled( GamePtr gamePtr, CommsConnType typ,
boolean send ); boolean send );
public static byte[][] smsproto_prepOutbound( byte[] buf, String phone, boolean forceNow, public enum SMS_CMD { NONE, INVITE, DATA, DEATH, ACK_INVITE, };
/*out*/ int[] waitSecs ) public static class SMSProtoMsg {
{ public SMS_CMD cmd;
int nowSeconds = (int)(System.currentTimeMillis() / 1000); public int gameID;
return smsproto_prepOutbound( getJNI().m_ptr, buf, phone, nowSeconds, public byte[] data; // other cases
forceNow, waitSecs );
} }
public static byte[][] smsproto_prepInbound( byte[] data, String fromPhone ) public static byte[][]
smsproto_prepOutbound( SMS_CMD cmd, int gameID, byte[] buf, String phone,
int port, boolean forceNow, /*out*/ int[] waitSecs )
{ {
return smsproto_prepInbound( getJNI().m_ptr, data, fromPhone ); return smsproto_prepOutbound( getJNI().m_ptr, cmd, gameID, buf, phone,
port, forceNow, waitSecs );
}
public static SMSProtoMsg[] smsproto_prepInbound( byte[] data,
String fromPhone, int wantPort )
{
return smsproto_prepInbound( getJNI().m_ptr, data, fromPhone, wantPort );
} }
// Dicts // Dicts
@ -502,13 +510,15 @@ public class XwJNI {
private static native int dict_iter_init( int jniState, byte[] dict, private static native int dict_iter_init( int jniState, byte[] dict,
String name, String path ); String name, String path );
private static native byte[][] smsproto_prepOutbound( int jniState, byte[] buf, private static native byte[][]
String phone, int nowSeconds, smsproto_prepOutbound( int jniState, SMS_CMD cmd, int gameID, byte[] buf,
boolean forceNow, String phone, int port, boolean forceNow,
/*out*/int[] waitSecs ); /*out*/int[] waitSecs );
private static native byte[][] smsproto_prepInbound( int jniState, byte[] data, private static native SMSProtoMsg[] smsproto_prepInbound( int jniState,
String fromPhone ); byte[] data,
String fromPhone,
int wantPort);
private static native boolean haveEnv( int jniState ); private static native boolean haveEnv( int jniState );
} }

View file

@ -772,10 +772,11 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue
static jobjectArray static jobjectArray
msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr ) msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr )
{ {
XP_ASSERT( arr->format == FORMAT_NET );
jclass clas = (*env)->FindClass( env, "[B" ); jclass clas = (*env)->FindClass( env, "[B" );
jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL ); jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) { for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsg* msg = &arr->msgs[ii]; SMSMsgNet* msg = &arr->u.msgsNet[ii];
jbyteArray arr = makeByteArray( env, msg->len, (const jbyte*)msg->data ); jbyteArray arr = makeByteArray( env, msg->len, (const jbyte*)msg->data );
(*env)->SetObjectArrayElement( env, result, ii, arr ); (*env)->SetObjectArrayElement( env, result, ii, arr );
deleteLocalRef( env, arr ); deleteLocalRef( env, arr );
@ -783,15 +784,43 @@ msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr )
return result; return result;
} }
static jobjectArray
msgArrayToJMsgArray( JNIEnv* env, const SMSMsgArray* arr )
{
XP_ASSERT( arr->format == FORMAT_LOC );
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/XwJNI$SMSProtoMsg") );
jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL );
jmethodID initId = (*env)->GetMethodID( env, clas, "<init>", "()V" );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
jobject jmsg = (*env)->NewObject( env, clas, initId );
const SMSMsgLoc* msgsLoc = &arr->u.msgsLoc[ii];
intToJenumField( env, jmsg, msgsLoc->cmd, "cmd", PKG_PATH("jni/XwJNI$SMS_CMD") );
setInt( env, jmsg, "gameID", msgsLoc->gameID );
jbyteArray arr = makeByteArray( env, msgsLoc->len,
(const jbyte*)msgsLoc->data );
setObject( env, jmsg, "data", "[B", arr );
deleteLocalRef( env, arr );
(*env)->SetObjectArrayElement( env, result, ii, jmsg );
deleteLocalRef( env, jmsg );
}
return result;
}
JNIEXPORT jobjectArray JNICALL JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData, ( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jCmd, jint jGameID,
jstring jToPhone, jint jNow, jboolean jForce, jintArray jWaitSecsArr ) jbyteArray jData, jstring jToPhone, jint jPort, jboolean jForce,
jintArray jWaitSecsArr )
{ {
jobjectArray result = NULL; jobjectArray result = NULL;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &globalState->ti, env ); map_thread( &globalState->ti, env );
SMS_CMD cmd = jEnumToInt( env, jCmd );
jbyte* data = NULL; jbyte* data = NULL;
int len = 0; int len = 0;
if ( NULL != jData ) { if ( NULL != jData ) {
@ -801,8 +830,9 @@ Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
const char* toPhone = (*env)->GetStringUTFChars( env, jToPhone, NULL ); const char* toPhone = (*env)->GetStringUTFChars( env, jToPhone, NULL );
XP_U16 waitSecs; XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, (const XP_U8*)data, SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, cmd, jGameID,
len, toPhone, jForce, &waitSecs ); (const XP_U8*)data, len,
toPhone, jPort, jForce, &waitSecs );
if ( !!arr ) { if ( !!arr ) {
result = msgArrayToByteArrays( env, arr ); result = msgArrayToByteArrays( env, arr );
smsproto_freeMsgArray( globalState->smsProto, arr ); smsproto_freeMsgArray( globalState->smsProto, arr );
@ -821,21 +851,22 @@ Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
JNIEXPORT jobjectArray JNICALL JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData, ( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData,
jstring jFromPhone ) jstring jFromPhone, jint jWantPort )
{ {
jobjectArray result = NULL; jobjectArray result = NULL;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &globalState->ti, env );
if ( !!jData ) { if ( !!jData ) {
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &globalState->ti, env );
int len = (*env)->GetArrayLength( env, jData ); int len = (*env)->GetArrayLength( env, jData );
jbyte* data = (*env)->GetByteArrayElements( env, jData, NULL ); jbyte* data = (*env)->GetByteArrayElements( env, jData, NULL );
const char* fromPhone = (*env)->GetStringUTFChars( env, jFromPhone, NULL ); const char* fromPhone = (*env)->GetStringUTFChars( env, jFromPhone, NULL );
SMSMsgArray* arr = smsproto_prepInbound( globalState->smsProto, fromPhone, SMSMsgArray* arr = smsproto_prepInbound( globalState->smsProto, fromPhone,
(XP_U8*)data, len ); jWantPort, (XP_U8*)data, len );
if ( !!arr ) { if ( !!arr ) {
result = msgArrayToByteArrays( env, arr ); result = msgArrayToJMsgArray( env, arr );
smsproto_freeMsgArray( globalState->smsProto, arr ); smsproto_freeMsgArray( globalState->smsProto, arr );
} }

View file

@ -0,0 +1,36 @@
#!/bin/sh
set -e -u
usage() {
[ $# -gt 0 ] && echo "ERROR: $1"
echo "usage: $0 [--clean|--help]"
echo "meant for rapid work where a release build's required, this"
echo "builds, signs, and installs a Rel build of the xw4 variant"
exit 1
}
CLEAN=''
while [ $# -gt 0 ]; do
case $1 in
--clean)
CLEAN=clean
;;
--help)
usage
;;
*)
usage "unexpected command $1"
;;
esac
shift
done
cd $(dirname $0)/..
pwd
./gradlew $CLEAN asXw4Rel
./scripts/sign-align.sh --apk $(find app/build -name '*xw4-release-unsigned-*.apk')
adb install -r $(find app/build -name '*xw4-release-signed-*.apk')

View file

@ -3,24 +3,31 @@
set -e -u set -e -u
NODE=xw4 NODE=xw4
VARIANT=xw4
CLASSPATH=${CLASSPATH:-""} CLASSPATH=${CLASSPATH:-""}
usage() { usage() {
[ $# -gt 0 ] && echo "Error: $1" [ $# -gt 0 ] && echo "Error: $1"
echo "usage: $0 " echo "usage: $0 [--variant xw4|xw4d]"
exit 1 exit 1
} }
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case $1 in case $1 in
--help) usage --help)
;; usage
*) usage "unexpected flag $1" ;;
;; --variant)
VARIANT=$2
shift
;;
*)
usage "unexpected flag $1"
;;
esac esac
shift shift
done done
cd $(dirname $0)/../app/build/intermediates/classes/${NODE}/debug cd $(dirname $0)/../app/build/intermediates/classes/${VARIANT}/debug
javah -o /tmp/javah$$.txt org.eehouse.android.${NODE}.jni.XwJNI javah -o /tmp/javah$$.txt org.eehouse.android.${NODE}.jni.XwJNI
javap -s org.eehouse.android.${NODE}.jni.XwJNI javap -s org.eehouse.android.${NODE}.jni.XwJNI

View file

@ -36,10 +36,11 @@ echo "# Generated by $0; do not edit!!!" > $TMP_MK
[ -n "$USE_CLANG" ] && echo "NDK_TOOLCHAIN_VERSION := clang" >> $TMP_MK [ -n "$USE_CLANG" ] && echo "NDK_TOOLCHAIN_VERSION := clang" >> $TMP_MK
# TODO: reserach whether armeabi-v7a is better here
if [ -n "$XWORDS_DEBUG_ARMONLY" ]; then if [ -n "$XWORDS_DEBUG_ARMONLY" ]; then
echo "APP_ABI := armeabi-v7a" >> $TMP_MK echo "APP_ABI := armeabi" >> $TMP_MK
elif [ -n "$XWORDS_DEBUG_X86ONLY" ]; then elif [ -n "$XWORDS_DEBUG_X86ONLY" ]; then
echo "APP_ABI := x86" >> $TMP_MK echo "APP_ABI := x86" >> $TMP_MK
else else
echo "APP_ABI := armeabi x86" >> $TMP_MK echo "APP_ABI := armeabi x86" >> $TMP_MK
fi fi

View file

@ -77,6 +77,7 @@ void nli_makeAddrRec( const NetLaunchInfo* invit, CommsAddrRec* addr );
void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID ); void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID ); void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
void nli_setGameName( NetLaunchInfo* invit, const XP_UCHAR* gameName );
#endif #endif

View file

@ -79,6 +79,7 @@ typedef struct ServerVolatiles {
GameOverListener gameOverListener; GameOverListener gameOverListener;
void* gameOverData; void* gameOverData;
XP_Bool showPrevMove; XP_Bool showPrevMove;
XP_Bool pickTilesCalled[MAX_NUM_PLAYERS];
} ServerVolatiles; } ServerVolatiles;
typedef struct ServerNonvolatiles { typedef struct ServerNonvolatiles {
@ -1062,6 +1063,8 @@ server_tilesPicked( ServerCtxt* server, XP_U16 player,
const TrayTileSet* newTilesP ) const TrayTileSet* newTilesP )
{ {
XP_ASSERT( 0 == model_getNumTilesInTray( server->vol.model, player ) ); XP_ASSERT( 0 == model_getNumTilesInTray( server->vol.model, player ) );
XP_ASSERT( server->vol.pickTilesCalled[player] );
server->vol.pickTilesCalled[player] = XP_FALSE;
TrayTileSet newTiles = *newTilesP; TrayTileSet newTiles = *newTilesP;
pool_removeTiles( server->pool, &newTiles ); pool_removeTiles( server->pool, &newTiles );
@ -1089,12 +1092,19 @@ informNeedPickTiles( ServerCtxt* server, XP_Bool initial, XP_U16 turn,
XP_Bool asking = nToPick > 0; XP_Bool asking = nToPick > 0;
if ( asking ) { if ( asking ) {
for ( Tile tile = 0; tile < nFaces; ++tile ) { /* We need to make sure we only call util_informNeedPickTiles once
faces[tile] = dict_getTileString( dict, tile ); without it returning. Even if server_do() is called a lot. */
counts[tile] = pool_getNTilesLeftFor( server->pool, tile ); if ( server->vol.pickTilesCalled[turn] ) {
XP_LOGF( "%s(): already asking for %d", __func__, turn );
} else {
server->vol.pickTilesCalled[turn] = XP_TRUE;
for ( Tile tile = 0; tile < nFaces; ++tile ) {
faces[tile] = dict_getTileString( dict, tile );
counts[tile] = pool_getNTilesLeftFor( server->pool, tile );
}
util_informNeedPickTiles( server->vol.util, initial, turn,
nToPick, nFaces, faces, counts );
} }
util_informNeedPickTiles( server->vol.util, initial, turn,
nToPick, nFaces, faces, counts );
} }
return asking; return asking;
} }
@ -1923,6 +1933,7 @@ makePoolOnce( ServerCtxt* server )
static XP_Bool static XP_Bool
assignTilesToAll( ServerCtxt* server ) assignTilesToAll( ServerCtxt* server )
{ {
LOG_FUNC();
XP_Bool allDone = XP_TRUE; XP_Bool allDone = XP_TRUE;
XP_U16 numAssigned; XP_U16 numAssigned;
XP_U16 ii; XP_U16 ii;
@ -1962,6 +1973,7 @@ assignTilesToAll( ServerCtxt* server )
} }
sortTilesIf( server, ii ); sortTilesIf( server, ii );
} }
LOG_RETURNF( "%d", allDone );
return allDone; return allDone;
} /* assignTilesToAll */ } /* assignTilesToAll */
@ -2466,6 +2478,7 @@ server_commitMove( ServerCtxt* server, TrayTileSet* newTilesP )
XP_ASSERT( turn >= 0 ); XP_ASSERT( turn >= 0 );
pool_removeTiles( server->pool, &newTiles ); pool_removeTiles( server->pool, &newTiles );
server->vol.pickTilesCalled[turn] = XP_FALSE;
nTilesMoved = model_getCurrentMoveCount( model, turn ); nTilesMoved = model_getCurrentMoveCount( model, turn );
fetchTiles( server, turn, nTilesMoved, NULL, &newTiles ); fetchTiles( server, turn, nTilesMoved, NULL, &newTiles );

View file

@ -31,6 +31,10 @@
/* PENDING: Might want to make SEND_NOW_SIZE smaller; might as well send now /* 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. */ if even the smallest new message is likely to put us over. */
# define SEND_NOW_SIZE MAX_LEN_BINARY # define SEND_NOW_SIZE MAX_LEN_BINARY
/* To match the SMSService format */
# define SMS_PROTO_VERSION_JAVA 1
# define SMS_PROTO_VERSION 1 # define SMS_PROTO_VERSION 1
# define SMS_PROTO_VERSION_COMBO 2 # define SMS_PROTO_VERSION_COMBO 2
@ -38,7 +42,7 @@
typedef struct _MsgRec { typedef struct _MsgRec {
XP_U32 createSeconds; XP_U32 createSeconds;
SMSMsg msg; SMSMsgNet msgNet;
} MsgRec; } MsgRec;
typedef struct _ToPhoneRec { typedef struct _ToPhoneRec {
@ -82,15 +86,18 @@ struct SMSProto {
#define KEY_NEXTID PERSIST_KEY("nextID") #define KEY_NEXTID PERSIST_KEY("nextID")
static int nextMsgID( SMSProto* state ); static int nextMsgID( SMSProto* state );
static SMSMsgArray* toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld ); static XWStreamCtxt* mkStream( SMSProto* state );
static SMSMsgArray* toNetMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld );
static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone, static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone,
XP_Bool create ); XP_Bool create );
static void addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf, static void addToOutRec( SMSProto* state, ToPhoneRec* rec, SMS_CMD cmd,
XP_U16 buflen, XP_U32 nowSeconds ); 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, static void addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
int indx, int count, const XP_U8* data, XP_U16 len ); int indx, int count, const XP_U8* data, XP_U16 len );
static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr, static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr,
const XP_UCHAR* fromPhone, int msgID ); const XP_UCHAR* fromPhone, XP_U16 wantPort,
int msgID );
static void savePartials( SMSProto* state ); static void savePartials( SMSProto* state );
static void restorePartials( SMSProto* state ); static void restorePartials( SMSProto* state );
static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex ); static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex );
@ -149,6 +156,48 @@ smsproto_free( SMSProto* 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 /* 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 * 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 * least some longer time since the oldest message was added, return an array
@ -159,26 +208,27 @@ smsproto_free( SMSProto* state )
* UtilCtxt around. * UtilCtxt around.
*/ */
SMSMsgArray* SMSMsgArray*
smsproto_prepOutbound( SMSProto* state, const XP_U8* buf, smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
XP_U16 buflen, const XP_UCHAR* toPhone, const void* buf, XP_U16 buflen, const XP_UCHAR* toPhone,
XP_Bool forceOld, XP_U16* waitSecsP ) int toPort, XP_Bool forceOld, XP_U16* waitSecsP )
{ {
XP_USE( toPort );
SMSMsgArray* result = NULL; SMSMsgArray* result = NULL;
#ifdef DEBUG #ifdef DEBUG
XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen ); XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen );
XP_LOGF( "%s(): len=%d, sum=%s, toPhone=%s", __func__, buflen, XP_LOGF( "%s(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", __func__, cmd,
checksum, toPhone ); gameID, buflen, checksum, toPhone );
XP_FREEP( state->mpool, &checksum ); XP_FREEP( state->mpool, &checksum );
#endif #endif
checkThread( state ); checkThread( state );
ToPhoneRec* rec = getForPhone( state, toPhone, !!buf ); ToPhoneRec* rec = getForPhone( state, toPhone, cmd != NONE );
/* First, add the new message (if present) to the array */ /* First, add the new message (if present) to the array */
XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil ); XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil );
if ( !!buf ) { if ( cmd != NONE ) {
addToRec( state, rec, buf, buflen, nowSeconds ); addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds );
} }
/* rec will be non-null if there's something in it */ /* rec will be non-null if there's something in it */
@ -191,7 +241,7 @@ smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
} }
if ( doSend ) { if ( doSend ) {
result = toMsgs( state, rec, forceOld ); result = toNetMsgs( state, rec, forceOld );
freeForPhone( state, toPhone ); freeForPhone( state, toPhone );
} }
@ -207,27 +257,45 @@ smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
} }
static SMSMsgArray* static SMSMsgArray*
appendMsg( SMSProto* state, SMSMsgArray* arr, SMSMsg* msg ) appendLocMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgLoc* msg )
{ {
if ( NULL == arr ) { if ( NULL == arr ) {
arr = XP_CALLOC( state->mpool, sizeof(*arr) ); arr = XP_CALLOC( state->mpool, sizeof(*arr) );
arr->format = FORMAT_LOC;
} else {
XP_ASSERT( arr->format == FORMAT_LOC );
} }
arr->msgs = XP_REALLOC( state->mpool, arr->msgs, arr->u.msgsLoc = XP_REALLOC( state->mpool, arr->u.msgsLoc,
(arr->nMsgs + 1) * sizeof(*arr->msgs) ); (arr->nMsgs + 1) * sizeof(*arr->u.msgsLoc) );
arr->msgs[arr->nMsgs++] = *msg; arr->u.msgsLoc[arr->nMsgs++] = *msg;
return arr;
}
static SMSMsgArray*
appendNetMsg( SMSProto* 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; return arr;
} }
SMSMsgArray* SMSMsgArray*
smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
const XP_U8* data, XP_U16 len ) XP_U16 wantPort, const XP_U8* data, XP_U16 len )
{ {
XP_LOGF( "%s(): len=%d, fromPhone=%s", __func__, len, fromPhone ); XP_LOGF( "%s(): len=%d, fromPhone=%s", __func__, len, fromPhone );
checkThread( state ); checkThread( state );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool) XWStreamCtxt* stream = mkStream( state );
dutil_getVTManager(state->dutil) );
stream_putBytes( stream, data, len ); stream_putBytes( stream, data, len );
SMSMsgArray* result = NULL; SMSMsgArray* result = NULL;
@ -244,7 +312,7 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
XP_U8 buf[len]; XP_U8 buf[len];
stream_getBytes( stream, buf, len ); stream_getBytes( stream, buf, len );
addMessage( state, fromPhone, msgID, indx, count, buf, len ); addMessage( state, fromPhone, msgID, indx, count, buf, len );
result = completeMsgs( state, result, fromPhone, msgID ); result = completeMsgs( state, result, fromPhone, wantPort, msgID );
savePartials( state ); savePartials( state );
} }
} }
@ -253,19 +321,39 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
XP_U8 oneLen, msgID; XP_U8 oneLen, msgID;
while ( stream_gotU8( stream, &oneLen ) while ( stream_gotU8( stream, &oneLen )
&& stream_gotU8( stream, &msgID ) ) { && stream_gotU8( stream, &msgID ) ) {
XP_U8 buf[oneLen]; XP_U8 tmp[oneLen];
if ( stream_gotBytes( stream, buf, oneLen ) ) { stream_getBytes( stream, tmp, oneLen );
SMSMsg msg = { .len = oneLen,
.msgID = msgID, XWStreamCtxt* msgStream = mkStream( state );
.data = XP_MALLOC( state->mpool, oneLen ), stream_putBytes( msgStream, tmp, oneLen );
};
XP_MEMCPY( msg.data, buf, oneLen ); XP_U32 gameID;
result = appendMsg( state, result, &msg ); 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 );
}
}
} }
stream_destroy( msgStream );
} }
} }
break; break;
default: default:
/* Don't assert! happens all the time */
XP_LOGF( "%s(): unexpected proto %d", __func__, proto ); XP_LOGF( "%s(): unexpected proto %d", __func__, proto );
break; break;
} }
@ -283,17 +371,31 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
checkThread( state ); checkThread( state );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) { for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
XP_FREEP( state->mpool, &arr->msgs[ii].data ); XP_U8** ptr = arr->format == FORMAT_LOC
? &arr->u.msgsLoc[ii].data : &arr->u.msgsNet[ii].data;
XP_FREEP( state->mpool, ptr );
} }
XP_FREEP( state->mpool, &arr->msgs ); 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 ); XP_FREEP( state->mpool, &arr );
} }
static void static void
freeMsg( SMSProto* state, MsgRec** msgp ) freeMsg( SMSProto* state, MsgRec** msgp )
{ {
XP_FREEP( state->mpool, &(*msgp)->msg.data ); XP_FREEP( state->mpool, &(*msgp)->msgNet.data );
XP_FREEP( state->mpool, msgp ); XP_FREEP( state->mpool, msgp );
} }
@ -353,20 +455,28 @@ freeForPhone( SMSProto* state, const XP_UCHAR* phone )
} }
static void static void
addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf, XP_U16 buflen, addToOutRec( SMSProto* state, ToPhoneRec* rec, SMS_CMD cmd,
XP_U32 nowSeconds ) 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) ); MsgRec* mRec = XP_CALLOC( state->mpool, sizeof(*rec) );
mRec->msg.len = buflen; XP_U16 len = stream_getSize( stream );
mRec->msg.data = XP_MALLOC( state->mpool, buflen ); mRec->msgNet.len = len;
XP_MEMCPY( mRec->msg.data, buf, buflen ); mRec->msgNet.data = XP_MALLOC( state->mpool, len );
XP_MEMCPY( mRec->msgNet.data, stream_getPtr(stream), len );
stream_destroy( stream );
mRec->createSeconds = nowSeconds; mRec->createSeconds = nowSeconds;
rec->msgs = XP_REALLOC( state->mpool, rec->msgs, (1 + rec->nMsgs) * sizeof(*rec->msgs) ); rec->msgs = XP_REALLOC( state->mpool, rec->msgs, (1 + rec->nMsgs) * sizeof(*rec->msgs) );
rec->msgs[rec->nMsgs++] = mRec; rec->msgs[rec->nMsgs++] = mRec;
rec->totalSize += buflen; rec->totalSize += len;
XP_LOGF( "%s(): added msg to %s of len %d; total now %d", __func__, rec->phone, XP_LOGF( "%s(): added msg to %s of len %d; total now %d", __func__, rec->phone,
buflen, rec->totalSize ); len, rec->totalSize );
if ( rec->nMsgs == 1 ) { if ( rec->nMsgs == 1 ) {
rec->createSeconds = nowSeconds; rec->createSeconds = nowSeconds;
@ -513,9 +623,7 @@ savePartials( SMSProto* state )
{ {
checkThread( state ); checkThread( state );
XWStreamCtxt* stream XWStreamCtxt* stream = mkStream( state );
= mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
stream_putU8( stream, PARTIALS_FORMAT ); stream_putU8( stream, PARTIALS_FORMAT );
stream_putU8( stream, state->nFromPhones ); stream_putU8( stream, state->nFromPhones );
@ -553,8 +661,8 @@ savePartials( SMSProto* state )
static void static void
restorePartials( SMSProto* state ) restorePartials( SMSProto* state )
{ {
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool) XWStreamCtxt* stream = mkStream( state );
dutil_getVTManager(state->dutil) );
dutil_loadStream( state->dutil, KEY_PARTIALS, stream ); dutil_loadStream( state->dutil, KEY_PARTIALS, stream );
if ( stream_getSize( stream ) >= 1 if ( stream_getSize( stream ) >= 1
&& PARTIALS_FORMAT == stream_getU8( stream ) ) { && PARTIALS_FORMAT == stream_getU8( stream ) ) {
@ -585,7 +693,7 @@ restorePartials( SMSProto* state )
static SMSMsgArray* static SMSMsgArray*
completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone, completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
int msgID ) XP_U16 wantPort, int msgID )
{ {
int fromPhoneIndex, msgIDIndex; int fromPhoneIndex, msgIDIndex;
MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE, MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE,
@ -607,16 +715,30 @@ completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
} }
if ( haveAll ) { if ( haveAll ) {
SMSMsg msg = { .len = len, XWStreamCtxt* stream = mkStream( state );
.msgID = msgID,
.data = XP_MALLOC( state->mpool, len ),
};
XP_U8* ptr = msg.data;
for ( int ii = 0; ii < rec->count; ++ii ) { for ( int ii = 0; ii < rec->count; ++ii ) {
XP_MEMCPY( ptr, rec->parts[ii].data, rec->parts[ii].len ); stream_putBytes( stream, rec->parts[ii].data, rec->parts[ii].len );
ptr += rec->parts[ii].len;
} }
arr = appendMsg( state, arr, &msg );
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_LOGF( "%s(): expected port %d, got %d", __func__,
wantPort, port );
XP_FREEP( state->mpool, &msg.data );
}
}
stream_destroy( stream );
freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex ); freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex );
} }
@ -625,13 +747,13 @@ completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
} }
static SMSMsgArray* static SMSMsgArray*
toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld ) toNetMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
{ {
SMSMsgArray* result = NULL; SMSMsgArray* result = NULL;
for ( XP_U16 ii = 0; ii < rec->nMsgs; ) { for ( XP_U16 ii = 0; ii < rec->nMsgs; ) {
// XP_LOGF( "%s(): looking at msg %d of %d", __func__, ii, rec->nMsgs ); // XP_LOGF( "%s(): looking at msg %d of %d", __func__, ii, rec->nMsgs );
XP_U16 count = (rec->msgs[ii]->msg.len + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY; 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 /* First, see if this message and some number of its neighbors can be
combined */ combined */
@ -639,7 +761,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
int sum = 0; int sum = 0;
if ( count == 1 && !forceOld ) { if ( count == 1 && !forceOld ) {
for ( ; last < rec->nMsgs; ++last ) { for ( ; last < rec->nMsgs; ++last ) {
int nextLen = rec->msgs[last]->msg.len; int nextLen = rec->msgs[last]->msgNet.len;
if ( sum + nextLen > MAX_LEN_BINARY ) { if ( sum + nextLen > MAX_LEN_BINARY ) {
break; break;
} }
@ -654,23 +776,23 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
last - 1, nMsgs ); last - 1, nMsgs );
} }
int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */ int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */
SMSMsg newMsg = { .len = len, SMSMsgNet newMsg = { .len = len,
.data = XP_MALLOC( state->mpool, len ) .data = XP_MALLOC( state->mpool, len )
}; };
int indx = 0; int indx = 0;
newMsg.data[indx++] = SMS_PROTO_VERSION_COMBO; newMsg.data[indx++] = SMS_PROTO_VERSION_COMBO;
for ( int jj = ii; jj < last; ++jj ) { for ( int jj = ii; jj < last; ++jj ) {
const SMSMsg* msg = &rec->msgs[jj]->msg; const SMSMsgNet* msg = &rec->msgs[jj]->msgNet;
newMsg.data[indx++] = msg->len; newMsg.data[indx++] = msg->len;
newMsg.data[indx++] = nextMsgID( state ); newMsg.data[indx++] = nextMsgID( state );
XP_MEMCPY( &newMsg.data[indx], msg->data, msg->len ); /* bad! */ XP_MEMCPY( &newMsg.data[indx], msg->data, msg->len ); /* bad! */
indx += msg->len; indx += msg->len;
} }
result = appendMsg( state, result, &newMsg ); result = appendNetMsg( state, result, &newMsg );
ii = last; ii = last;
} else { } else {
int msgID = nextMsgID( state ); int msgID = nextMsgID( state );
const SMSMsg* msg = &rec->msgs[ii]->msg; const SMSMsgNet* msg = &rec->msgs[ii]->msgNet;
XP_U8* nextStart = msg->data; XP_U8* nextStart = msg->data;
XP_U16 lenLeft = msg->len; XP_U16 lenLeft = msg->len;
for ( XP_U16 indx = 0; indx < count; ++indx ) { for ( XP_U16 indx = 0; indx < count; ++indx ) {
@ -681,8 +803,8 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
} }
lenLeft -= useLen; lenLeft -= useLen;
SMSMsg newMsg = { .len = useLen + 4, SMSMsgNet newMsg = { .len = useLen + 4,
.data = XP_MALLOC( state->mpool, useLen + 4 ) .data = XP_MALLOC( state->mpool, useLen + 4 )
}; };
newMsg.data[0] = SMS_PROTO_VERSION; newMsg.data[0] = SMS_PROTO_VERSION;
newMsg.data[1] = msgID; newMsg.data[1] = msgID;
@ -691,7 +813,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
XP_MEMCPY( newMsg.data + 4, nextStart, useLen ); XP_MEMCPY( newMsg.data + 4, nextStart, useLen );
nextStart += useLen; nextStart += useLen;
result = appendMsg( state, result, &newMsg ); result = appendNetMsg( state, result, &newMsg );
} }
++ii; ++ii;
} }
@ -710,6 +832,14 @@ nextMsgID( SMSProto* state )
return result; return result;
} }
static XWStreamCtxt*
mkStream( SMSProto* state )
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
return stream;
}
#ifdef DEBUG #ifdef DEBUG
static void static void
checkThread( SMSProto* state ) checkThread( SMSProto* state )
@ -745,17 +875,19 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
} }
/* Loop until all the messages are ready. */ /* 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) { for ( XP_Bool firstTime = XP_TRUE; ; firstTime = XP_FALSE) {
XP_Bool allDone = XP_TRUE; XP_Bool allDone = XP_TRUE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) { for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
XP_U16 waitSecs; XP_U16 waitSecs;
if ( firstTime ) { if ( firstTime ) {
XP_U16 len = (ii + 1) * 30; XP_U16 len = (ii + 1) * 30;
arrs[ii] = smsproto_prepOutbound( state, (XP_U8*)buf, len, phones[ii], arrs[ii] = smsproto_prepOutbound( state, DATA, gameID, buf, len, phones[ii],
forceOld, &waitSecs ); port, forceOld, &waitSecs );
} else if ( NULL == arrs[ii]) { } else if ( NULL == arrs[ii]) {
arrs[ii] = smsproto_prepOutbound( state, NULL, 0, phones[ii], arrs[ii] = smsproto_prepOutbound( state, DATA, gameID, NULL, 0, phones[ii],
forceOld, &waitSecs ); port, forceOld, &waitSecs );
} else { } else {
continue; continue;
} }
@ -772,13 +904,17 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
XP_Bool haveOne = XP_FALSE; XP_Bool haveOne = XP_FALSE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) { for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
if (!!arrs[ii] && indx < arrs[ii]->nMsgs) { if (!!arrs[ii] && indx < arrs[ii]->nMsgs) {
XP_ASSERT( arrs[ii]->format == FORMAT_NET );
haveOne = XP_TRUE; haveOne = XP_TRUE;
SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii], SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii], port,
arrs[ii]->msgs[indx].data, arrs[ii]->u.msgsNet[indx].data,
arrs[ii]->msgs[indx].len ); arrs[ii]->u.msgsNet[indx].len );
if ( !!outArr ) { if ( !!outArr ) {
SMSMsg* msg = &outArr->msgs[0]; XP_ASSERT( outArr->format == FORMAT_LOC );
XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID ); 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( outArr->nMsgs == 1 );
XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) ); XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) );
smsproto_freeMsgArray( state, outArr ); smsproto_freeMsgArray( state, outArr );
@ -796,8 +932,8 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
/* Now let's send a bunch of small messages that should get combined */ /* Now let's send a bunch of small messages that should get combined */
for ( int nUsed = 0; ; ++nUsed ) { for ( int nUsed = 0; ; ++nUsed ) {
XP_U16 waitSecs; XP_U16 waitSecs;
SMSMsgArray* sendArr = smsproto_prepOutbound( state, (XP_U8*)&buf[nUsed], SMSMsgArray* sendArr = smsproto_prepOutbound( state, DATA, gameID, &buf[nUsed],
smallSiz, phones[0], smallSiz, phones[0], port,
XP_FALSE, &waitSecs ); XP_FALSE, &waitSecs );
if ( sendArr == NULL ) { if ( sendArr == NULL ) {
XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz ); XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz );
@ -805,17 +941,21 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
} }
XP_ASSERT( waitSecs == 0 ); XP_ASSERT( waitSecs == 0 );
XP_ASSERT( sendArr->format == FORMAT_NET );
int totalBack = 0; int totalBack = 0;
for ( int jj = 0; jj < sendArr->nMsgs; ++jj ) { for ( int jj = 0; jj < sendArr->nMsgs; ++jj ) {
SMSMsgArray* recvArr = smsproto_prepInbound( state, phones[0], SMSMsgArray* recvArr = smsproto_prepInbound( state, phones[0], port,
sendArr->msgs[jj].data, sendArr->u.msgsNet[jj].data,
sendArr->msgs[jj].len ); sendArr->u.msgsNet[jj].len );
if ( !!recvArr ) { if ( !!recvArr ) {
XP_ASSERT( recvArr->format == FORMAT_LOC );
XP_LOGF( "%s(): got %d msgs (from %d)", __func__, recvArr->nMsgs, nUsed + 1 ); XP_LOGF( "%s(): got %d msgs (from %d)", __func__, recvArr->nMsgs, nUsed + 1 );
for ( int kk = 0; kk < recvArr->nMsgs; ++kk ) { for ( int kk = 0; kk < recvArr->nMsgs; ++kk ) {
SMSMsg* msg = &recvArr->msgs[kk]; SMSMsgLoc* msg = &recvArr->u.msgsLoc[kk];
XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID ); // 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( msg->len == smallSiz );
XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) ); XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) );
++totalBack; ++totalBack;
@ -833,25 +973,55 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
/* Now let's add a too-long message and unpack only the first part. Make /* Now let's add a too-long message and unpack only the first part. Make
sure it's cleaned up correctly */ sure it's cleaned up correctly */
XP_U16 waitSecs; XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs ); SMSMsgArray* arr = smsproto_prepOutbound( state, DATA, gameID, buf, 200, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( !!arr && arr->nMsgs > 1 ); XP_ASSERT( !!arr && arr->nMsgs > 1 );
/* add only part 1 */ /* add only part 1 */
SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[0].data, arr->msgs[0].len ); SMSMsgArray* out = smsproto_prepInbound( state, "33333", port, arr->u.msgsNet[0].data,
arr->u.msgsNet[0].len );
XP_ASSERT( !out ); XP_ASSERT( !out );
smsproto_freeMsgArray( state, arr ); smsproto_freeMsgArray( state, arr );
/* Try the no-buffer messages */
XP_LOGF( "%s(): trying DEATH", __func__ );
arr = smsproto_prepOutbound( state, DEATH, gameID, NULL, 0, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( arr->format == FORMAT_NET );
out = smsproto_prepInbound( state, "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, DEATH, gameID, NULL, 0, "33333",
port, XP_TRUE, &waitSecs );
XP_ASSERT( arr->format == FORMAT_NET );
out = smsproto_prepInbound( state, "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 */ /* now a message that's unpacked across multiple sessions to test store/load */
XP_LOGF( "%s(): testing store/restore", __func__ ); XP_LOGF( "%s(): testing store/restore", __func__ );
arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs ); arr = smsproto_prepOutbound( state, DATA, gameID, (XP_U8*)buf, 200, "33333",
port, XP_TRUE, &waitSecs );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) { for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data, SMSMsgArray* out = smsproto_prepInbound( state, "33333", port,
arr->msgs[ii].len ); arr->u.msgsNet[ii].data,
arr->u.msgsNet[ii].len );
if ( !!out ) { if ( !!out ) {
XP_ASSERT( out->nMsgs == 1); XP_ASSERT( out->nMsgs == 1);
XP_ASSERT( out->format == FORMAT_LOC );
XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii ); XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii );
XP_ASSERT( out->msgs[0].len == 200 ); XP_ASSERT( out->u.msgsLoc[0].len == 200 );
XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) ); XP_ASSERT( 0 == memcmp( out->u.msgsLoc[0].data, buf, 200 ) );
smsproto_freeMsgArray( state, out ); smsproto_freeMsgArray( state, out );
break; break;
} }

View file

@ -44,23 +44,44 @@
#include "xptypes.h" #include "xptypes.h"
#include "mempool.h" /* debug only */ #include "mempool.h" /* debug only */
#include "nli.h"
typedef struct SMSProto SMSProto; typedef struct SMSProto SMSProto;
typedef struct _SMSMsg { typedef enum { NONE, INVITE, DATA, DEATH, ACK_INVITE, } SMS_CMD;
/* Unpacked/local format with relevant fields exposed */
typedef struct _SMSMsgLoc {
// XP_U16 msgID;
SMS_CMD cmd;
XP_U32 gameID;
XP_U16 len;
XP_U8* data; // will be NetLaunchInfo* if cmd == INVITE
} SMSMsgLoc;
/* Flattened format suitable for transmission over wire. Encapsulates Loc
format data */
typedef struct _SMSMsgNet {
// XP_U16 msgID;
XP_U16 len; XP_U16 len;
XP_U16 msgID;
XP_U8* data; XP_U8* data;
} SMSMsg; } SMSMsgNet;
typedef enum { FORMAT_NONE, FORMAT_LOC, FORMAT_NET } SMS_FORMAT;
typedef struct _SMSMsgArray { typedef struct _SMSMsgArray {
XP_U16 nMsgs; XP_U16 nMsgs;
SMSMsg* msgs; SMS_FORMAT format;
union {
SMSMsgNet* msgsNet;
SMSMsgLoc* msgsLoc;
} u;
} SMSMsgArray; } SMSMsgArray;
struct SMSProto* smsproto_init( MPFORMAL XW_DUtilCtxt* dutil ); struct SMSProto* smsproto_init( MPFORMAL XW_DUtilCtxt* dutil );
void smsproto_free( SMSProto* state ); void smsproto_free( SMSProto* state );
/* Return ptr to structure if one's ready to be sent, otherwise null. Caller * /* Return ptr to structure if one's ready to be sent, otherwise null. Caller *
* should interpret null as meaning it's meant to call again. To support that, * should interpret null as meaning it's meant to call again. To support that,
* null buf is legit. * null buf is legit.
@ -68,14 +89,16 @@ void smsproto_free( SMSProto* state );
* When send() returns a non-null value, that value must be passed to * When send() returns a non-null value, that value must be passed to
* freeMsgArray() or there will be leakage. * freeMsgArray() or there will be leakage.
*/ */
SMSMsgArray* smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* toPhone, SMSMsgArray* smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd,
XP_U32 gameID, const void* buf, XP_U16 buflen,
const XP_UCHAR* toPhone, int port,
XP_Bool forceOld, XP_U16* waitSecs ); XP_Bool forceOld, XP_U16* waitSecs );
/* When a message is received, pass it in for reassambly. Non-null return /* When a message is received, pass it in for reassambly. Non-null return
means one or more messages is ready for consumption. */ means one or more messages is ready for consumption. */
SMSMsgArray* smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, SMSMsgArray* smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
const XP_U8* data, XP_U16 len ); XP_U16 wantPort, const XP_U8* data, XP_U16 len );
void smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr ); void smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr );

View file

@ -189,6 +189,26 @@ stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr )
return success; return success;
} }
XP_Bool
stream_gotU16( XWStreamCtxt* stream, XP_U16* ptr )
{
XP_Bool success = sizeof(*ptr) <= stream_getSize( stream );
if ( success ) {
*ptr = stream_getU16( stream );
}
return success;
}
XP_Bool
stream_gotU32( XWStreamCtxt* stream, XP_U32* ptr )
{
XP_Bool success = sizeof(*ptr) <= stream_getSize( stream );
if ( success ) {
*ptr = stream_getU32( stream );
}
return success;
}
XP_Bool XP_Bool
stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len ) stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len )
{ {

View file

@ -57,6 +57,8 @@ XP_U16 stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 len );
void stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str ); void stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str );
XP_Bool stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr ); XP_Bool stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr );
XP_Bool stream_gotU32( XWStreamCtxt* stream, XP_U32* ptr );
XP_Bool stream_gotU16( XWStreamCtxt* stream, XP_U16* ptr );
XP_Bool stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len ); XP_Bool stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len );
XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr

View file

@ -1659,8 +1659,7 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
gchar gameName[64]; gchar gameName[64];
snprintf( gameName, VSIZE(gameName), "Game %d", cGlobals->gi->gameID ); snprintf( gameName, VSIZE(gameName), "Game %d", cGlobals->gi->gameID );
linux_sms_invite( cGlobals->params, cGlobals->gi, &addr, gameName, linux_sms_invite( cGlobals->params, &nli,
nPlayers, forceChannel,
addrs->u.sms.phone, addrs->u.sms.port ); addrs->u.sms.phone, addrs->u.sms.port );
} }
if ( 0 != devID || !!relayID ) { if ( 0 != devID || !!relayID ) {

View file

@ -746,26 +746,29 @@ gtkNoticeRcvd( void* closure )
} }
static void static void
smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), /* smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), */
XP_U32 gameID, XP_U16 dictLang, const XP_UCHAR* dictName, /* XP_U32 gameID, XP_U16 dictLang, const XP_UCHAR* dictName, */
XP_U16 nPlayers, XP_U16 nHere, XP_U16 forceChannel, /* XP_U16 nPlayers, XP_U16 nHere, XP_U16 forceChannel, */
/* const CommsAddrRec* returnAddr ) */
smsInviteReceived( void* closure, const NetLaunchInfo* nli,
const CommsAddrRec* returnAddr ) const CommsAddrRec* returnAddr )
{ {
GtkAppGlobals* apg = (GtkAppGlobals*)closure; GtkAppGlobals* apg = (GtkAppGlobals*)closure;
LaunchParams* params = apg->params; LaunchParams* params = apg->params;
XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, " XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, "
"nHere=%d, forceChannel=%d)", __func__, gameName, gameID, dictName, "nHere=%d, forceChannel=%d)", __func__, nli->gameName,
nPlayers, nHere, forceChannel ); nli->gameID, nli->dict, nli->nPlayersT,
nli->nPlayersH, nli->forceChannel );
CurGameInfo gi = {0}; CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, &params->pgi ); gi_copy( MPPARM(params->mpool) &gi, &params->pgi );
gi_setNPlayers( &gi, nPlayers, nHere ); gi_setNPlayers( &gi, nli->nPlayersT, nli->nPlayersH );
gi.gameID = gameID; gi.gameID = nli->gameID;
gi.dictLang = dictLang; gi.dictLang = nli->lang;
gi.forceChannel = forceChannel; gi.forceChannel = nli->forceChannel;
gi.serverRole = SERVER_ISCLIENT; /* recipient of invitation is client */ gi.serverRole = SERVER_ISCLIENT; /* recipient of invitation is client */
replaceStringIfDifferent( params->mpool, &gi.dictName, dictName ); replaceStringIfDifferent( params->mpool, &gi.dictName, nli->dict );
GtkGameGlobals* globals = malloc( sizeof(*globals) ); GtkGameGlobals* globals = malloc( sizeof(*globals) );
params->needsNewGame = XP_FALSE; params->needsNewGame = XP_FALSE;

View file

@ -68,12 +68,10 @@ typedef struct _LinSMSData {
SMSProto* protoState; SMSProto* protoState;
} LinSMSData; } LinSMSData;
static void doSend( LaunchParams* params, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID );
static gboolean retrySend( gpointer data ); static gboolean retrySend( gpointer data );
static void sendOrRetry( LaunchParams* params, SMSMsgArray* arr, XP_U16 waitSecs, static void sendOrRetry( LaunchParams* params, SMSMsgArray* arr, SMS_CMD cmd,
const XP_UCHAR* phone, XP_U16 port, XP_U32 gameID ); XP_U16 waitSecs, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID );
static gint check_for_files( gpointer data ); static gint check_for_files( gpointer data );
static gint check_for_files_once( gpointer data ); static gint check_for_files_once( gpointer data );
@ -85,13 +83,7 @@ formatQueuePath( const XP_UCHAR* phone, XP_U16 port, XP_UCHAR* path,
snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port ); snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port );
} }
typedef enum { NONE, INVITE, DATA, DEATH, ACK, } SMS_CMD;
#define SMS_PROTO_VERSION 0
static LinSMSData* getStorage( LaunchParams* params ); static LinSMSData* getStorage( LaunchParams* params );
static void writeHeader( XWStreamCtxt* stream, SMS_CMD cmd );
static void static void
lock_queue( LinSMSData* storage ) lock_queue( LinSMSData* storage )
@ -114,7 +106,7 @@ unlock_queue( LinSMSData* storage )
} }
static XP_S16 static XP_S16
write_fake_sms( LaunchParams* params, XWStreamCtxt* stream, write_fake_sms( LaunchParams* params, const void* buf, XP_U16 buflen,
const XP_UCHAR* phone, XP_U16 port ) const XP_UCHAR* phone, XP_U16 port )
{ {
XP_S16 nSent; XP_S16 nSent;
@ -122,13 +114,11 @@ write_fake_sms( LaunchParams* params, XWStreamCtxt* stream,
XP_Bool skipWrite = pct < params->smsSendFailPct; XP_Bool skipWrite = pct < params->smsSendFailPct;
if ( skipWrite ) { if ( skipWrite ) {
nSent = stream_getSize( stream ); nSent = buflen;
XP_LOGF( "%s(): dropping sms msg of len %d to phone %s", __func__, XP_LOGF( "%s(): dropping sms msg of len %d to phone %s", __func__,
nSent, phone ); nSent, phone );
} else { } else {
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
const XP_U8* buf = stream_getPtr( stream );
XP_U16 buflen = stream_getSize( stream );
XP_LOGF( "%s(phone=%s, port=%d, len=%d)", __func__, phone, XP_LOGF( "%s(phone=%s, port=%d, len=%d)", __func__, phone,
port, buflen ); port, buflen );
@ -236,81 +226,38 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
return nRead; return nRead;
} /* decodeAndDelete */ } /* decodeAndDelete */
static void
dispatch_invite( LinSMSData* storage, XP_U16 XP_UNUSED(proto),
XWStreamCtxt* stream, CommsAddrRec* addr )
{
XP_UCHAR gameName[256];
XP_UCHAR dictName[256];
XP_U32 gameID = stream_getU32( stream );
XP_LOGF( "%s: got gameID %d", __func__, gameID );
stringFromStreamHere( stream, gameName, VSIZE(gameName) );
XP_U32 dictLang = stream_getU32( stream );
stringFromStreamHere( stream, dictName, VSIZE(dictName) );
XP_U8 nMissing = stream_getU8( stream );
XP_U8 nPlayers = stream_getU8( stream );
XP_U16 forceChannel = stream_getU8( stream );
addrFromStream( addr, stream );
(*storage->procs->inviteReceived)( storage->procClosure, gameName,
gameID, dictLang, dictName, nPlayers,
nMissing, forceChannel, addr );
}
static void
dispatch_data( LinSMSData* storage, XP_U16 XP_UNUSED(proto),
XWStreamCtxt* stream, const CommsAddrRec* addr )
{
LOG_FUNC();
XP_U32 gameID = stream_getU32( stream );
XP_U16 len = stream_getSize( stream );
XP_U8 data[len];
stream_getBytes( stream, data, len );
const XP_UCHAR* fromPhone = addr->u.sms.phone;
SMSMsgArray* arr = smsproto_prepInbound( storage->protoState, fromPhone,
data, len );
if ( NULL != arr ) {
for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsg* msg = &arr->msgs[ii];
(*storage->procs->msgReceived)( storage->procClosure, addr, gameID,
msg->data, msg->len );
}
smsproto_freeMsgArray( storage->protoState, arr );
}
}
static void static void
parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
CommsAddrRec* addr ) CommsAddrRec* addr )
{ {
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) const XP_UCHAR* fromPhone = addr->u.sms.phone;
params->vtMgr ); SMSMsgArray* arr = smsproto_prepInbound( storage->protoState, fromPhone,
stream_setVersion( stream, CUR_STREAM_VERS ); storage->myPort, buf, len );
stream_putBytes( stream, buf, len ); if ( NULL != arr ) {
XP_ASSERT( arr->format == FORMAT_LOC );
XP_U8 proto = stream_getU8( stream ); for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) {
XP_ASSERT( SMS_PROTO_VERSION == proto ); SMSMsgLoc* msg = &arr->u.msgsLoc[ii];
XP_U8 cmd = stream_getU8( stream ); switch ( msg->cmd ) {
switch( cmd ) { case DATA:
case INVITE: (*storage->procs->msgReceived)( storage->procClosure, addr,
dispatch_invite( storage, proto, stream, addr ); msg->gameID,
break; msg->data, msg->len );
case DATA: break;
dispatch_data( storage, proto, stream, addr ); case INVITE:
break; (*storage->procs->inviteReceived)( storage->procClosure,
case DEATH: (NetLaunchInfo*)msg->data, addr );
case ACK: /* gameName, */
break; /* gameID, dictLang, dictName, nPlayers, */
default: /* nMissing, forceChannel, addr ); */
XP_ASSERT( 0 ); break;
default:
XP_ASSERT(0); /* implement me!! */
break;
}
}
smsproto_freeMsgArray( storage->protoState, arr );
} }
stream_destroy( stream );
} }
void void
@ -341,28 +288,18 @@ linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort,
} /* linux_sms_init */ } /* linux_sms_init */
void void
linux_sms_invite( LaunchParams* params, const CurGameInfo* gi, linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli,
const CommsAddrRec* addr, const gchar* gameName,
XP_U16 nMissing, int forceChannel,
const gchar* toPhone, int toPort ) const gchar* toPhone, int toPort )
{ {
LOG_FUNC(); LOG_FUNC();
XWStreamCtxt* stream; LinSMSData* storage = getStorage( params );
stream = mem_stream_make_raw( MPPARM(params->mpool) params->vtMgr );
writeHeader( stream, INVITE );
stream_putU32( stream, gi->gameID );
stringToStream( stream, gameName );
stream_putU32( stream, gi->dictLang );
stringToStream( stream, gi->dictName );
stream_putU8( stream, nMissing );
stream_putU8( stream, gi->nPlayers );
stream_putU8( stream, forceChannel );
addrToStream( stream, addr ); XP_U16 waitSecs;
SMSMsgArray* arr
write_fake_sms( params, stream, toPhone, toPort ); = smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, nli,
sizeof(*nli), toPhone, toPort, XP_FALSE,
stream_destroy( stream ); &waitSecs );
sendOrRetry( params, arr, INVITE, waitSecs, toPhone, toPort, nli->gameID );
} }
XP_S16 XP_S16
@ -372,43 +309,32 @@ linux_sms_send( LaunchParams* params, const XP_U8* buf,
{ {
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
XP_U16 waitSecs; XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, buf, buflen, SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, DATA, gameID,
phone, XP_TRUE, &waitSecs ); buf, buflen, phone, port,
sendOrRetry( params, arr, waitSecs, phone, port, gameID ); XP_TRUE, &waitSecs );
sendOrRetry( params, arr, DATA, waitSecs, phone, port, gameID );
return buflen; return buflen;
} }
static void
doSend( LaunchParams* params, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID )
{
XP_LOGF( "%s(len=%d)", __func__, buflen );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
writeHeader( stream, DATA );
stream_putU32( stream, gameID );
stream_putBytes( stream, buf, buflen );
(void)write_fake_sms( params, stream, phone, port );
stream_destroy( stream );
}
typedef struct _RetryClosure { typedef struct _RetryClosure {
LaunchParams* params; LaunchParams* params;
SMS_CMD cmd;
XP_U16 port; XP_U16 port;
XP_U32 gameID; XP_U32 gameID;
XP_UCHAR phone[32]; XP_UCHAR phone[32];
} RetryClosure; } RetryClosure;
static void static void
sendOrRetry( LaunchParams* params, SMSMsgArray* arr, XP_U16 waitSecs, sendOrRetry( LaunchParams* params, SMSMsgArray* arr, SMS_CMD cmd,
const XP_UCHAR* phone, XP_U16 port, XP_U32 gameID ) XP_U16 waitSecs, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID )
{ {
if ( !!arr ) { if ( !!arr ) {
for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) { for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsg* msg = &arr->msgs[ii]; const SMSMsgNet* msg = &arr->u.msgsNet[ii];
doSend( params, msg->data, msg->len, phone, port, gameID ); // doSend( params, msg->data, msg->len, phone, port, gameID );
(void)write_fake_sms( params, msg->data, msg->len,
phone, port );
} }
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
@ -420,6 +346,7 @@ sendOrRetry( LaunchParams* params, SMSMsgArray* arr, XP_U16 waitSecs,
XP_STRCAT( closure->phone, phone ); XP_STRCAT( closure->phone, phone );
closure->port = port; closure->port = port;
closure->gameID = gameID; closure->gameID = gameID;
closure->cmd = cmd;
g_timeout_add_seconds( 5, retrySend, closure ); g_timeout_add_seconds( 5, retrySend, closure );
} }
} }
@ -430,9 +357,11 @@ retrySend( gpointer data )
RetryClosure* closure = (RetryClosure*)data; RetryClosure* closure = (RetryClosure*)data;
LinSMSData* storage = getStorage( closure->params ); LinSMSData* storage = getStorage( closure->params );
XP_U16 waitSecs; XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, NULL, 0, SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, closure->cmd,
closure->phone, XP_TRUE, &waitSecs ); closure->gameID, NULL, 0,
sendOrRetry( closure->params, arr, waitSecs, closure->phone, closure->phone, closure->port,
XP_TRUE, &waitSecs );
sendOrRetry( closure->params, arr, closure->cmd, waitSecs, closure->phone,
closure->port, closure->gameID ); closure->port, closure->gameID );
XP_FREEP( closure->params->mpool, &closure ); XP_FREEP( closure->params->mpool, &closure );
return FALSE; return FALSE;
@ -538,11 +467,4 @@ getStorage( LaunchParams* params )
return storage; return storage;
} }
static void
writeHeader( XWStreamCtxt* stream, SMS_CMD cmd )
{
stream_putU8( stream, SMS_PROTO_VERSION );
stream_putU8( stream, cmd );
}
#endif /* XWFEATURE_SMS */ #endif /* XWFEATURE_SMS */

View file

@ -23,12 +23,10 @@
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
#include "main.h" #include "main.h"
#include "nli.h"
typedef struct _SMSProcs { typedef struct _SMSProcs {
void (*inviteReceived)( void* closure, const XP_UCHAR* gameName, void (*inviteReceived)( void* closure, const NetLaunchInfo* nli,
XP_U32 gameID, XP_U16 dictLang,
const XP_UCHAR* dictName, XP_U16 nPlayers,
XP_U16 nHere, XP_U16 forceChannel,
const CommsAddrRec* returnAddr ); const CommsAddrRec* returnAddr );
void (*msgReceived)( void* closure, const CommsAddrRec* from, XP_U32 gameID, void (*msgReceived)( void* closure, const CommsAddrRec* from, XP_U32 gameID,
const XP_U8* buf, XP_U16 len ); const XP_U8* buf, XP_U16 len );
@ -45,9 +43,7 @@ void linux_sms_init( LaunchParams* params, const gchar* phone,
XP_S16 linux_sms_send( LaunchParams* params, const XP_U8* buf, XP_S16 linux_sms_send( LaunchParams* params, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port, XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID ); XP_U32 gameID );
void linux_sms_invite( LaunchParams* params, const CurGameInfo* info, void linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli,
const CommsAddrRec* addr, const gchar* gameName,
XP_U16 nMissing, int forceChannel,
const gchar* phone, int port ); const gchar* phone, int port );
void linux_sms_cleanup( LaunchParams* params ); void linux_sms_cleanup( LaunchParams* params );