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
- platform-tools
- build-tools-27.0.3
- android-26
- android-23
licenses:
- android-sdk-preview-license-.+
- android-sdk-license-.+

View file

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

View file

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

View file

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

View file

@ -115,7 +115,7 @@ public class BoardDelegate extends DelegateBase
private BoardUtilCtxt m_utils;
private boolean m_gameOver = false;
private JNIThread m_jniThread;
private volatile JNIThread m_jniThread;
private JNIThread m_jniThreadRef;
private JNIThread.GameStateInfo m_gsi;
@ -136,6 +136,7 @@ public class BoardDelegate extends DelegateBase
m_when = when;
m_handle = handle;
}
@Override
public void run() {
m_timers[m_why] = null;
if ( null != m_jniThread ) {
@ -1117,6 +1118,7 @@ public class BoardDelegate extends DelegateBase
case ENABLE_SMS_DO:
post( new Runnable() {
@Override
public void run() {
retrySMSInvites( params );
}
@ -1164,6 +1166,7 @@ public class BoardDelegate extends DelegateBase
case ENABLE_RELAY_DO_OR:
if ( m_dropOnDismiss ) {
postDelayed( new Runnable() {
@Override
public void run() {
askDropRelay();
}
@ -1174,6 +1177,16 @@ public class BoardDelegate extends DelegateBase
finish();
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:
handled = false;
}
@ -1257,17 +1270,20 @@ public class BoardDelegate extends DelegateBase
@SuppressWarnings("fallthrough")
public void eventOccurred( MultiService.MultiEvent event, final Object ... args )
{
boolean doStopProgress = false;
switch( event ) {
case MESSAGE_ACCEPTED:
case MESSAGE_REFUSED:
ConnStatusHandler.
updateStatusIn( m_activity, this, CommsConnType.COMMS_CONN_BT,
MultiService.MultiEvent.MESSAGE_ACCEPTED == event);
doStopProgress = true;
break;
case MESSAGE_NOGAME:
int gameID = (Integer)args[0];
if ( gameID == m_gi.gameID ) {
post( new Runnable() {
@Override
public void run() {
showDialogFragment( DlgID.DLG_DELETED );
}
@ -1285,13 +1301,11 @@ public class BoardDelegate extends DelegateBase
Log.w( TAG, "failed to create game" );
break;
case NEWGAME_DUP_REJECTED:
if ( m_progressShown ) {
m_progressShown = false;
stopProgress(); // in case it's a BT invite
}
doStopProgress = true;
final String msg =
getString( R.string.err_dup_invite_fmt, (String)args[0] );
post( new Runnable() {
@Override
public void run() {
makeOkOnlyBuilder( msg ).show();
}
@ -1311,13 +1325,15 @@ public class BoardDelegate extends DelegateBase
break;
default:
if ( m_progressShown ) {
m_progressShown = false;
stopProgress(); // in case it's a BT invite
}
doStopProgress = true; // in case it's a BT invite
super.eventOccurred( event, args );
break;
}
if ( doStopProgress && m_progressShown ) {
m_progressShown = false;
stopProgress();
}
}
//////////////////////////////////////////////////
@ -1328,6 +1344,7 @@ public class BoardDelegate extends DelegateBase
final boolean allHere, final int nMissing )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
handleConndMessage( room, devOrder, allHere, nMissing ); // from here too
}
@ -1381,6 +1398,7 @@ public class BoardDelegate extends DelegateBase
final int strIDf = strID;
final DlgID dlgIDf = dlgID;
post( new Runnable() {
@Override
public void run() {
showDialogFragment( dlgIDf, R.string.relay_alert,
getString( strIDf ) );
@ -1397,6 +1415,7 @@ public class BoardDelegate extends DelegateBase
{
if ( success ) {
post( new Runnable() {
@Override
public void run() {
setGotGameDict( name );
}
@ -1452,6 +1471,7 @@ public class BoardDelegate extends DelegateBase
public void invalidateParent()
{
runOnUiThread(new Runnable() {
@Override
public void run() {
m_view.invalidate();
}
@ -1462,6 +1482,7 @@ public class BoardDelegate extends DelegateBase
{
final String msg = ConnStatusHandler.getStatusText( m_activity, m_connTypes );
post( new Runnable() {
@Override
public void run() {
if ( null == msg ) {
askNoAddrsDelete();
@ -1627,6 +1648,7 @@ public class BoardDelegate extends DelegateBase
public void requestTime()
{
runOnUiThread( new Runnable() {
@Override
public void run() {
if ( null != m_jniThread ) {
m_jniThread.handleBkgrnd( JNICmd.CMD_DO );
@ -1678,6 +1700,7 @@ public class BoardDelegate extends DelegateBase
if ( 0 != id ) {
final String bonusStr = getString( id );
post( new Runnable() {
@Override
public void run() {
showToast( bonusStr );
}
@ -1696,6 +1719,7 @@ public class BoardDelegate extends DelegateBase
}
final String text = expl;
post( new Runnable() {
@Override
public void run() {
showToast( text );
}
@ -1706,6 +1730,7 @@ public class BoardDelegate extends DelegateBase
public void cellSquareHeld( final String words )
{
post( new Runnable() {
@Override
public void run() {
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
@Override
public void notifyPickTileBlank( int playerNum, int col, int row,
String[] texts )
{
final TilePickAlert.TilePickState tps =
TilePickAlert.TilePickState tps =
new TilePickAlert.TilePickState( playerNum, texts, col, row );
runOnUiThread( new Runnable() {
@Override
public void run() {
show( TilePickAlert.newInstance( Action.BLANK_PICKED, tps ) );
}
} );
startTP( Action.BLANK_PICKED, tps );
}
@Override
@ -1764,15 +1795,10 @@ public class BoardDelegate extends DelegateBase
int playerNum, int nToPick,
String[] texts, int[] counts )
{
final TilePickAlert.TilePickState tps
TilePickAlert.TilePickState tps
= new TilePickAlert.TilePickState( isInitial, playerNum, nToPick,
texts, counts );
runOnUiThread( new Runnable() {
@Override
public void run() {
show( TilePickAlert.newInstance( Action.TRAY_PICKED, tps ) );
}
} );
startTP( Action.TRAY_PICKED, tps );
}
@Override
@ -1787,6 +1813,7 @@ public class BoardDelegate extends DelegateBase
if ( 0 <= newTurn ) {
m_mySIS.nMissing = 0;
post( new Runnable() {
@Override
public void run() {
makeNotAgainBuilder( R.string.not_again_turnchanged,
R.string.key_notagain_turnchanged )
@ -1801,7 +1828,9 @@ public class BoardDelegate extends DelegateBase
@Override
public boolean engineProgressCallback()
{
return ! m_jniThread.busy();
// return true if engine should keep going
JNIThread jnit = m_jniThread;
return jnit != null && !jnit.busy();
}
@Override
@ -1879,6 +1908,7 @@ public class BoardDelegate extends DelegateBase
if ( asToast ) {
final int residf = resid;
runOnUiThread( new Runnable() {
@Override
public void run() {
showToast( residf );
}
@ -2021,6 +2051,7 @@ public class BoardDelegate extends DelegateBase
String fromPlayer, final int tsSeconds )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
DBUtils.appendChatHistory( m_activity, m_rowid, msg,
fromIndx, tsSeconds );
@ -2543,6 +2574,7 @@ public class BoardDelegate extends DelegateBase
if ( keepOn ) {
if ( null == m_screenTimer ) {
m_screenTimer = new Runnable() {
@Override
public void run() {
if ( null != m_view ) {
m_view.setKeepScreenOn( false );

View file

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

View file

@ -1183,7 +1183,9 @@ public class GameUtils {
boolean informNow )
{
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;
GamePtr gamePtr = loadMakeGame( context, lock );

View file

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

View file

@ -149,7 +149,8 @@ public class MultiService {
{
Assert.assertTrue( isMissingDictIntent( intent ) );
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;
}

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
* rights reserved.
* Copyright 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@ -27,8 +27,13 @@ import android.net.Uri;
import android.os.Bundle;
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.Serializable;
import java.util.Iterator;
import org.json.JSONException;
@ -90,7 +95,7 @@ public class NetLaunchInfo implements Serializable {
inviteID = GameUtils.formatGameID( Utils.nextRandomInt() );
}
public NetLaunchInfo( Context context, String data )
private NetLaunchInfo( Context context, String data ) throws JSONException
{
init( context, data );
}
@ -128,6 +133,31 @@ public class NetLaunchInfo implements Serializable {
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 )
{
this();
@ -422,10 +452,9 @@ public class NetLaunchInfo implements Serializable {
return result;
}
private void init( Context context, String data )
private void init( Context context, String data ) throws JSONException
{
CommsConnTypeSet supported = CommsConnTypeSet.getSupported( context );
try {
JSONObject json = new JSONObject( data );
int flags = json.optInt(ADDRS_KEY, -1);
@ -477,10 +506,6 @@ public class NetLaunchInfo implements Serializable {
}
}
} catch ( JSONException jse ) {
Log.ex( TAG, jse );
}
removeUnsupported( supported );
calcValid();
@ -589,6 +614,20 @@ public class NetLaunchInfo implements Serializable {
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 )
{
Assert.fail();

View file

@ -397,7 +397,7 @@ public class RelayService extends XWService
case GOT_INVITE:
int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
NetLaunchInfo nli
= new NetLaunchInfo( this, intent.getStringExtra(NLI_DATA) );
= NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
receiveInvitation( srcDevID, nli );
break;
case SEND:
@ -820,7 +820,7 @@ public class RelayService extends XWService
Log.d( TAG, "sendInvitation(%d->%d/%s [%s])", srcDevID, destDevID,
relayID, nliStr );
NetLaunchInfo nli = new NetLaunchInfo( this, nliStr );
NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, nliStr );
byte[] nliData = XwJNI.nliToStream( nli );
if ( BuildConfig.DEBUG ) {
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.CommsConnType;
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 java.io.ByteArrayInputStream;
@ -75,17 +77,13 @@ public class SMSService extends XWService {
private static final String BUFFER = "BUFFER";
private static final String BINBUFFER = "BINBUFFER";
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 =
SMSService.class.getName() + "_PHONES";
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_receiveReceiver;
private OnSharedPreferenceChangeListener m_prefsListener;
@ -195,9 +193,9 @@ public class SMSService extends XWService {
{
Intent intent = getIntentTo( context, SMSAction.INVITE );
intent.putExtra( PHONE, phone );
String asString = nli.toString();
Log.w( TAG, "inviteRemote(%s, '%s')", phone, asString );
intent.putExtra( GAMEDATA_STR, asString );
Log.w( TAG, "inviteRemote(%s, '%s')", phone, nli );
byte[] data = nli.asByteArray();
intent.putExtra( GAMEDATA_BA, data );
context.startService( intent );
}
@ -339,7 +337,8 @@ public class SMSService extends XWService {
break;
case INVITE:
phone = intent.getStringExtra( PHONE );
inviteRemote( phone, intent.getStringExtra( GAMEDATA_STR ) );
buffer = intent.getByteArrayExtra( GAMEDATA_BA );
inviteRemote( phone, buffer );
break;
case ADDED_MISSING:
NetLaunchInfo nli
@ -360,7 +359,7 @@ public class SMSService extends XWService {
break;
case RESEND:
phone = intent.getStringExtra( PHONE );
resendFor( phone, null, false );
resendFor( phone );
}
}
@ -375,100 +374,59 @@ public class SMSService extends XWService {
return result;
} // onStartCommand
private void inviteRemote( String phone, String nliData )
private void inviteRemote( String phone, byte[] asBytes )
{
Log.i( TAG, "inviteRemote()" );
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 );
}
resendFor( phone, SMS_CMD.INVITE, 0, asBytes, true );
}
private void ackInvite( String phone, int gameID )
{
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
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 );
}
resendFor( phone, SMS_CMD.ACK_INVITE, gameID, null );
}
private void sendDiedPacket( String phone, int gameID )
{
if ( !s_sentDied.contains(gameID) ) {
ByteArrayOutputStream bas = new ByteArrayOutputStream( 32 );
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 );
}
resendFor( phone, SMS_CMD.DEATH, gameID, null );
}
}
public int sendPacket( String phone, int gameID, byte[] bytes )
{
int nSent = -1;
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
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;
resendFor( phone, SMS_CMD.DATA, gameID, bytes );
return bytes.length;
}
private void send( SMS_CMD cmd, byte[] bytes, String phone )
throws java.io.IOException
private void sendOrRetry( byte[][] msgs, String toPhone, int waitSecs )
{
Log.d( TAG, "send(%s, len=%d)", cmd, bytes.length );
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
DataOutputStream dos = new DataOutputStream( bas );
dos.writeByte( SMS_PROTO_VERSION );
if ( SMS_PROTO_VERSION_WITHPORT <= SMS_PROTO_VERSION ) {
dos.writeShort( getNBSPort() );
if ( null != msgs ) {
sendBuffers( msgs, toPhone );
}
if ( waitSecs > 0 ) {
postResend( toPhone, waitSecs );
}
}
dos.writeByte( cmd.ordinal() );
dos.write( bytes, 0, bytes.length );
dos.flush();
byte[] data = bas.toByteArray();
private void resendFor( String toPhone, SMS_CMD cmd, int gameID, byte[] data )
{
boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this );
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 };
byte[][] msgs = XwJNI.smsproto_prepOutbound( data, phone, forceNow, waitSecs );
if ( null != msgs ) {
sendBuffers( msgs, phone );
}
if ( waitSecs[0] > 0 ) {
Assert.assertFalse( forceNow );
postResend( phone, waitSecs[0] );
}
byte[][] msgs = XwJNI.smsproto_prepOutbound( cmd, gameID, data, toPhone,
getNBSPort(), forceNow,
waitSecs );
sendOrRetry( msgs, toPhone, waitSecs[0] );
}
private void postResend( final String phone, final int waitSecs )
@ -492,97 +450,46 @@ public class SMSService extends XWService {
} ).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() );
DataInputStream dis =
new DataInputStream( new ByteArrayInputStream(data) );
try {
switch( cmd ) {
Log.i( TAG, "receive(cmd=%s)", msg.cmd );
switch( msg.cmd ) {
case INVITE:
String nliData = dis.readUTF();
makeForInvite( phone, new NetLaunchInfo( this, nliData ) );
makeForInvite( phone, NetLaunchInfo.makeFrom( this, msg.data ) );
break;
case DATA:
int gameID = dis.readInt();
byte[] rest = new byte[dis.available()];
dis.readFully( rest );
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
if ( feedMessage( msg.gameID, msg.data, new CommsAddrRec( phone ) ) ) {
SMSResendReceiver.resetTimer( this );
}
break;
case DEATH:
gameID = dis.readInt();
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
postEvent( MultiEvent.MESSAGE_NOGAME, msg.gameID );
break;
case ACK:
gameID = dis.readInt();
postEvent( MultiEvent.NEWGAME_SUCCESS,
gameID );
case ACK_INVITE:
postEvent( MultiEvent.NEWGAME_SUCCESS, msg.gameID );
break;
default:
Log.w( TAG, "unexpected cmd %s", cmd.toString() );
Log.w( TAG, "unexpected cmd %s", msg.cmd );
Assert.assertFalse( BuildConfig.DEBUG );
break;
}
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
}
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 ) {
for ( byte[] msg : msgs ) {
if ( !disAssemble( senderPhone, msg ) ) {
Log.e( TAG, "failed on message from %s", senderPhone );
}
for ( SMSProtoMsg msg : msgs ) {
receive( msg, senderPhone );
}
postEvent( MultiEvent.SMS_RECEIVE_OK );
} else {
Log.w( TAG, "receiveBuffer(): bogus or incomplete message from phone %s",
Log.d( TAG, "receiveBuffer(): bogus or incomplete message from %s",
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
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 )
{
if ( handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS ) ) {
if ( nli != null ) {
handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS );
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()
{
Activity activity = getActivity();

View file

@ -760,7 +760,7 @@ public class WiDirService extends XWService {
{
Log.d( TAG, "handleGotInvite()" );
String nliData = intent.getStringExtra( KEY_NLI );
NetLaunchInfo nli = new NetLaunchInfo( this, nliData );
NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, nliData );
String returnMac = intent.getStringExtra( KEY_SRC );
if ( !handleInvitation( nli, returnMac, DictFetchOwner.OWNER_P2P ) ) {
@ -774,6 +774,12 @@ public class WiDirService extends XWService {
Log.e( TAG, "postNotification() doing nothing" );
}
@Override
MultiMsgSink getSink( long rowid )
{
return m_sink;
}
private void handleGameGone( Intent intent )
{
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 ) );
DbgUtils.logEnable( this );
ConnStatusHandler.loadState( this );
OnBootReceiver.startTimers( 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 );
// Return true if able to start game only
protected boolean handleInvitation( NetLaunchInfo nli, String device,
DictFetchOwner dfo )
{
@ -110,6 +111,7 @@ abstract class XWService extends Service {
}
postNotification( device, nli.gameID(), rowid );
success = true;
} else {
Intent intent = MultiService
.makeMissingDictIntent( this, nli, dfo );
@ -118,6 +120,7 @@ abstract class XWService extends Service {
}
}
}
Log.d( TAG, "handleInvitation() => %b", success );
return success;
}
@ -130,7 +133,7 @@ abstract class XWService extends Service {
}
// 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,
MultiMsgSink sink, byte[] msg,

View file

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

View file

@ -772,10 +772,11 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue
static jobjectArray
msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr )
{
XP_ASSERT( arr->format == FORMAT_NET );
jclass clas = (*env)->FindClass( env, "[B" );
jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL );
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 );
(*env)->SetObjectArrayElement( env, result, ii, arr );
deleteLocalRef( env, arr );
@ -783,15 +784,43 @@ msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr )
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
Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData,
jstring jToPhone, jint jNow, jboolean jForce, jintArray jWaitSecsArr )
( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jCmd, jint jGameID,
jbyteArray jData, jstring jToPhone, jint jPort, jboolean jForce,
jintArray jWaitSecsArr )
{
jobjectArray result = NULL;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &globalState->ti, env );
SMS_CMD cmd = jEnumToInt( env, jCmd );
jbyte* data = NULL;
int len = 0;
if ( NULL != jData ) {
@ -801,8 +830,9 @@ Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
const char* toPhone = (*env)->GetStringUTFChars( env, jToPhone, NULL );
XP_U16 waitSecs;
SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, (const XP_U8*)data,
len, toPhone, jForce, &waitSecs );
SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, cmd, jGameID,
(const XP_U8*)data, len,
toPhone, jPort, jForce, &waitSecs );
if ( !!arr ) {
result = msgArrayToByteArrays( env, arr );
smsproto_freeMsgArray( globalState->smsProto, arr );
@ -821,21 +851,22 @@ Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData,
jstring jFromPhone )
jstring jFromPhone, jint jWantPort )
{
jobjectArray result = NULL;
if ( !!jData ) {
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &globalState->ti, env );
if ( !!jData ) {
int len = (*env)->GetArrayLength( env, jData );
jbyte* data = (*env)->GetByteArrayElements( env, jData, NULL );
const char* fromPhone = (*env)->GetStringUTFChars( env, jFromPhone, NULL );
SMSMsgArray* arr = smsproto_prepInbound( globalState->smsProto, fromPhone,
(XP_U8*)data, len );
jWantPort, (XP_U8*)data, len );
if ( !!arr ) {
result = msgArrayToByteArrays( env, arr );
result = msgArrayToJMsgArray( env, 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
NODE=xw4
VARIANT=xw4
CLASSPATH=${CLASSPATH:-""}
usage() {
[ $# -gt 0 ] && echo "Error: $1"
echo "usage: $0 "
echo "usage: $0 [--variant xw4|xw4d]"
exit 1
}
while [ $# -gt 0 ]; do
case $1 in
--help) usage
--help)
usage
;;
*) usage "unexpected flag $1"
--variant)
VARIANT=$2
shift
;;
*)
usage "unexpected flag $1"
;;
esac
shift
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
javap -s org.eehouse.android.${NODE}.jni.XwJNI

View file

@ -36,8 +36,9 @@ echo "# Generated by $0; do not edit!!!" > $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
echo "APP_ABI := armeabi-v7a" >> $TMP_MK
echo "APP_ABI := armeabi" >> $TMP_MK
elif [ -n "$XWORDS_DEBUG_X86ONLY" ]; then
echo "APP_ABI := x86" >> $TMP_MK
else

View file

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

View file

@ -79,6 +79,7 @@ typedef struct ServerVolatiles {
GameOverListener gameOverListener;
void* gameOverData;
XP_Bool showPrevMove;
XP_Bool pickTilesCalled[MAX_NUM_PLAYERS];
} ServerVolatiles;
typedef struct ServerNonvolatiles {
@ -1062,6 +1063,8 @@ server_tilesPicked( ServerCtxt* server, XP_U16 player,
const TrayTileSet* newTilesP )
{
XP_ASSERT( 0 == model_getNumTilesInTray( server->vol.model, player ) );
XP_ASSERT( server->vol.pickTilesCalled[player] );
server->vol.pickTilesCalled[player] = XP_FALSE;
TrayTileSet newTiles = *newTilesP;
pool_removeTiles( server->pool, &newTiles );
@ -1089,6 +1092,12 @@ informNeedPickTiles( ServerCtxt* server, XP_Bool initial, XP_U16 turn,
XP_Bool asking = nToPick > 0;
if ( asking ) {
/* We need to make sure we only call util_informNeedPickTiles once
without it returning. Even if server_do() is called a lot. */
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 );
@ -1096,6 +1105,7 @@ informNeedPickTiles( ServerCtxt* server, XP_Bool initial, XP_U16 turn,
util_informNeedPickTiles( server->vol.util, initial, turn,
nToPick, nFaces, faces, counts );
}
}
return asking;
}
@ -1923,6 +1933,7 @@ makePoolOnce( ServerCtxt* server )
static XP_Bool
assignTilesToAll( ServerCtxt* server )
{
LOG_FUNC();
XP_Bool allDone = XP_TRUE;
XP_U16 numAssigned;
XP_U16 ii;
@ -1962,6 +1973,7 @@ assignTilesToAll( ServerCtxt* server )
}
sortTilesIf( server, ii );
}
LOG_RETURNF( "%d", allDone );
return allDone;
} /* assignTilesToAll */
@ -2466,6 +2478,7 @@ server_commitMove( ServerCtxt* server, TrayTileSet* newTilesP )
XP_ASSERT( turn >= 0 );
pool_removeTiles( server->pool, &newTiles );
server->vol.pickTilesCalled[turn] = XP_FALSE;
nTilesMoved = model_getCurrentMoveCount( model, turn );
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
if even the smallest new message is likely to put us over. */
# define SEND_NOW_SIZE MAX_LEN_BINARY
/* To match the SMSService format */
# define SMS_PROTO_VERSION_JAVA 1
# define SMS_PROTO_VERSION 1
# define SMS_PROTO_VERSION_COMBO 2
@ -38,7 +42,7 @@
typedef struct _MsgRec {
XP_U32 createSeconds;
SMSMsg msg;
SMSMsgNet msgNet;
} MsgRec;
typedef struct _ToPhoneRec {
@ -82,15 +86,18 @@ struct SMSProto {
#define KEY_NEXTID PERSIST_KEY("nextID")
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,
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 port, XP_U32 gameID, const XP_U8* buf,
XP_U16 buflen, XP_U32 nowSeconds );
static void addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
int indx, int count, const XP_U8* data, XP_U16 len );
static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr,
const XP_UCHAR* fromPhone, int msgID );
const XP_UCHAR* fromPhone, XP_U16 wantPort,
int msgID );
static void savePartials( SMSProto* state );
static void restorePartials( SMSProto* state );
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
* 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
@ -159,26 +208,27 @@ smsproto_free( SMSProto* state )
* UtilCtxt around.
*/
SMSMsgArray*
smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* toPhone,
XP_Bool forceOld, XP_U16* waitSecsP )
smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
const void* buf, XP_U16 buflen, const XP_UCHAR* toPhone,
int toPort, XP_Bool forceOld, XP_U16* waitSecsP )
{
XP_USE( toPort );
SMSMsgArray* result = NULL;
#ifdef DEBUG
XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen );
XP_LOGF( "%s(): len=%d, sum=%s, toPhone=%s", __func__, buflen,
checksum, toPhone );
XP_LOGF( "%s(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", __func__, cmd,
gameID, buflen, checksum, toPhone );
XP_FREEP( state->mpool, &checksum );
#endif
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 */
XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil );
if ( !!buf ) {
addToRec( state, rec, buf, buflen, nowSeconds );
if ( cmd != NONE ) {
addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds );
}
/* 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 ) {
result = toMsgs( state, rec, forceOld );
result = toNetMsgs( state, rec, forceOld );
freeForPhone( state, toPhone );
}
@ -207,27 +257,45 @@ smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
}
static SMSMsgArray*
appendMsg( SMSProto* state, SMSMsgArray* arr, SMSMsg* msg )
appendLocMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgLoc* msg )
{
if ( NULL == arr ) {
arr = XP_CALLOC( state->mpool, sizeof(*arr) );
arr->format = FORMAT_LOC;
} else {
XP_ASSERT( arr->format == FORMAT_LOC );
}
arr->msgs = XP_REALLOC( state->mpool, arr->msgs,
(arr->nMsgs + 1) * sizeof(*arr->msgs) );
arr->msgs[arr->nMsgs++] = *msg;
arr->u.msgsLoc = XP_REALLOC( state->mpool, arr->u.msgsLoc,
(arr->nMsgs + 1) * sizeof(*arr->u.msgsLoc) );
arr->u.msgsLoc[arr->nMsgs++] = *msg;
return arr;
}
static SMSMsgArray*
appendNetMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgNet* msg )
{
if ( NULL == arr ) {
arr = XP_CALLOC( state->mpool, sizeof(*arr) );
arr->format = FORMAT_NET;
} else {
XP_ASSERT( arr->format == FORMAT_NET );
}
arr->u.msgsNet = XP_REALLOC( state->mpool, arr->u.msgsNet,
(arr->nMsgs + 1) * sizeof(*arr->u.msgsNet) );
arr->u.msgsNet[arr->nMsgs++] = *msg;
return arr;
}
SMSMsgArray*
smsproto_prepInbound( SMSProto* state, 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 );
checkThread( state );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
XWStreamCtxt* stream = mkStream( state );
stream_putBytes( stream, data, len );
SMSMsgArray* result = NULL;
@ -244,7 +312,7 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
XP_U8 buf[len];
stream_getBytes( stream, 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 );
}
}
@ -253,19 +321,39 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
XP_U8 oneLen, msgID;
while ( stream_gotU8( stream, &oneLen )
&& stream_gotU8( stream, &msgID ) ) {
XP_U8 buf[oneLen];
if ( stream_gotBytes( stream, buf, oneLen ) ) {
SMSMsg msg = { .len = oneLen,
.msgID = msgID,
.data = XP_MALLOC( state->mpool, oneLen ),
XP_U8 tmp[oneLen];
stream_getBytes( stream, tmp, oneLen );
XWStreamCtxt* msgStream = mkStream( state );
stream_putBytes( msgStream, tmp, oneLen );
XP_U32 gameID;
XP_U16 port;
SMS_CMD cmd;
if ( headerFromStream( msgStream, &cmd, &port, &gameID ) ) {
XP_U16 msgLen = stream_getSize( msgStream );
XP_U8 buf[msgLen];
if ( stream_gotBytes( msgStream, buf, msgLen ) ) {
if ( port == wantPort ) {
SMSMsgLoc msg = { .len = msgLen,
.cmd = cmd,
.gameID = gameID,
.data = XP_MALLOC( state->mpool, msgLen ),
};
XP_MEMCPY( msg.data, buf, oneLen );
result = appendMsg( state, result, &msg );
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;
default:
/* Don't assert! happens all the time */
XP_LOGF( "%s(): unexpected proto %d", __func__, proto );
break;
}
@ -283,17 +371,31 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
checkThread( state );
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 );
}
static void
freeMsg( SMSProto* state, MsgRec** msgp )
{
XP_FREEP( state->mpool, &(*msgp)->msg.data );
XP_FREEP( state->mpool, &(*msgp)->msgNet.data );
XP_FREEP( state->mpool, msgp );
}
@ -353,20 +455,28 @@ freeForPhone( SMSProto* state, const XP_UCHAR* phone )
}
static void
addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf, XP_U16 buflen,
addToOutRec( SMSProto* state, ToPhoneRec* rec, SMS_CMD cmd,
XP_U16 port, XP_U32 gameID, const XP_U8* buf, XP_U16 buflen,
XP_U32 nowSeconds )
{
XWStreamCtxt* stream = mkStream( state );
headerToStream( stream, cmd, port, gameID );
stream_putBytes( stream, buf, buflen );
MsgRec* mRec = XP_CALLOC( state->mpool, sizeof(*rec) );
mRec->msg.len = buflen;
mRec->msg.data = XP_MALLOC( state->mpool, buflen );
XP_MEMCPY( mRec->msg.data, buf, buflen );
XP_U16 len = stream_getSize( stream );
mRec->msgNet.len = len;
mRec->msgNet.data = XP_MALLOC( state->mpool, len );
XP_MEMCPY( mRec->msgNet.data, stream_getPtr(stream), len );
stream_destroy( stream );
mRec->createSeconds = nowSeconds;
rec->msgs = XP_REALLOC( state->mpool, rec->msgs, (1 + rec->nMsgs) * sizeof(*rec->msgs) );
rec->msgs[rec->nMsgs++] = mRec;
rec->totalSize += buflen;
rec->totalSize += len;
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 ) {
rec->createSeconds = nowSeconds;
@ -513,9 +623,7 @@ savePartials( SMSProto* state )
{
checkThread( state );
XWStreamCtxt* stream
= mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
XWStreamCtxt* stream = mkStream( state );
stream_putU8( stream, PARTIALS_FORMAT );
stream_putU8( stream, state->nFromPhones );
@ -553,8 +661,8 @@ savePartials( SMSProto* state )
static void
restorePartials( SMSProto* state )
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
XWStreamCtxt* stream = mkStream( state );
dutil_loadStream( state->dutil, KEY_PARTIALS, stream );
if ( stream_getSize( stream ) >= 1
&& PARTIALS_FORMAT == stream_getU8( stream ) ) {
@ -585,7 +693,7 @@ restorePartials( SMSProto* state )
static SMSMsgArray*
completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
int msgID )
XP_U16 wantPort, int msgID )
{
int fromPhoneIndex, msgIDIndex;
MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE,
@ -607,16 +715,30 @@ completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
}
if ( haveAll ) {
SMSMsg msg = { .len = len,
.msgID = msgID,
XWStreamCtxt* stream = mkStream( state );
for ( int ii = 0; ii < rec->count; ++ii ) {
stream_putBytes( stream, rec->parts[ii].data, rec->parts[ii].len );
}
XP_U32 gameID;
XP_U16 port;
SMS_CMD cmd;
if ( headerFromStream( stream, &cmd, &port, &gameID ) ) {
XP_U16 len = stream_getSize( stream );
SMSMsgLoc msg = { .len = len,
.cmd = cmd,
.gameID = gameID,
.data = XP_MALLOC( state->mpool, len ),
};
XP_U8* ptr = msg.data;
for ( int ii = 0; ii < rec->count; ++ii ) {
XP_MEMCPY( ptr, rec->parts[ii].data, rec->parts[ii].len );
ptr += rec->parts[ii].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 );
}
arr = appendMsg( state, arr, &msg );
}
stream_destroy( stream );
freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex );
}
@ -625,13 +747,13 @@ completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
}
static SMSMsgArray*
toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
toNetMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
{
SMSMsgArray* result = NULL;
for ( XP_U16 ii = 0; ii < rec->nMsgs; ) {
// XP_LOGF( "%s(): looking at msg %d of %d", __func__, ii, rec->nMsgs );
XP_U16 count = (rec->msgs[ii]->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
combined */
@ -639,7 +761,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
int sum = 0;
if ( count == 1 && !forceOld ) {
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 ) {
break;
}
@ -654,23 +776,23 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
last - 1, nMsgs );
}
int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */
SMSMsg newMsg = { .len = len,
SMSMsgNet newMsg = { .len = len,
.data = XP_MALLOC( state->mpool, len )
};
int indx = 0;
newMsg.data[indx++] = SMS_PROTO_VERSION_COMBO;
for ( int jj = ii; jj < last; ++jj ) {
const SMSMsg* msg = &rec->msgs[jj]->msg;
const SMSMsgNet* msg = &rec->msgs[jj]->msgNet;
newMsg.data[indx++] = msg->len;
newMsg.data[indx++] = nextMsgID( state );
XP_MEMCPY( &newMsg.data[indx], msg->data, msg->len ); /* bad! */
indx += msg->len;
}
result = appendMsg( state, result, &newMsg );
result = appendNetMsg( state, result, &newMsg );
ii = last;
} else {
int msgID = nextMsgID( state );
const SMSMsg* msg = &rec->msgs[ii]->msg;
const SMSMsgNet* msg = &rec->msgs[ii]->msgNet;
XP_U8* nextStart = msg->data;
XP_U16 lenLeft = msg->len;
for ( XP_U16 indx = 0; indx < count; ++indx ) {
@ -681,7 +803,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
}
lenLeft -= useLen;
SMSMsg newMsg = { .len = useLen + 4,
SMSMsgNet newMsg = { .len = useLen + 4,
.data = XP_MALLOC( state->mpool, useLen + 4 )
};
newMsg.data[0] = SMS_PROTO_VERSION;
@ -691,7 +813,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
XP_MEMCPY( newMsg.data + 4, nextStart, useLen );
nextStart += useLen;
result = appendMsg( state, result, &newMsg );
result = appendNetMsg( state, result, &newMsg );
}
++ii;
}
@ -710,6 +832,14 @@ nextMsgID( SMSProto* state )
return result;
}
static XWStreamCtxt*
mkStream( SMSProto* state )
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
return stream;
}
#ifdef DEBUG
static void
checkThread( SMSProto* state )
@ -745,17 +875,19 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
}
/* Loop until all the messages are ready. */
const XP_U32 gameID = 12344321;
const int port = 1;
for ( XP_Bool firstTime = XP_TRUE; ; firstTime = XP_FALSE) {
XP_Bool allDone = XP_TRUE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
XP_U16 waitSecs;
if ( firstTime ) {
XP_U16 len = (ii + 1) * 30;
arrs[ii] = smsproto_prepOutbound( state, (XP_U8*)buf, len, phones[ii],
forceOld, &waitSecs );
arrs[ii] = smsproto_prepOutbound( state, DATA, gameID, buf, len, phones[ii],
port, forceOld, &waitSecs );
} else if ( NULL == arrs[ii]) {
arrs[ii] = smsproto_prepOutbound( state, NULL, 0, phones[ii],
forceOld, &waitSecs );
arrs[ii] = smsproto_prepOutbound( state, DATA, gameID, NULL, 0, phones[ii],
port, forceOld, &waitSecs );
} else {
continue;
}
@ -772,13 +904,17 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
XP_Bool haveOne = XP_FALSE;
for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
if (!!arrs[ii] && indx < arrs[ii]->nMsgs) {
XP_ASSERT( arrs[ii]->format == FORMAT_NET );
haveOne = XP_TRUE;
SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii],
arrs[ii]->msgs[indx].data,
arrs[ii]->msgs[indx].len );
SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii], port,
arrs[ii]->u.msgsNet[indx].data,
arrs[ii]->u.msgsNet[indx].len );
if ( !!outArr ) {
SMSMsg* msg = &outArr->msgs[0];
XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
XP_ASSERT( outArr->format == FORMAT_LOC );
SMSMsgLoc* msg = &outArr->u.msgsLoc[0];
XP_ASSERT( msg->gameID == gameID );
XP_ASSERT( msg->cmd == DATA );
// XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
XP_ASSERT( outArr->nMsgs == 1 );
XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) );
smsproto_freeMsgArray( state, outArr );
@ -796,8 +932,8 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
/* Now let's send a bunch of small messages that should get combined */
for ( int nUsed = 0; ; ++nUsed ) {
XP_U16 waitSecs;
SMSMsgArray* sendArr = smsproto_prepOutbound( state, (XP_U8*)&buf[nUsed],
smallSiz, phones[0],
SMSMsgArray* sendArr = smsproto_prepOutbound( state, DATA, gameID, &buf[nUsed],
smallSiz, phones[0], port,
XP_FALSE, &waitSecs );
if ( sendArr == NULL ) {
XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz );
@ -805,17 +941,21 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
}
XP_ASSERT( waitSecs == 0 );
XP_ASSERT( sendArr->format == FORMAT_NET );
int totalBack = 0;
for ( int jj = 0; jj < sendArr->nMsgs; ++jj ) {
SMSMsgArray* recvArr = smsproto_prepInbound( state, phones[0],
sendArr->msgs[jj].data,
sendArr->msgs[jj].len );
SMSMsgArray* recvArr = smsproto_prepInbound( state, phones[0], port,
sendArr->u.msgsNet[jj].data,
sendArr->u.msgsNet[jj].len );
if ( !!recvArr ) {
XP_ASSERT( recvArr->format == FORMAT_LOC );
XP_LOGF( "%s(): got %d msgs (from %d)", __func__, recvArr->nMsgs, nUsed + 1 );
for ( int kk = 0; kk < recvArr->nMsgs; ++kk ) {
SMSMsg* msg = &recvArr->msgs[kk];
XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
SMSMsgLoc* msg = &recvArr->u.msgsLoc[kk];
// XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
XP_ASSERT( msg->gameID == gameID );
XP_ASSERT( msg->cmd == DATA );
XP_ASSERT( msg->len == smallSiz );
XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) );
++totalBack;
@ -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
sure it's cleaned up correctly */
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 );
/* 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 );
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 */
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 ) {
SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data,
arr->msgs[ii].len );
SMSMsgArray* out = smsproto_prepInbound( state, "33333", port,
arr->u.msgsNet[ii].data,
arr->u.msgsNet[ii].len );
if ( !!out ) {
XP_ASSERT( out->nMsgs == 1);
XP_ASSERT( out->format == FORMAT_LOC );
XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii );
XP_ASSERT( out->msgs[0].len == 200 );
XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) );
XP_ASSERT( out->u.msgsLoc[0].len == 200 );
XP_ASSERT( 0 == memcmp( out->u.msgsLoc[0].data, buf, 200 ) );
smsproto_freeMsgArray( state, out );
break;
}

View file

@ -44,23 +44,44 @@
#include "xptypes.h"
#include "mempool.h" /* debug only */
#include "nli.h"
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 msgID;
XP_U8* data;
} SMSMsg;
} SMSMsgNet;
typedef enum { FORMAT_NONE, FORMAT_LOC, FORMAT_NET } SMS_FORMAT;
typedef struct _SMSMsgArray {
XP_U16 nMsgs;
SMSMsg* msgs;
SMS_FORMAT format;
union {
SMSMsgNet* msgsNet;
SMSMsgLoc* msgsLoc;
} u;
} SMSMsgArray;
struct SMSProto* smsproto_init( MPFORMAL XW_DUtilCtxt* dutil );
void smsproto_free( SMSProto* state );
/* 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,
* 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
* 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 );
/* When a message is received, pass it in for reassambly. Non-null return
means one or more messages is ready for consumption. */
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 );

View file

@ -189,6 +189,26 @@ stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr )
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
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 );
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_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr

View file

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

View file

@ -746,26 +746,29 @@ gtkNoticeRcvd( void* closure )
}
static void
smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
XP_U32 gameID, XP_U16 dictLang, const XP_UCHAR* dictName,
XP_U16 nPlayers, XP_U16 nHere, XP_U16 forceChannel,
/* smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), */
/* XP_U32 gameID, XP_U16 dictLang, const XP_UCHAR* dictName, */
/* XP_U16 nPlayers, XP_U16 nHere, XP_U16 forceChannel, */
/* const CommsAddrRec* returnAddr ) */
smsInviteReceived( void* closure, const NetLaunchInfo* nli,
const CommsAddrRec* returnAddr )
{
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
LaunchParams* params = apg->params;
XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, "
"nHere=%d, forceChannel=%d)", __func__, gameName, gameID, dictName,
nPlayers, nHere, forceChannel );
"nHere=%d, forceChannel=%d)", __func__, nli->gameName,
nli->gameID, nli->dict, nli->nPlayersT,
nli->nPlayersH, nli->forceChannel );
CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, &params->pgi );
gi_setNPlayers( &gi, nPlayers, nHere );
gi.gameID = gameID;
gi.dictLang = dictLang;
gi.forceChannel = forceChannel;
gi_setNPlayers( &gi, nli->nPlayersT, nli->nPlayersH );
gi.gameID = nli->gameID;
gi.dictLang = nli->lang;
gi.forceChannel = nli->forceChannel;
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) );
params->needsNewGame = XP_FALSE;

View file

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

View file

@ -23,12 +23,10 @@
#ifdef XWFEATURE_SMS
#include "main.h"
#include "nli.h"
typedef struct _SMSProcs {
void (*inviteReceived)( void* closure, const XP_UCHAR* gameName,
XP_U32 gameID, XP_U16 dictLang,
const XP_UCHAR* dictName, XP_U16 nPlayers,
XP_U16 nHere, XP_U16 forceChannel,
void (*inviteReceived)( void* closure, const NetLaunchInfo* nli,
const CommsAddrRec* returnAddr );
void (*msgReceived)( void* closure, const CommsAddrRec* from, XP_U32 gameID,
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_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID );
void linux_sms_invite( LaunchParams* params, const CurGameInfo* info,
const CommsAddrRec* addr, const gchar* gameName,
XP_U16 nMissing, int forceChannel,
void linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli,
const gchar* phone, int port );
void linux_sms_cleanup( LaunchParams* params );